我们接着上周的流程继续讲解,接着看_prep_resize()方法,其代码和注释如下:
def _prep_resize(self, context, image, instance, instance_type,
quotas, request_spec, filter_properties, node,
clean_shutdown=True):
if not filter_properties:
filter_properties = {}
if not instance.host:
self._set_instance_obj_error_state(context, instance)
msg = _('Instance has no source host')
raise exception.MigrationError(reason=msg)
same_host = instance.host == self.host
# if the flavor IDs match, it's migrate; otherwise resize
if same_host and instance_type.id == instance['instance_type_id']:
# check driver whether support migrate to same host
if not self.driver.capabilities['supports_migrate_to_same_host']:
raise exception.UnableToMigrateToSelf(
instance_id=instance.uuid, host=self.host)
# NOTE(danms): Stash the new instance_type to avoid having to
# look it up in the database later
instance.set_flavor(instance_type, 'new')
# NOTE(mriedem): Stash the old vm_state so we can set the
# resized/reverted instance back to the same state later.
vm_state = instance.vm_state
LOG.debug('Stashing vm_state: %s', vm_state, instance=instance)
instance.system_metadata['old_vm_state'] = vm_state
instance.save()
limits = filter_properties.get('limits', {})
rt = self._get_resource_tracker(node)
with rt.resize_claim(context, instance, instance_type,
image_meta=image, limits=limits) as claim:
LOG.info(_LI('Migrating'), context=context, instance=instance)
self.compute_rpcapi.resize_instance(
context, instance, claim.migration, image,
instance_type, quotas.reservations,
clean_shutdown)
接着调用/nova/compute/rpcapi.py中的resize_instance()方法,其代码如下:
def resize_instance(self, ctxt, instance, migration, image, instance_type,
reservations=None, clean_shutdown=True):
msg_args = {'instance': instance, 'migration': migration,
'image': image, 'reservations': reservations,
'instance_type': instance_type,
'clean_shutdown': clean_shutdown,
}
version = '4.1'
if not self.client.can_send_version(version):
msg_args['instance_type'] = objects_base.obj_to_primitive(
instance_type)
version = '4.0'
cctxt = self.client.prepare(server=_compute_host(None, instance),
version=version)
cctxt.cast(ctxt, 'resize_instance', **msg_args)
接着调用/nova/compute/manage.py中的resize_instance()方法,该节点为源计算节点,其代码如下:
@wrap_exception()
@reverts_task_state
@wrap_instance_event
@errors_out_migration
@wrap_instance_fault
def resize_instance(self, context, instance, image,
reservations, migration, instance_type,
clean_shutdown):
"""Starts the migration of a running instance to another host."""
quotas = objects.Quotas.from_reservations(context,
reservations,
instance=instance)
with self._error_out_instance_on_exception(context, instance,
quotas=quotas):
# TODO(chaochin) Remove this until v5 RPC API
# Code downstream may expect extra_specs to be populated since it
# is receiving an object, so lookup the flavor to ensure this.
if (not instance_type or
not isinstance(instance_type, objects.Flavor)):
instance_type = objects.Flavor.get_by_id(
context, migration['new_instance_type_id'])
#获取虚拟机网络信息
network_info = self.network_api.get_instance_nw_info(context,
instance)
#设置迁移相关状态,并保存在数据库表migrate中
#(包括源主机,目的主机,迁移状态,此处为migrating状态)
migration.status = 'migrating'
with migration.obj_as_admin():
migration.save()
#设置虚拟机状态为RESIZE_PREP
instance.task_state = task_states.RESIZE_MIGRATING
instance.save(expected_task_state=task_states.RESIZE_PREP)
self._notify_about_instance_usage(
context, instance, "resize.start", network_info=network_info)
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
context, instance.uuid)
#获取虚拟机磁盘块设备信息(包括卷,快照、镜像磁盘文件)
block_device_info = self._get_instance_block_device_info(
context, instance, bdms=bdms)
timeout, retry_interval = self._get_power_off_values(context,
instance, clean_shutdown)
#迁移磁盘且关机
disk_info = self.driver.migrate_disk_and_power_off(
context, instance, migration.dest_host,
instance_type, network_info,
block_device_info,
timeout, retry_interval)
self._terminate_volume_connections(context, instance, bdms)
migration_p = obj_base.obj_to_primitive(migration)
#开始网络迁移
self.network_api.migrate_instance_start(context,
instance,
migration_p)
#migration数据库表状态设置为post-migrating、
migration.status = 'post-migrating'
with migration.obj_as_admin():
migration.save()
instance.host = migration.dest_compute
instance.node = migration.dest_node
#instance数据库表instance.task_state为RESIZE_MIGRATED
instance.task_state = task_states.RESIZE_MIGRATED
instance.save(expected_task_state=task_states.RESIZE_MIGRATING)
self.compute_rpcapi.finish_resize(context, instance,
migration, image, disk_info,
migration.dest_compute, reservations=quotas.reservations)
self._notify_about_instance_usage(context, instance, "resize.end",
network_info=network_info)
self.instance_events.clear_events_for_instance(instance)
这里直接看/nova/compute/manage.py中的finish_resize()方法,该节点为目的计算节点,其代码如下:
@wrap_exception()
@reverts_task_state
@wrap_instance_event
@errors_out_migration
@wrap_instance_fault
def finish_resize(self, context, disk_info, image, instance,
reservations, migration):
"""Completes the migration process.
Sets up the newly transferred disk and turns on the instance at its
new host machine.
"""
quotas = objects.Quotas.from_reservations(context,
reservations,
instance=instance)
try:
self._finish_resize(context, instance, migration,
disk_info, image)
quotas.commit()
except Exception:
LOG.exception(_LE('Setting instance vm_state to ERROR'),
instance=instance)
with excutils.save_and_reraise_exception():
try:
quotas.rollback()
except Exception:
LOG.exception(_LE("Failed to rollback quota for failed "
"finish_resize"),
instance=instance)
self._set_instance_obj_error_state(context, instance)
接着调用_finish_resize()方法,其代码如下:
def _finish_resize(self, context, instance, migration, disk_info,
image):
resize_instance = False
old_instance_type_id = migration['old_instance_type_id']
new_instance_type_id = migration['new_instance_type_id']
old_instance_type = instance.get_flavor()
# NOTE(mriedem): Get the old_vm_state so we know if we should
# power on the instance. If old_vm_state is not set we need to default
# to ACTIVE for backwards compatibility
#获得旧虚拟机的状态
old_vm_state = instance.system_metadata.get('old_vm_state',
vm_states.ACTIVE)
instance.set_flavor(old_instance_type, 'old')
#修改虚拟机类型(内存和磁盘等信息)
if old_instance_type_id != new_instance_type_id:
instance_type = instance.get_flavor('new')
self._set_instance_info(instance, instance_type)
for key in ('root_gb', 'swap', 'ephemeral_gb'):
if old_instance_type[key] != instance_type[key]:
resize_instance = True
break
instance.apply_migration_context()
# NOTE(tr3buchet): setup networks on destination host
#目标主机创建网络
self.network_api.setup_networks_on_host(context, instance,
migration['dest_compute'])
migration_p = obj_base.obj_to_primitive(migration)
self.network_api.migrate_instance_finish(context,
instance,
migration_p)
#迁移网络
network_info = self.network_api.get_instance_nw_info(context, instance)
#数据库实例状态更新为RESIZE_FINISH
instance.task_state = task_states.RESIZE_FINISH
instance.save(expected_task_state=task_states.RESIZE_MIGRATED)
self._notify_about_instance_usage(
context, instance, "finish_resize.start",
network_info=network_info)
#获取磁盘文件信息(包括卷、快照,镜像)
block_device_info = self._get_instance_block_device_info(
context, instance, refresh_conn_info=True)
# NOTE(mriedem): If the original vm_state was STOPPED, we don't
# automatically power on the instance after it's migrated
power_on = old_vm_state != vm_states.STOPPED
try:
self.driver.finish_migration(context, migration, instance,
disk_info,
network_info,
image, resize_instance,
block_device_info, power_on)
except Exception:
with excutils.save_and_reraise_exception():
if old_instance_type_id != new_instance_type_id:
self._set_instance_info(instance,
old_instance_type)
#更新迁移完毕后虚拟机状态信息
migration.status = 'finished'
with migration.obj_as_admin():
migration.save()
instance.vm_state = vm_states.RESIZED
instance.task_state = None
instance.launched_at = timeutils.utcnow()
instance.save(expected_task_state=task_states.RESIZE_FINISH)
self._update_scheduler_instance_info(context, instance)
self._notify_about_instance_usage(
context, instance, "finish_resize.end",
network_info=network_info)
其中driver.finish_migration()方法,由于compute_driver=libvirt.LibvirtDriver,这个参数也是在nova.conf配置文件得到的。所以这里会去调用nova/virt/libvirt/driver.py.finish_migration()方法,其代码和注释如下:
def finish_migration(self, context, migration, instance, disk_info,
network_info, image_meta, resize_instance,
block_device_info=None, power_on=True):
LOG.debug("Starting finish_migration", instance=instance)
image_meta = objects.ImageMeta.from_dict(image_meta)
# resize disks. only "disk" and "disk.local" are necessary.
#迁移磁盘信息(仅仅对于root_gb与ephemeral_gb)
disk_info = jsonutils.loads(disk_info)
for info in disk_info:
#获取该disk类型下的磁盘size
size = self._disk_size_from_instance(instance, info)
if resize_instance:
image = imgmodel.LocalFileImage(info['path'],
info['type'])
self._disk_resize(image, size)
#如果磁盘格式为raw,且使用了use_cow_images,则先转化为qcow2
if info['type'] == 'raw' and CONF.use_cow_images:
self._disk_raw_to_qcow2(info['path'])
#获取磁盘信息(例如virio、disk.local、disk.swap)
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
instance,
image_meta,
block_device_info)
# assume _create_image do nothing if a target file exists.
self._create_image(context, instance, disk_info['mapping'],
network_info=network_info,
block_device_info=None, inject_files=False,
fallback_from_host=migration.source_compute)
#生成xml文件
xml = self._get_guest_xml(context, instance, network_info, disk_info,
image_meta,
block_device_info=block_device_info,
write_to_disk=True)
# NOTE(mriedem): vifs_already_plugged=True here, regardless of whether
# or not we've migrated to another host, because we unplug VIFs locally
# and the status change in the port might go undetected by the neutron
# L2 agent (or neutron server) so neutron may not know that the VIF was
# unplugged in the first place and never send an event.
#根据配置和xml文件创建虚拟机
self._create_domain_and_network(context, xml, instance, network_info,
disk_info,
block_device_info=block_device_info,
power_on=power_on,
vifs_already_plugged=True)
#如果迁移前为开机状态,则开机(至此迁移全部完成)
if power_on:
timer = loopingcall.FixedIntervalLoopingCall(
self._wait_for_running,
instance)
timer.start(interval=0.5).wait()
LOG.debug("finish_migration finished successfully.", instance=instance)