美文网首页Ovirt程序员
【Ovirt 笔记】engine-iso-uploader 的实

【Ovirt 笔记】engine-iso-uploader 的实

作者: 58bc06151329 | 来源:发表于2018-05-15 18:00 被阅读6次

    文前说明

    作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

    本文仅供学习交流使用,侵权必删。
    不用于商业目的,转载请注明出处。

    分析整理的版本为 Ovirt 3.4.5 版本。

    命令使用方式:
    engine-iso-uploader [options] list 显示 ISO 存储域列表
    engine-iso-uploader [options] upload FILE [FILE]...[FILE] 上传 ISO 到存储域。

    选项组 说明
    --version 显示程序的版本号
    -h,--help 显示帮助信息
    --quiet 控制台简洁输出(默认 false)
    --log-file=PATH 日志文件路径(默认为 /var/log/ovirt-engine/ovirt-iso-uploader/ovirt-iso-uploader-yyyyMMddHHmmss.log
    --conf-file=PATH 配置文件路径(默认为 /etc/ovirt-engine/isouploader.conf
    --cert-file=PATH CA 证书用来验证引擎(默认为 /etc/pki/ovirt-engine/ca.pem
    --insecure 不验证引擎(默认 off)
    • engine 配置,针对引擎 restApi 的授权。
    engine 配置组 说明
    -u,--user= restApi 用户,例如:user@engine.example.com,默认 admin@internal
    -r,--engine= restApi IP 地址,例如:localhost:443
    ISO 存储域配置组 说明
    -i,--iso-domain= 指定上传文件的 ISO 存储域
    -n,--nfs-server= 指定上传文件的 NFS 服务器,此选项是 ISO 域的替代方案,不与 ISO 域组合,例如:--nfs-server=example.com:/path/to/some/dir
    • 默认情况下,程序会使用 NFS 将文件复制到 ISO 域,下面配置采用 SSH 文件传输方式。
    连接配置组 说明
    --ssh-user=root 指定用于 SSH 传输的 SSH 用户,必须为 root,目标文件服务器上的组 UID 和 GID 为 36
    --ssh-port= SSH 连接接口
    -k,--key-file= SSH Key 身份文件(私钥)用于访问文件服务器。
    • 该功能支持多个文件上传,支持通配符。

    • 显示 ISO 存储域列表

    [root@localhost ~]# engine-iso-uploader list
    Please provide the REST API password for the admin@internal oVirt Engine user (CTRL+D to abort): 
    ISO Storage Domain Name   | Datacenter                | ISO Domain Status
    myiso                     | Myowndc                   | active
    

    命令采用了 python 方式进行实现。

    optparse 模块

    • engine-image-uploader.sh 中使用了 optparse 模块,这是一个专门用来在命令行添加选项的一个模块。

    • 代码示例

    from optparse import OptionParser
    parser = OptionParser(...)
    parser.add_option(.....)
    
    • OptionParser 命令参数
      • 不要求一定要传递参数
    参数 说明
    usage 可以打印用法。
    version 在使用 %prog --version 的时候输出版本信息。
    description 描述信息
    • add_option 添加命令行参数
    参数 说明
    action 指示 optparser 解析参数时候该如何处理。默认是 ' store ' 将命令行参数值保存 options 对象里 。action 的值有 store、store_true、store_false、store_const、append、count、callback。
    type 默认是 string,也可以是 int、float 等。
    dest 如果没有指定 dest 参数,将用命令行参数名来对 options 对象的值进行存取。
    store store 可以为 store_true 和 store_false 两种形式。用于处理命令行参数后面不带值的情况。如 -v、-q 等命令行参数。
    default 设置默认值。
    help 指定帮助文档。
    metavar 提示用户期望参数。
    • parse_args 解析命令行形参

      • (options, args) = parser.parse_args() 可以传递一个参数列表给 parse_args()。否则,默认使用命令行参数 (sysargv[1:])。
      • parse_args() 返回两个值
        • options 这是一个对象(optpars.Values),保存有命令行参数值。只要知道命令行参数名,如 file,就可以访问其对应的值 options.file。
        • args,一个由 positional arguments 组成的列表。
    • 如果 options 很多的时候,可以进行分组

    group = OptionGroup(parser)
    group.add_option()
    parser.add_option_group(group)
    

    shutil 模块

    • engine-image-uploader.sh 中使用了 shutil 模块,这是一个高级的文件、文件夹、压缩包 处理模块。
    命令 说明
    shutil.copyfileobj(fsrc, fdst[, length]) 将文件内容拷贝到另一个文件中
    shutil.copyfile(src, dst) 拷贝文件
    shutil.copy(src, dst) 拷贝文件和权限
    shutil.copy2(src, dst) 拷贝文件和状态信息
    shutil.copymode(src, dst) 仅拷贝权限。内容、组、用户均不变
    shutil.copystat(src, dst) 仅拷贝状态的信息,即文件属性,包括:mode bits, atime, mtime, flags
    shutil.ignore_patterns(*patterns) 忽略哪个文件,有选择性的拷贝
    shutil.copytree(src, dst, symlinks=False, ignore=None) 递归的去拷贝文件夹
    shutil.rmtree(path[, ignore_errors[, onerror]]) 递归的去删除文件
    shutil.move(src, dst) 递归的去移动文件,它类似 mv 命令,其实就是重命名。
    shutil.make_archive(base_name, format,...) 创建压缩包并返回文件路径,例如:zip、tar

    engine-iso-uploader 命令执行流程

    解析参数和加载配置文件

    • 默认配置文件为 /etc/ovirt-engine/isouploader.conf 在这个配置文件中也可以定义一些参数。(如:RestApi 的用户名和密码等)
    conf = None
    conf = Configuration(parser)
    
    class Configuration(dict)
    ......
    if not parser:
        raise Exception("Configuration requires a parser")
    self.options, self.args = self.parser.parse_args()
    
    self.load_config_file()
    
    if self.args:
       self.from_args(self.args)
    

    ISO 上传功能

    isoup = ISOUploader(conf)
    
    组装不同的 cmd 命令
    class Caller(object):
        """
        Utility class for forking programs.
        """
        def __init__(self, configuration):
            self.configuration = configuration
    
        def prep(self, cmd):
            _cmd = cmd % self.configuration
            logging.debug(_cmd)
            return shlex.split(_cmd)
    
        def call(self, cmds):
            """Uses the configuration to fork a subprocess and run cmds"""
            _cmds = self.prep(cmds)
            logging.debug("_cmds(%s)" % _cmds)
            proc = subprocess.Popen(_cmds,
                       stdout=subprocess.PIPE,
                       stderr=subprocess.PIPE)
            stdout, stderr = proc.communicate()
            returncode = proc.returncode
            logging.debug("returncode(%s)" % returncode)
            logging.debug("STDOUT(%s)" % stdout)
            logging.debug("STDERR(%s)" % stderr)
    
            if returncode == 0:
                return (stdout,returncode)
            else:
                raise Exception(stderr)
    
    根据命令类型的不同执行不同的方法
    • LIST 执行 list_all_ISO_storage_domains() 方法查询导出域列表。
      • 通过调用 restApi 进行查询。
      • 查询所有存储域列表,再根据存储域类型过滤出导出域列表。
     def list_all_ISO_storage_domains(self):
            """
            List only the ISO storage domains in sorted format.
            """
            def get_name(ary):
                return ary[0]
    
            if not self._initialize_api():
                sys.exit(ExitCodes.CRITICAL)
    
            dcAry = self.api.datacenters.list()
            if dcAry is not None:
                isoAry = []
                for dc in dcAry:
                    dcName = dc.get_name()
                    logging.debug("Found a DC named(%s)" % dcName)
                    domainAry = dc.storagedomains.list()
                    if domainAry is not None:
                        for domain in domainAry:
                            if domain.get_type() == 'iso':
                                status = domain.get_status()
                                if status is not None:
                                    isoAry.append(
                                        [
                                            domain.get_name(),
                                            dcName,
                                            status.get_state()
                                        ]
                                    )
                                else:
                                    logging.debug(
                                        "the storage domain didn't "
                                        "have a status element."
                                    )
                    else:
                        logging.debug(
                            _("DC(%s) does not have a storage domain."),
                            dcName
                        )
    
    • UPLOAD 执行 upload_to_storage_domain() 方法上传 ISO。

      • 通过 restApi 根据导出域名称获取 ISO 存储域的(ID、地址、路径等信息)。
      • ISO 默认文件目录为 images/11111111-1111-1111-1111-111111111111
    • 上传支持 NFS 和 SSH 两种上传方式。

      • 通过参数和配置文件获取组装 SSH 命令。
    cmd = self.format_ssh_command(SCP)
    
    def format_ssh_command(self, cmd=SSH):
            cmd = "%s " % cmd
            port_flag = "-p" if cmd.startswith(SSH) else "-P"
            if "ssh_port" in self.configuration:
                cmd += port_flag + " %(ssh_port)s " % self.configuration
            if "key_file" in self.configuration:
                cmd += "-i %(key_file)s " % self.configuration
            return cmd
    
    • 使用 mount 命令挂载远程共享临时目录。
    MOUNT='/bin/mount'
    NFS_MOUNT_OPTS = '-t nfs -o rw,sync,soft'
    def format_nfs_command(self, address, export, dir):
            cmd = '%s %s %s:%s %s' % (MOUNT, NFS_MOUNT_OPTS, address, export, dir)
            logging.debug('NFS mount command (%s)' % cmd)
            return cmd
    
    • 处理 ISO 文件。

      • 查看 NFS 目录是否存在。
      • 检查 NFS 目录空间是否足够。
      • 查看临时 NFS 目录是否存在。
      • 检查临时 NFS 目录空间是否足够。
    • 上传 ISO 文件到临时 NFS 目录。

    # NFS support.
                tmpDir = tempfile.mkdtemp()
                logging.debug('local NFS mount point is %s' % tmpDir)
                cmd = self.format_nfs_command(address, path, tmpDir)
                try:
                    self.caller.call(cmd)
                    getpwnam(NFS_USER)
                    for filename in self.configuration.files:
                        logging.info(_("Start uploading %s "), filename)
                        dest_dir = os.path.join(
                            tmpDir,
                            remote_path
                        )
                        dest_file = os.path.join(
                            dest_dir,
                            os.path.basename(filename)
                        )
                        retVal = self.exists_nfs(
                            dest_file,
                            NUMERIC_VDSM_ID,
                            NUMERIC_VDSM_ID
                        )
    
    • 再从临时 NFS 目录空间拷贝到 NFS 目录空间。
    if (dir_size > file_size):
                                    temp_dest_file = os.path.join(
                                        dest_dir,
                                        '.%s' % os.path.basename(filename)
                                    )
                                    if self.copy_file(
                                        filename,
                                        temp_dest_file,
                                        NUMERIC_VDSM_ID,
                                        NUMERIC_VDSM_ID
                                    ):
                                        if self.rename_file_nfs(
                                            temp_dest_file,
                                            dest_file,
                                            NUMERIC_VDSM_ID,
                                            NUMERIC_VDSM_ID
                                        ):
                                            if id is not None:
                                                # Force oVirt Engine to refresh
                                                #the list
                                                # of files in the ISO domain
                                                self.refresh_iso_domain(id)
                                            logging.info(
                                                _(
                                                    '{f} uploaded successfully'
                                                ).format(
                                                    f=filename,
                                                )
                                            )
    ......
    
    • 上传完成后,执行 umount 命令取消挂载远程共享临时目录。
    UMOUNT='/bin/umount'
    NFS_UMOUNT_OPTS = '-t nfs -f '
    cmd = '%s %s %s' % (UMOUNT, NFS_UMOUNT_OPTS, mount_dir)
    logging.debug(cmd)
    self.caller.call(cmd)
    
    • 最后删除临时文件目录及文件
    finally:
          try:
               cmd = '%s %s %s' % (UMOUNT, NFS_UMOUNT_OPTS, tmpDir)
               logging.debug(cmd)
               self.caller.call(cmd)
               shutil.rmtree(tmpDir)
           except Exception, e:
                ExitCodes.exit_code = ExitCodes.CLEANUP_ERR
                logging.debug(e)
    

    相关文章

      网友评论

        本文标题:【Ovirt 笔记】engine-iso-uploader 的实

        本文链接:https://www.haomeiwen.com/subject/rtdydftx.html