@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED, vm_states.SUSPENDED])
def snapshot_volume_backed(self, context, instance, name, extra_properties=None):
"""Snapshot the given volume-backed instance.从实例的system_metadata生成镜像属性(排除不可继承属性),如下:
# {
#
u'min_disk': u'20',
#
'is_public': False,
#
'min_ram': u'0',
#
'properties': {
#
'base_image_ref': u''
# },
# 'name':
u'snapshot1'
# }
:param
instance: nova.objects.instance.Instance object
:param name:
name of the backup or snapshot
:param
extra_properties: dict of extra image properties to include
:returns: the
new image metadata
"""
# 获取实例的metadata属性
image_meta = self._initialize_instance_snapshot_metadata(instance, name, extra_properties)
image_meta['size'] = 0
# 清除镜像metadata属性中的container_format,disk_forma属性
for attr in ('container_format', 'disk_format'):
image_meta.pop(attr, None)
properties =
image_meta['properties']
# clean properties before filling,清除properties属性里面的'block_device_mapping', 'bdm_v2', 'root_device_name'相关属性值
for key in ('block_device_mapping', 'bdm_v2', 'root_device_name'):
properties.pop(key, None)
# 将实例中的‘root_device_name’属性更新到properties属性里,image_meta的最终内容如:
# {
# 'name': u'snapshot1',
# u'min_ram': u'0',
# u'min_disk': u'20',
# 'is_public': False,
# 'properties': {
#
u'base_image_ref': u'',
#
'root_device_name': u'/dev/vda'
# },
# 'size': 0
# }
if instance.root_device_name:
properties['root_device_name'] = instance.root_device_name
# 从数据库中获取该云主机所关联的所有块设备,结果会返回一个BlockDeviceMappingList对象
bdms =
objects.BlockDeviceMappingList.get_by_instance_uuid(context, instance.uuid)
# 接下来开始做快照的操作,注意,云主机挂在了多少个卷设备,就要做多少次快照
mapping = [] # list of BDM dicts that can go into the image properties
# Do some up-front
filtering of the list of BDMs from
# which we are going
to create snapshots.
volume_bdms = []
for bdm in bdms:
if bdm.no_device:
# 映射关系中没有块设备,则忽略此条映射
continue
if bdm.is_volume:
# These will be handled
below.此映射包含块设备,加入到volume_bdms,准备做快照
volume_bdms.append(bdm)
else:
mapping.append(bdm.get_image_mapping())
# Check limits in Cinder before creating
snapshots to avoid going over
# quota in the middle
of a list of volumes. This is a best-effort check
# but concurrently
running snapshot requests from the same project
# could still fail to
create volume snapshots if they go over limit.
# 在创建快照之前,需要首先在Cinder中检查配额限制,以避免超过配额限制
if volume_bdms:
limits = self.volume_api.get_absolute_limits(context)
total_snapshots_used = limits['totalSnapshotsUsed']
max_snapshots = limits['maxTotalSnapshots']
# -1 means there is unlimited quota for snapshots
if (max_snapshots > -1 and
len(volume_bdms) + total_snapshots_used >
max_snapshots):
LOG.debug('Unable to
create volume snapshots for instance. Currently
has %s snapshots, requesting %s new snapshots, with a limit of %s.',
total_snapshots_used, len(volume_bdms),
max_snapshots, instance=instance)
raise exception.OverQuota(overs='snapshots')
quiesced = False
# 判断虚拟机的状态,如果虚拟机处于active,则通过rpc通知虚拟机进入静默状态(异常处理省略)
if instance.vm_state == vm_states.ACTIVE:
LOG.info("Attempting
to quiesce instance before volume snapshot.", instance=instance)
self.compute_rpcapi.quiesce_instance(context, instance)
quiesced = True
# 定义一个获取云主机上的及具体卷信息的方法,返回云主机的卷映射
@wrap_instance_event(prefix='api')
def snapshot_instance(self, context, instance, bdms):
for
bdm
in volume_bdms:
# create snapshot
based on volume_id
#根据卷的volume_id从数据库获取卷的详细信息
volume =
self.volume_api.get(context, bdm.volume_id)
# 组装出一个貌似是desc的消息,比如快照名称是snapshot1,则这里就是snapshot for snapshot1
name = _('snapshot for %s') % image_meta['name']
LOG.debug('Creating snapshot
from volume %s.', volume['id'], instance=instance)
# 调用cinder的api create_snapshot_force创建新的卷
# 在“create_snapshot_force”中实际上是通过cinderclient来调用volume_snapshots.create
# 来发起创建卷的请求,具体是由cinder-volume来完成卷的快照,返回的内容为卷快照的信息,
# 格式如:
#{