http://aspirer2004.blog.163.com/blog/#m=0
本文主要讨论nova/virt/libvirt/driver.py:_create_image的相关流程,只讨论file磁盘,不包括EBS盘(block设备)。
1. Resize过程的镜像拷贝优化
1.1 优化之前
首先通过libvirt的XMLDesc()方法拿到虚拟机的配置文件,然后从配置文件中读取所有file类型磁盘的信息(路径,driver,qcow2的backing file);然后如果是不同host之间resize,则qemu-img convert合并base和子镜像为qcow2(无backing file),之后通过rsync ssh方式拷贝合并后的镜像到新的host对于instance目录下,然后在if not os.path.exists(self.path) or not os.path.exists(base):则创建镜像,resize过程这个self.path是已经拷贝过来的,所以不需要创建镜像,也就是什么都不做。
1.2 优化之后(仅优化了resize过程,创建过程与优化之前相同)
拷贝镜像是用rsync的daemon push模式,并且不合并base和子镜像,只拷贝子镜像部分,然后在目标host上检查base是否存在,不存在则下载,扩容,最后qemu-img rebase把子镜像rebase到新的base上;目前第二块盘(disk.local)以及swap盘(disk.swap)是不拷贝的,因为如果拷贝过去的仅仅是子镜像,会导致base找不到,为disk.local、disk.swap准备base镜像这部分代码没有实现,所以在拷贝子镜像过程中忽略了disk.local,disk.swap目前没有配置,所以代码里面没有忽略,如果开启了swap的配置,则resize过程会出现问题(base找不到导致虚拟机无法启动)。
优化后的镜像生成流程:
#nova/virt/libvirt/driver.py:LibvirtDriver._create_image()
if snapshot_optimization \
and not self._volume_in_mapping(self.default_root_device,
block_device_info):
self._create_snapshot_image(context, instance,
disk_images['image_id'],
basepath('disk'), size)
# nova/virt/libvirt/driver.py:LibvirtDriver
# useoptimized snapshot image
# 优化后的resize过程镜像生成流程
def _create_snapshot_image(self, context, instance, image_id,
target, size):
# NOTE(hzwangpan): for resize operation, the'disk' is copied from
# source node before _create_image(), so ifwe fetch the 'disk' here,
# it will cover the 'disk' copied from source
# 只有当'disk'不存在的时候才下载'disk',M3从快照恢复流程优化的遗留代码
# M3的快照只有COW部分也即'disk',所以创建虚拟机的时候要先下载'disk',
# 然后根据其backing file的名称从glance下载它的base,这里就是下载'disk'
# 的流程,因为社区原有的下载镜像代码会转换镜像格式,而我们不需要转换,
# 所以这里新加了一个fetch_orig_image()方法。
# resize的时候instance目录下'disk'是存在的,已经从源端拷贝过来了。
if not os.path.exists(target):
libvirt_utils.fetch_orig_image(context=context, target=target,
image_id=image_id,
user_id=instance["user_id"],
project_id=instance["project_id"])
if not os.path.exists(target):
LOG.error(_("fetch image failed, image id: %s"), image_id,
instance=instance, context=context)
raise exception.CouldNotFetchImage(image_id)
# 查询'disk'的backing file信息,也即查找其base
backing_file = libvirt_utils.get_disk_backing_file(target)
if not backing_file:
LOG.error(_("get backing file of image %sfailed"), image_id,
instance=instance, context=context)
raise exception.ImageUnacceptable(image_id=image_id,
reason=_("%s doesn't has backing file") % target)
virtual_size = libvirt_utils.get_disk_size(target)
size = max(size, virtual_size)
# get base image by backing file
# 根据backing file名称下载base镜像
# 如果没有M3那种不完整的快照存在,则从backing file名称下载base镜像
# 的流程可以简化为根据image id下载镜像,因为每一个虚拟机都是从一个
# 完整的镜像/快照创建的,所以resize的时候根据虚拟机的image id下载到
# 的镜像就是'disk'的base。
base_dir = os.path.join(FLAGS.instances_path, '_base')
if not os.path.exists(base_dir):
utils.ensure_tree(base_dir)
old_backing_file = os.path.join(base_dir, backing_file)
old_size = 0
if "_" in os.path.basename(old_backing_file):
base_img = old_backing_file.rsplit("_", 1)[0]
old_size = int(old_backing_file.rsplit("_", 1)[1]) * \
(1024L * 1024L * 1024L)
else:
base_img = old_backing_file
# 先检查不带大小信息的base是否存在,如果存在就不需要从glance下载了
# 如果不存在,则需要从glance下载base
if not os.path.exists(base_img):
self._get_base_image_by_backing_file(context, instance,
image_id, base_img)
lock_path = os.path.join(FLAGS.instances_path, 'locks')
@utils.synchronized(base_img, external=True, lock_path=lock_path)
def copy_and_extend(base_img, target_img, size):
if not os.path.exists(target_img):
libvirt_utils.copy_image(base_img, target_img)
disk.extend(target_img, size)
# NOTE(wangpan): qemu-img rebase 'Safe mode'need the old backing file,
# refer to qemu-img manual formore details.
# 从没有大小信息的base拷贝扩容出'disk'的老的backing file,因为qemu-img
# rebase默认是采用“安全模式”的,这种模式需要COW部分的新老backing file
# 都存在才能正常执行。
if old_size:
copy_and_extend(base_img, old_backing_file, old_size)
# 从没有大小信息的base拷贝扩容出'disk'的新的backing file,也即resize之后的大小
new_backing_file = base_img
if size:
size_gb = size / (1024 * 1024 * 1024)
new_backing_file += "_%d" % size_gb
copy_and_extend(base_img, new_backing_file, size)
# when old_backing_file != new_backing_file,rebase is needed
# 如果新老backing file不一样,则需要对'disk'进行rebase操作
if old_backing_file != new_backing_file:
libvirt_utils.rebase_cow_image(new_backing_file, target)
def _get_base_image_by_backing_file(self, context, instance,
image_id, backing_file):
base_image_id_sha1 = os.path.basename(backing_file)
LOG.debug(_("image id sha1 of backing file%(backing_file)s "
"is: %(base_image_id_sha1)s") % locals(),
instance=instance, context=context)
(image_service, image_id) = glance.get_remote_image_service(
context, image_id)
# 根据base名称,从glance查询镜像/快照信息
image_info = image_service.get_image_properties(context,
"image_id_sha1",
base_image_id_sha1)
if not image_info:
LOG.error(_("can't find base image bybase_image_id_sha1 "
" %(base_image_id_sha1)s, snapshotimage_id: %(image_id)s") %
locals(), instance=instance, context=context)
raise exception.ImageNotFound(image_id=base_image_id_sha1)
base_image_id = str(image_info[0].get("image_id"))
lock_path = os.path.join(FLAGS.instances_path, 'locks')
# 下载找到的镜像/快照
@utils.synchronized(base_image_id_sha1,
external=True, lock_path=lock_path)
def fetch_base_image(context, target, image_id, user_id, project_id):
if not os.path.exists(target):
# 使用原有的下载镜像的方法,会转换镜像格式
libvirt_utils.fetch_image(context=context,
target=target,
image_id=image_id,
user_id=user_id,
project_id=project_id)
fetch_base_image(context, backing_file, base_image_id,
instance["user_id"], instance["project_id"])
if not os.path.exists(backing_file):
LOG.error(_("fetch base image failed, image id:%s"),
base_image_id, instance=instance, context=context)
raise exception.CouldNotFetchImage(base_image_id)
2. 公共流程
创建和resize的公共流程:
# nova/virt/libvirt/driver.py:LibvirtDriver._create_image()
# syntactic nicety(为了语法好看,定义了三个内部方法)
def basepath(fname='', suffix=suffix):
return os.path.join(FLAGS.instances_path,
instance['name'],
fname + suffix)
def image(fname, image_type=FLAGS.libvirt_images_type):
return self.image_backend.image(instance['name'],
fname + suffix, image_type)
def raw(fname):
return image(fname, image_type='raw')
# ensure directories exist and are writable
# 创建instance目录,用来存放镜像和libvirt.xml配置文件
utils.ensure_tree(basepath(suffix=''))
# 写入libvirt.xml配置文件
libvirt_utils.write_to_file(basepath('libvirt.xml'), libvirt_xml)
# 写入console.log控制台输出文件
libvirt_utils.write_to_file(basepath('console.log', ''), '', 007)
# get image type(为了优化镜像流程而新增的代码)
image_type = None
has_base_id_sha1 = False
(image_service, image_id) = glance.get_remote_image_service(
context, disk_images['image_id'])
try:
image_info = image_service.show(context, image_id)
if image_info and 'properties' in image_info:
if image_info['properties'].get('image_type') == "snapshot":
image_type = "snapshot"
else: # 如果不是快照,则认为是普通镜像
image_type = "image"
# base_image_id_sha1是为了兼容M3时的快照(只上传COW部分)
if image_info['properties'].get('base_image_id_sha1'):
has_base_id_sha1 = True
except Exception:
image_type = None
has_base_id_sha1 = None
LOG.warn(_("get image type of %s faild") % image_id,
context=context, instance=instance)
pass
# 检查镜像是否有backing file,也即是否只是COW部分
backing_file = None
if os.path.exists(basepath('disk')):
backing_file = libvirt_utils.get_disk_backing_file(
basepath('disk'))
# 下面的这些判断都是为了检查是否需要走我们自己修改的镜像流程
# snapshot_optimization为True则需要走修改后的流程
snapshot_optimization = False
# check use image snapshot optimization ornot
use_qcow2 = ((FLAGS.libvirt_images_type == 'default' and
FLAGS.use_cow_images) or
FLAGS.libvirt_images_type == 'qcow2')
# only qcow2 image may be need to optimize,and images with
# 'kernel_id' or 'ramdisk_id' shouldn't beoptimized
if FLAGS.allow_image_snapshot_optimization and use_qcow2 and \
not disk_images['kernel_id'] and not disk_images['ramdisk_id']:
# 下面的这些if语句是为了判断当前属于哪种镜像的哪个操作
# 然后就可以判断是否需要走修改后的流程,这种判断方式比较人肉,
# 以后改起来也比较麻烦,但目前没有更好的办法了。
# normal image, when create instance(普通镜像的创建虚拟机过程)
if image_type == "image" and backing_file is None and \
not has_base_id_sha1:
snapshot_optimization = False
# normal image, when resize(普通镜像的resize过程)
if image_type == "image" and backing_file is not None and \
not has_base_id_sha1:
snapshot_optimization = True
# unbroken snapshot, when create instance(完整快照的创建虚拟机过程)
if image_type == "snapshot" and backing_file is None and \
not has_base_id_sha1:
snapshot_optimization = False
# unbroken snapshot, when resize(完整快照的resize过程)
if image_type == "snapshot" and backing_file is not None and \
not has_base_id_sha1:
snapshot_optimization = True
# only cow part snapshot, when createinstance
# (只有COW部分的快照(M3修改)的创建过程)
if image_type == "snapshot" and backing_file is None and \
has_base_id_sha1:
snapshot_optimization = True
# only cow part snapshot, when resize
# (只有COW部分的快照(M3修改)的resize过程)
if image_type == "snapshot" and backing_file is not None and \
has_base_id_sha1:
snapshot_optimization = True
# 生成base的文件名
root_fname = hashlib.sha1(str(disk_images['image_id'])).hexdigest()
3. 创建过程
3.1 概述
对于qcow2格式镜像root盘,原有流程是先下载镜像(或者说先创建base),然后qemu-img create生成子镜像(disk),对于qcow2格式的第二块临时盘和第三块swap盘,首先是通过mkfs/mkswap创建base,之后qemu-img create生成子镜像(disk.local/disk.swap)。
传入参数:
# nova/virt/libvirt/driver.py:LibvirtDriver.spawn()
self._create_image(context, instance, xml, network_info=network_info,
block_device_info=block_device_info,
files=injected_files,
admin_pass=admin_password)
3.2 Root盘
目前如果不是M3版本的快照文件,完整快照或者镜像的创建过程与社区F版本流程一致。首先根据image id下载镜像,之后转换、copy、扩容后生成并Cache base镜像,最后qemu-img create创建COW部分的disk。
#nova/virt/libvirt/driver.py:LibvirtDriver._create_image()
elif not self._volume_in_mapping(self.default_root_device,
block_device_info):
# image是上面说的三个内部方法之一,初始化为一个对象,具体的对象是
# 根据镜像的格式来确定的,FLAGS.libvirt_images_type默认是default,
# 然后会再判断FLAGS.use_cow_images是否为True,默认值为True
# 如果是True则image是Qcow2类的对象,目前这两个值都是保持默认。
# 否则则是Raw,LVM则需要配置libvirt_images_type='lvm'。
# 'disk'参数是root盘的文件名,也就是加上instance目录后的image.path
# cache就是image类里的一个方法,用来缓存base
# fetch_func就是如果base不存在,用来从glance下载镜像的方法
# filename是base文件的名称
image('disk').cache(fetch_func=libvirt_utils.fetch_image,
context=context,
filename=root_fname,
size=size,
image_id=disk_images['image_id'],
user_id=instance['user_id'],
project_id=instance['project_id'])
# nova/virt/libvirt/imagebackend.py:Image.cache()
def cache(self, fetch_func, filename, size=None, *args, **kwargs):
"""Creates image fromtemplate.
Ensures that template and image notalready exists.
Ensures that base directory exists.
Synchronizes on template fetching.
:fetch_func: Function that creates thebase image
Should accept `target`argument.
:filename: Name of the file in theimage directory
:size: Size of created image in bytes(optional)
"""
# 根据base的文件名加锁,防止两个创建过程同时下载导致的镜像损坏
@utils.synchronized(filename, external=True, lock_path=self.lock_path)
def call_if_not_exists(target, *args, **kwargs):
# 这里的判断必不可少,因为可能拿到锁的时候另外一个创建流程已经下载过了这个镜像
if not os.path.exists(target):
fetch_func(target=target, *args, **kwargs)
# 如果instance目录下'disk'文件已经存在,则什么都不做,否则生成'disk'
if not os.path.exists(self.path): # self.path的初始化见下面的代码
base_dir = os.path.join(FLAGS.instances_path, '_base')
if not os.path.exists(base_dir):
utils.ensure_tree(base_dir)
base = os.path.join(base_dir, filename)
# 把下载镜像的方法作为参数传给创建disk的方法
self.create_image(call_if_not_exists, base, size,
*args, **kwargs)
# nova/virt/libvirt/imagebackend.py:
class Qcow2(Image):
def __init__(self, instance, name):
super(Qcow2, self).__init__("file", "qcow2", is_block_dev=False)
# instance=instance['name'],name='disk'
# self.path就是instance目录下的disk文件
self.path = os.path.join(FLAGS.instances_path,
instance, name)
def create_image(self, prepare_template, base, size, *args, **kwargs):
# 加锁,防止镜像在使用过程中中被删除或修改
@utils.synchronized(base, external=True, lock_path=self.lock_path)
def copy_qcow2_image(base, target, size):
qcow2_base = base
if size:
size_gb = size / (1024 * 1024 * 1024)
qcow2_base += '_%d' % size_gb
if not os.path.exists(qcow2_base):
with utils.remove_path_on_error(qcow2_base):
# 根据flavor拷贝后扩容base
libvirt_utils.copy_image(base, qcow2_base)
disk.extend(qcow2_base, size)
# 使用qemu-img create命令行创建COW部分也即disk文件
libvirt_utils.create_cow_image(qcow2_base, target)
# 使用传入的下载镜像的方法下载镜像,也即准备base
prepare_template(target=base, *args, **kwargs)
with utils.remove_path_on_error(self.path):
copy_qcow2_image(base, self.path, size)
下载时是先把镜像保存在_base目录下,命名为root_fname.part,然后转换为raw格式,转换过程中的目标文件命名为root_fname.converted,转换完成后删除root_fname.part,并把root_fname.converted改为root_fname,扩容后的后面加上size信息例如root_fname_10。
生成的libvirt.xml配置文件中root盘的配置为:
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='none'/>
<source file='/home/openstack/nova/instances/instance-000005ac/disk'/>
<target dev='vda' bus='virtio'/>
</disk>
3.3 Ephemeral盘
首先qemu-img create创建base(mkfs.ext3格式化),之后qemu-imgcreate创建COW部分的disk.local,配置文件与root盘相同,只是file文件的名称(disk改为disk.local)、target dev(vda改为vdb)不同而已。
#nova/virt/libvirt/driver.py:LibvirtDriver._create_image()
ephemeral_gb = instance['ephemeral_gb']
if ephemeral_gb and not self._volume_in_mapping(
self.default_second_device, block_device_info):
# 如果有第二块盘'disk.local',则swap盘作为第三块盘vdc
swap_device = self.default_third_device
# 封装创建第二块盘的方法_create_ephemeral
fn = functools.partial(self._create_ephemeral,
fs_label='ephemeral0',
os_type=instance["os_type"])
fname = "ephemeral_%s_%s_%s" % ("0",
ephemeral_gb,
instance["os_type"])
size = ephemeral_gb * 1024 * 1024 * 1024
# 与root盘的创建流程类似,差别只是将从glance下载镜像改为qemu-img创建base
image('disk.local').cache(fetch_func=fn,
filename=fname,
size=size,
ephemeral_size=ephemeral_gb)
else:
swap_device = self.default_second_device
# nova/virt/libvirt/driver.py:LibvirtDriver
def _create_ephemeral(self, target, ephemeral_size, fs_label, os_type):
# 创建未格式化的空磁盘文件
self._create_local(target, ephemeral_size)
# 格式化为ext3格式
disk.mkfs(os_type, fs_label, target)
@staticmethod
def _create_local(target, local_size, unit='G',
fs_format=None, label=None):
"""Create a blank imageof specified size"""
if not fs_format:
fs_format = FLAGS.default_ephemeral_format # 默认为None
# qemu-img create命令创建raw格式的base
libvirt_utils.create_image('raw', target,
'%d%c' % (local_size, unit))
if fs_format: # =None,这里不执行
libvirt_utils.mkfs(fs_format, target, label)
3.4 Swap盘
流程与ephemeral盘相同,只是base格式不同,首先qemu-img创建base,并用mkswap格式化,之后qemu-imgcreate创建COW部分的disk.local。配置文件与root盘相同,只是file文件的名称(disk改为disk. swap)、target dev(vda改为vdb/vdc,根据有无Ephemeral盘而定)不同而已。
4. Resize/冷迁移过程
4.1 Root盘
resize源端:
# nova/virt/libvirt/driver.py:LibvirtDriver
@exception.wrap_exception()
def migrate_disk_and_power_off(self, context, instance, dest,
instance_type, network_info,
block_device_info=None):
LOG.debug(_("Startingmigrate_disk_and_power_off"),
instance=instance)
# 获取虚拟机上所有的type='file'类型的disk的信息
disk_info_text = self.get_instance_disk_info(instance['name'])
disk_info = jsonutils.loads(disk_info_text)
# 关机
self.power_off(instance)
# 块设备处理,我们目前没有使用cinder,所以这里不处理
block_device_mapping = driver.block_device_info_get_mapping(
block_device_info)
for vol in block_device_mapping:
connection_info = vol['connection_info']
mount_device = vol['mount_device'].rpartition("/")[2]
self.volume_driver_method('disconnect_volume',
connection_info,
mount_device)
# copy disks to destination
# rename instance dir to +_resize at firstfor using
# shared storage for instance dir (eg. NFS).
# 拷贝disk到目标host
same_host = (dest == self.get_host_ip_addr())
inst_base = "%s/%s" % (FLAGS.instances_path, instance['name'])
inst_base_resize = inst_base + "_resize"
clean_remote_dir = False
try:
# 先把instance目录改为instance-xxxxx_resize,备份过程
utils.execute('mv', inst_base, inst_base_resize)
if same_host:
dest = None
utils.execute('mkdir', '-p', inst_base)
else:
# 不同宿主机之间的resize
if not FLAGS.use_rsync:
# 优化前的流程是用ssh创建目标端的instance目录
utils.execute('ssh', dest, 'mkdir', '-p', inst_base)
else:
# 新流程是用rsync创建目录
libvirt_utils.make_remote_instance_dir(inst_base_resize,
dest, instance['name'])
clean_remote_dir = True
# 遍历所有disk
for info in disk_info:
# assume inst_base == dirname(info['path'])
img_path = info['path']
fname = os.path.basename(img_path)
# FIXME(wangpan): when resize, we ignore theephemeral disk
# 我们在这里忽略了第二块盘'disk.local',不拷贝到目标端
# 这里我们还应该忽略第三块盘'disk.swap',不过暂时没用到
if fname == "disk.local":
LOG.debug(_("ignoredisk.local when resize"),
instance=instance)
continue
from_path = os.path.join(inst_base_resize, fname)
remote_path = "%s/%s" % (instance['name'], fname)
if info['type'] == 'qcow2' and info['backing_file']:
tmp_path = from_path + "_rbase"
# Note(hzzhoushaoyu): if allow optimization,just copy
# qcow2 to destination without merge.
# 优化后的流程是只拷贝COW部分,不合并COW和base
if FLAGS.allow_image_snapshot_optimization:
tmp_path = from_path
else:
# merge backing file
# 老的流程是先合并COW和base之后再拷贝
utils.execute('qemu-img', 'convert', '-f', 'qcow2',
'-O', 'qcow2', from_path, tmp_path)
if same_host and \
not FLAGS.allow_image_snapshot_optimization:
utils.execute('mv', tmp_path, img_path)
elif same_host and FLAGS.allow_image_snapshot_optimization:
utils.execute('cp', tmp_path, img_path)
else:
if not FLAGS.use_rsync:
# 老的流程使用rsync的ssh模式拷贝磁盘文件
libvirt_utils.copy_image(tmp_path, img_path,
host=dest)
else:
# 优化后使用rsync的daemon push模式拷贝
libvirt_utils.copy_image_to_remote(tmp_path,
remote_path, dest)
if not FLAGS.allow_image_snapshot_optimization:
utils.execute('rm', '-f', tmp_path)
else: # raw or qcow2 with no backing file
if not FLAGS.use_rsync or same_host:
libvirt_utils.copy_image(from_path, img_path,
host=dest)
else:
libvirt_utils.copy_image_to_remote(tmp_path,
remote_path, dest)
except Exception, e:
try:
# 异常处理,清理残留文件
if os.path.exists(inst_base_resize):
utils.execute('rm', '-rf', inst_base)
if clean_remote_dir and FLAGS.use_rsync:
libvirt_utils.clean_remote_dir(instance['name'], dest)
utils.execute('mv', inst_base_resize, inst_base)
if not FLAGS.use_rsync:
utils.execute('ssh', dest, 'rm', '-rf', inst_base)
except Exception:
pass
raise e
# 返回磁盘信息共目的端使用
return disk_info_text
resize目的端:
# nova/virt/libvirt/driver.py:LibvirtDriver
@exception.wrap_exception()
def finish_migration(self, context, migration, instance, disk_info,
network_info, image_meta, resize_instance,
block_device_info=None):
LOG.debug(_("Starting finish_migration"), instance=instance)
# 生成libvirt.xml文件
xml = self.to_xml(instance, network_info,
block_device_info=block_device_info)
# assume _create_image do nothing if a targetfile exists.
# TODO(oda): injecting files is not necessary
# 这里生成镜像,但是实际上社区原有流程不会生成镜像,因为'disk'已经拷贝过来了
# 所以imagebackend.py里面的cache方法什么事情都不做
# 这里主要是创建instance目录,写入libvirt.xml和console.log文件
# 但是我们修改后的流程,会根据'disk'的backing file下载它的base,
# 还会在这里重新生成第二块盘的base和'disk.local',
# 第三块盘'disk.swap'因为拷贝的时候没有忽略,所以这里不会重新生成,
# 所以这里可能会导致disk.swap找不到base,虚拟机启动失败。
self._create_image(context, instance, xml,
network_info=network_info,
block_device_info=None)
# resize disks. only "disk" and"disk.local" are necessary.
# resize磁盘,忽略了第三块盘
disk_info = jsonutils.loads(disk_info)
for info in disk_info:
fname = os.path.basename(info['path'])
if fname == 'disk':
size = instance['root_gb']
elif fname == 'disk.local':
size = instance['ephemeral_gb']
else:
size = 0
size *= 1024 * 1024 * 1024
# If we have a non partitioned image that wecan extend
# then ensure we're in 'raw' format so we canextend file system.
fmt = info['type']
# 如果是qcow2格式的镜像,并且可以resize,则先把它转换为raw格式
if (size and fmt == 'qcow2' and
disk.can_resize_fs(info['path'], size, use_cow=True)):
path_raw = info['path'] + '_raw'
utils.execute('qemu-img', 'convert', '-f', 'qcow2',
'-O', 'raw', info['path'], path_raw)
utils.execute('mv', path_raw, info['path'])
fmt = 'raw'
# resize磁盘
if size:
disk.extend(info['path'], size)
if fmt == 'raw' and FLAGS.use_cow_images:
# back to qcow2 (no backing_file though) sothat snapshot
# will be available
# 如果是raw格式或者刚刚被转换成raw格式,则再次转换成qcow2
path_qcow = info['path'] + '_qcow'
utils.execute('qemu-img', 'convert', '-f', 'raw',
'-O', 'qcow2', info['path'], path_qcow)
utils.execute('mv', path_qcow, info['path'])
### 上面的两次转换过程是很耗时的,所以不建议这么做
### 还好我们目前的root_gb大小都是一样的,不会做resize动作
# 创建虚拟机
self._create_domain_and_network(xml, instance, network_info,
block_device_info)
# 等待虚拟机启动
timer = utils.LoopingCall(self._wait_for_running, instance)
timer.start(interval=0.5).wait()
4.2 Ephemeral盘
与root盘相同
4.3 Swap盘
与root盘相同
5. 热迁移过程(带blockmigration情况)
5.1 Root盘
热迁移目的端:
# nova/virt/libvirt/driver.py:LibvirtDriver
def pre_block_migration(self, ctxt, instance, disk_info_json):
"""Preparation blockmigration.
:params ctxt: security context
:params instance:
nova.db.sqlalchemy.models.Instanceobject
instance object that is migrated.
:params disk_info_json:
json strings specified inget_instance_disk_info
"""
# 与resize相同,disk_info_json也是找到的所有type='file'的disk
disk_info = jsonutils.loads(disk_info_json)
# make instance directory
instance_dir = os.path.join(FLAGS.instances_path, instance['name'])
if os.path.exists(instance_dir):
raise exception.DestinationDiskExists(path=instance_dir)
os.mkdir(instance_dir)
# 遍历所有file disk
for info in disk_info:
base = os.path.basename(info['path'])
# Get image type and create empty disk image,and
# create backing file in case of qcow2.
instance_disk = os.path.join(instance_dir, base)
# 如果disk没有backing file(raw格式、或者不带backing file的qcow2)
# 则直接用'qemu-img create'创建空盘,磁盘内容会随着热迁移流程拷贝过来
# 这就是block migration的意义。
if not info['backing_file']:
libvirt_utils.create_image(info['type'], instance_disk,
info['disk_size'])
else:
# 有backing file的disk
# 与创建虚拟机相同的镜像生成流程,也就是先准备base,
# 再qemu-img create 'disk',这里生成的'disk'也是类似空盘
# 需要block migration拷贝过来
# 需要注意的是如果是M3的不完整快照,这里的流程会出错,
# 因为这里是根据image id下载base的,而不完整的快照的id就是它本身
# 我们需要的是根据快照的id找到并下载它的base
# M4的完整快照应该与普通镜像相同,所以没有这个问题
# Creating backing file follows same way asspawning instances.
cache_name = os.path.basename(info['backing_file'])
# Remove any size tags which the cachemanages
cache_name = cache_name.split('_')[0]
# 下面的流程与创建流程相同
image = self.image_backend.image(instance['name'],
instance_disk,
FLAGS.libvirt_images_type)
image.cache(fetch_func=libvirt_utils.fetch_image,
context=ctxt,
filename=cache_name,
image_id=instance['image_ref'],
user_id=instance['user_id'],
project_id=instance['project_id'],
size=info['virt_disk_size'])
5.2 Ephemeral盘
与root盘相同
5.3 Swap盘
与root盘相同