【Ovirt 笔记】engine-iso-uploader 的实
2018-05-15 本文已影响6人
58bc06151329
文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
分析整理的版本为 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)