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

【Ovirt 笔记】engine-image-uploader

作者: 58bc06151329 | 来源:发表于2018-05-15 15:55 被阅读5次

文前说明

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

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

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

命令使用方式:
engine-image-uploader [options] list 显示导出存储域列表
engine-image-uploader [options] upload [file | directory] 上传开发格式为 OVF 的虚拟机。

选项组 说明
--version 显示程序的版本号
-h,--help 显示帮助信息
--quiet 控制台简洁输出(默认 false)
--log-file=PATH 日志文件路径(默认为 /var/log/ovirt-engine/ovirt-image-uploader/ovirt-image-uploader-yyyyMMddHHmmss.log
--conf-file=PATH 配置文件路径(默认为 /etc/ovirt-engine/imageuploader.conf
  • engine 配置,针对引擎 restApi 的授权。
engine 配置组 说明
-u,--user= restApi 用户,例如:user@engine.example.com,默认 admin@internal
-r,--engine= restApi IP 地址,例如:localhost:443
--cert-file=PATH CA 证书用来验证引擎(默认为 /etc/pki/ovirt-engine/ca.pem
--insecure 不验证引擎(默认 off)
  • 导出域配置,用于指定 OVF 上传到导出域配置。
导出域配置组 说明
-e,--export-domain 指定上传文件的导出域
-n,--nfs-server= 指定上传文件的 NFS 服务器,此选项是导出域的替代方案,不与导出域组合,例如:--nfs-server=example.com:/path/to/some/dir
-i,--ovf-id 如果不想更新 OVF 的 UUID,可以使用此选项指定,默认生成新的 UUID
-d,--disk-instance-id 如果不想更新磁盘的 UUID,可以使用此选项指定,默认生成新的 UUID(需要确保磁盘 UUID 没有冲突)
-m,--mac-address 如果不想更新 MAC 地址,可以使用此选项指定,默认生成新的 MAC 地址(需要确保 MAC 地址没有冲突)
-N,-name= 重命名新的镜像文件名称
  • 该功能只支持 OVF 虚拟机文件格式。

  • OVF 数据的特征

    • 如果使用 OVF 存档(而不是目录),则必须使用 GZIP 压缩创建。
    • OVF 数据应该包含以下格式的图像和主目录(内部布局)
|-- images
|   |-- <Image Group UUID>
|        |--- <Image UUID (this is the disk image)>
|        |--- <Image UUID (this is the disk image)>.meta
|-- master
|   |---vms
|       |--- <UUID>
|             |--- <UUID>.ovf
  • 显示导出存储域列表
[root@localhost ~]# engine-image-uploader list
Please provide the REST API password for the admin@internal oVirt Engine user (CTRL+D to abort): 
Export Storage Domain Name     | Datacenter                | Export Domain Status
myexportdom                    | 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-image-uploader 命令执行流程

解析参数和加载配置文件

  • 默认配置文件为 /etc/ovirt-engine/imageuploader.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)

镜像上传功能

imageup = ImageUploader(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_export_storage_domains() 方法查询导出域列表。
    • 通过调用 restApi 进行查询。
    • 查询所有存储域列表,再根据存储域类型过滤出导出域列表。
if self.api is None:
            # The API has not been initialized yet.
            try:
                self.configuration.prompt(
                    "engine",
                    msg=_("hostname of oVirt Engine")
                )
                self.configuration.prompt(
                    "user",
                    msg=_("REST API username for oVirt Engine")
                )
                self.configuration.getpass(
                    "passwd",
                    msg=(
                        _("REST API password for the %s oVirt Engine user") %
                        self.configuration.get("user")
                    )
                )
            except Configuration.SkipException:
                raise Exception(
                    "Insufficient information provided to communicate with "
                    "the oVirt Engine REST API."
                )

            url = "https://" + self.configuration.get("engine") + "/api"
  • UPLOAD 执行 upload_to_storage_domain() 方法上传 OVF 镜像。
    • 通过 restApi 根据导出域名称获取导出域的(ID、地址、路径等信息)。
    • 使用 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
  • 处理 OVF 文件。

    • 如果 OVF 存档是一个目录结构,并且格式相关也正确。
    • 查看 NFS 目录是否存在。
    • 检查 NFS 目录空间是否足够。
    • 查看临时 NFS 目录是否存在。
    • 检查临时 NFS 目录空间是否足够。
  • 上传 OVF 存档到临时 NFS 目录。

logging.debug('OVF data %s is a directory' % ovf_file)
                    ovf_file_size = self.get_ovf_dir_space(ovf_file)
                    if (ovf_file_size != -1 and self.update_ovf_xml(ovf_file)):
                        self.copy_files_nfs(ovf_file, dest_dir, address, ovf_file_size, ovf_file)
  • 再从临时 NFS 目录空间拷贝到 NFS 目录空间。
for root, dirs, files in os.walk(source_dir, topdown=True):
            for name in files:
                for paths in files_to_copy:
                    if str(paths).endswith(name):
                        remote_file = os.path.join(remote_dir, paths)
                        if name.endswith('.ovf'):
                            ovf_file = os.path.join(root,name)
                            remote_ovf_file = remote_file
                        else:
                            if not self.copy_file_nfs(os.path.join(root,name),
                                                      remote_file,
                                                      NUMERIC_VDSM_ID,
                                                      NUMERIC_VDSM_ID):
                                return
  • 将 .ovf 文件拷贝至 OVF 配置目录
self.copy_file_nfs(ovf_file, remote_ovf_file, NUMERIC_VDSM_ID,NUMERIC_VDSM_ID)
  • 上传 OVF 存档不是一个目录,则需要首先执行解压操作。
def unpack_ovf(self, ovf_file, dest_dir):
        '''Given a path to an OVF .tgz this function will unpack it into dest_dir. '''
        retVal = True
        try:
            tar = tarfile.open(ovf_file, "r:gz")
            tar.extractall(dest_dir)
        except Exception, e:
            retVal = False
            logging.error(_("Problem unpacking %s.  Message %s"
                    % (ovf_file,
                       str(e).strip())))
        finally:
            tar.close()
        return retVal
  • 上传完成后,执行 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:
              logging.debug("Cleaning up OVF extract directory %s" % ovf_extract_dir)
              shutil.rmtree(ovf_extract_dir)
       except Exception, e:
              ExitCodes.exit_code=ExitCodes.CLEANUP_ERR
              logging.debug(e)

相关文章

网友评论

    本文标题:【Ovirt 笔记】engine-image-uploader

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