标签:
这是创建云主机实例快照源码分析系列的最后一篇,在第一篇文章中分析了从镜像启动云主机,创建在线/离线快照的过程;本篇将分析从启动盘启动的云主机创建快照的过程,下面请看正文:
函数入口和前述一样,还是
nova/api/openstack/compute/servers.py/ServersController._action_create_image
,下面一起来看看:
def _action_create_image(self, req, id, body):
"""省略了与‘镜像启动云主机,做快照‘的相关代码,具体分析可以看上一篇
博文的分析,另外下文的分析中省略了异常处理部分,输入参数如下:
req = Request对象,包含本地请求的上下文信息
id = u‘85972ed5-f670-4790-b158-2c72c0b7bde5‘,实例id
body = {u‘createImage‘: {u‘name‘: u‘snapshot1‘, u‘metadata‘: {}}}
"""
#从req中获取请求上下文并执行权限认证
context = req.environ[‘nova.context‘]
authorize(context, action=‘create_image‘)
从body中解析出快照名及属性
entity = body["createImage"]
image_name = common.normalize_name(entity["name"])
metadata = entity.get(‘metadata‘, {})
#检查属性配额
common.check_img_metadata_properties_quota(context, metadata)
#从数据库中获取实例对象(InstanceV2)及块设备映射列表
#(BlockDeviceMappingList)
instance = self._get_server(context, req, id)
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
context, instance.uuid)
#通过系统盘是否是volume来判断,系统是否从磁盘启动
if self.compute_api.is_volume_backed_instance(context,
instance,
bdms):
authorize(context, action="create_image:allow_volume_backed")
#instance是InstanceV2对象,通过id获得
#image_name = snapshot1,快照名
#调用nova/compute/api.py/API.snapshot_volume_backed方法
#执行快照,下文具体分析
image = self.compute_api.snapshot_volume_backed(
context,
instance,
image_name,
extra_properties=
metadata)
......
-------------------------------------------------------------
接上文: nova/compute/api.py/API.snapshot_volume_backed
def snapshot_volume_backed(self, context, instance, name,
extra_properties=None):
"""从实例的system_metadata生成镜像属性(排除不可继承属性),如下:
{u‘min_disk‘: u‘20‘, ‘is_public‘: False, u‘min_ram‘: u‘0‘,
‘properties‘: {u‘base_image_ref‘: u‘‘}, ‘name‘:
u‘snapshot1‘}
"""
image_meta = self._initialize_instance_snapshot_metadata(
instance, name, extra_properties)
""" the new image is simply a bucket of properties
(particularly the block device mapping, kernel and ramdisk
IDs) with no image data, hence the zero size
"""
image_meta[‘size‘] = 0
#下面的代码清除了container_format,disk_format属性
for attr in (‘container_format‘, ‘disk_format‘):
image_meta.pop(attr, None)
properties = image_meta[‘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,所以最终的快照属性字典如
下:
{‘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
#省略异常处理代码
#如果云主机处于运行状态(在线快照),则快照前需要静默文件系统
#这需要agent的支持,如果没有安装或者静默失败,则抛异常
quiesced = False
if instance.vm_state == vm_states.ACTIVE:
#通过rpc.call发送quiesce_instance请求给`nova-compute`
#进而借助libvirt发送请求给虚拟机内的agent实现文件系统的静默
self.compute_rpcapi.quiesce_instance(context,
instance)
quiesced = True
#从数据库中获取该云主机关联的所有块设备,返回一个
#BlockDeviceMappingList对象
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
context, instance.uuid)
#准备执行快照(如果有多个volume设备,则需要执行多次)
mapping = []
for bdm in bdms:
if bdm.no_device:
continue
#只对volume类型的设备执行快照
if bdm.is_volume:
# create snapshot based on volume_id
#根据volume_id,从数据库获取卷信息字典
volume = self.volume_api.get(context, bdm.volume_id)
""" NOTE(yamahata): Should we wait for snapshot
creation? Linux LVM snapshot creation completes in
short time, it doesn‘t matter for now.
"""
#根据我们的输入,name = ‘snapshot for snapshot1‘
name = _(‘snapshot for %s‘) % image_meta[‘name‘]
"""通过cinderclient发起创建快照的请求,由cinder-volume完
成卷快照,系统卷快照信息字典如下:
{
‘status‘: u‘creating‘,
‘display_name‘: u‘snapshot for snapshot1‘,
‘created_at‘: u‘2016-06-24T09:23:00.517279‘,
‘display_description‘: u‘‘, ‘volume_size‘: 20,
‘volume_id‘: u‘60e16af2-0684-433c-a1b6-
c1af1c2523fc‘, ‘progress‘: None,
‘project_id‘: u‘25520b29dce346d38bc4b055c5ffbfcb‘,
‘id‘: u‘cede2421-ea68-4a8e-937d-c27074b9024b‘,
‘size‘: 20
}
具体执行过程,下文分析
"""
snapshot = self.volume_api.create_snapshot_force(
context, volume[‘id‘], name,
volume[‘display_description‘])
"""根据bdm构建快照属性字典,并生成BlockDeviceDict对象,
系统卷快照属性字典如下:
{
‘guest_format‘: None,
‘boot_index‘: 0,
‘no_device‘: None,
‘connection_info‘: None,
‘snapshot_id‘: u‘cede2421-ea68-4a8e-937d-
c27074b9024b‘,
‘volume_size‘: 20,
‘device_name‘: u‘/dev/vda‘,
‘disk_bus‘: u‘virtio‘,
‘image_id‘: None,
‘source_type‘: ‘snapshot‘,
‘device_type‘: u‘disk‘,
‘volume_id‘: None,
‘destination_type‘: ‘volume‘,
‘delete_on_termination‘: False
}
"""
mapping_dict =
block_device.snapshot_from_bdm(snapshot[‘id‘],
bdm)
#排除那些只在数据库中显示的字段(如:created_at等)的字典
mapping_dict = mapping_dict.get_image_mapping()
#对于非volume设备,直接从dbm中获取映射字典
else:
mapping_dict = bdm.get_image_mapping()
#将所有的设备映射字典添加到mapping列表,作为快照属性的
#一部分上传到glance数据库
mapping.append(mapping_dict)
#如果之前静默了文件系统,这里就要解冻;
#由rpc.cast发送异步请求给nova-compute处理,nova-compute处理该请
#求时会等到快照完成后才解冻文件系统,解冻请求也需要agent的支持
if quiesced:
self.compute_rpcapi.unquiesce_instance(context,
instance, mapping)
#更新快照属性字典
if mapping:
properties[‘block_device_mapping‘] = mapping
properties[‘bdm_v2‘] = True
"""添加glance快照(镜像)数据库条目(会在Dashboard的镜像面板显示一
条名为snapshot1的快照记录),我的例子中信息如下:
大部分信息都拷贝至系统盘属性,这是因为卷快照是可以直接用来启动云主机的
另外‘block_device_mapping‘属性中包含所有的volume设备快照信息
(如果有的话),每个volume设备快照信息作为一条记录,记录在
image_properties数据表;
{
‘name‘: u‘snapshot1‘, u‘min_ram‘: u‘0‘, u‘min_disk‘: u‘20‘,
‘is_public‘: False,
‘properties‘: {
‘bdm_v2‘: True,
‘block_device_mapping‘: [{‘guest_format‘: None,
‘boot_index‘: 0, ‘no_device‘: None, ‘image_id‘: None,
‘volume_id‘: None, ‘device_name‘: u‘/dev/vda‘,
‘disk_bus‘:u‘virtio‘, ‘volume_size‘: 20,
‘source_type‘: ‘snapshot‘, ‘device_type‘: u‘disk‘,
‘snapshot_id‘: u‘cede2421-ea68-4a8e-937d-
c27074b9024b‘, ‘destination_type‘: ‘volume‘,
‘delete_on_termination‘: False}],
u‘base_image_ref‘: u‘‘,
‘root_device_name‘: u‘/dev/vda‘
},
‘size‘: 0
}
"""
return self.image_api.create(context, image_meta)
小结:nova-api
主要完成了如下的功能:
上文中cinderclient通过http发送快照请求后,cinder-api
会接受到该请求,处理函数如下:
#cinder/api/v2/snapshots.py/SnapshotsController.create
def create(self, req, body):
"""根据上文的分析,我们得到如下的输入参数
req = Request对象,包含本次请求的上下文
body = {u‘snapshot‘: {u‘volume_id‘: u‘60e16af2-0684-433c-
a1b6-c1af1c2523fc‘, u‘force‘: True, u‘description‘: u‘‘,
u‘name‘: u‘snapshot for snapshot1‘, u‘metadata‘: {}}}, 这个
是快照属性信息
"""
kwargs = {}
#获得请求上下文
context = req.environ[‘cinder.context‘]
#输入参数是否合法
self.assert_valid_body(body, ‘snapshot‘)
#从body中提取参数
snapshot = body[‘snapshot‘]
kwargs[‘metadata‘] = snapshot.get(‘metadata‘, None)
#省略异常处理,如果不包含volume_id则抛异常
volume_id = snapshot[‘volume_id‘]
#从数据库中提取卷信息;省略异常处理,如果找不到卷则抛异常
volume = self.volume_api.get(context, volume_id)
#是否是强制快照,我们这里force = True,强制与非强制快照的区别体现在
#非可以用(available)状态卷快照的处理上,请看后文的分析
force = snapshot.get(‘force‘, False)
msg = _LI("Create snapshot from volume %s")
LOG.info(msg, volume_id, context=context)
#验证快照名及快照描述是否合法,长度不能超过256
self.validate_name_and_description(snapshot)
#用快照名做快照描述
if ‘name‘ in snapshot:
snapshot[‘display_name‘] = snapshot.pop(‘name‘)
#参数类型转换,如果是非True/False的值,则抛异常
force = strutils.bool_from_string(force, strict=True)
#force = True,走这个分支,否则走else分支;下述两个方法都是对
_create_snapshot的封装,请看下文的分析
if force:
new_snapshot = self.volume_api.create_snapshot_force(
context,
volume,
snapshot.get(‘display_name‘),
snapshot.get(‘description‘),
**kwargs)
else:
new_snapshot = self.volume_api.create_snapshot(
context,
volume,
snapshot.get(‘display_name‘),
snapshot.get(‘description‘),
**kwargs)
#将快照信息条件到请求体中,应答的时候要用
req.cache_db_snapshot(new_snapshot)
#返回快照描述信息,应答的时候需要
return self._view_builder.detail(req, new_snapshot)
-------------------------------------------------------------
#接上文
def _create_snapshot(self, context,
volume, name, description,
force=False, metadata=None,
cgsnapshot_id=None):
"""根据上文的分析:force = True"""
"""该方法完成如下功能:
1. 执行卷状态条件判断,如果卷处于维护状态,迁移过程中,副本卷,
force=False且不是可用状态,则抛异常
2. 执行用户快照配额管理,用户可以为不同的卷类型设置配额信息,如:
volumes, gigabytes,snapshots,我这里使用的是ceph rbd,例子如下:
{‘gigabytes‘: 20, u‘snapshots_ceph‘: 1, u‘gigabytes_ceph‘:
20, ‘snapshots‘: 1}, 用户默认配额如下:
{‘gigabytes‘: 1000, u‘snapshots_ceph‘: -1, ‘snapshots‘: 10,
u‘gigabytes_ceph‘: -1}
如果配额不足则抛异常
3. 创建快照条目,我的例子中是(要知道,创建快照要先创建glance数据库条
目):
{‘status‘: u‘creating‘, ‘volume_type_id‘: ‘d494e240-17b3-
4d35-a5a1-2923d8677d79‘, ‘display_name‘: u‘snapshot for
snapshot1‘, ‘user_id‘: ‘b652f9bd65844f739684a20ed77e9a0f‘,
‘display_description‘: u‘‘, ‘cgsnapshot_id‘: None,
‘volume_size‘: 20, ‘encryption_key_id‘: None, ‘volume_id‘:
‘60e16af2-0684-433c-a1b6-c1af1c2523fc‘, ‘progress‘: u‘0%‘,
‘project_id‘: ‘25520b29dce346d38bc4b055c5ffbfcb‘,
‘metadata‘: {}
}
卷快照完成后,会在Dashboard的云硬盘快照面板显示一条名为‘
snapshot for snapshot1‘的卷快照记录
"""
snapshot = self.create_snapshot_in_db(
context, volume, name,
description, force, metadata, cgsnapshot_id)
#调用rpc.cast将create_snapshot消息投递到消息队列,由cinder-
#volume完成快照
self.volume_rpcapi.create_snapshot(context, volume, snapshot)
return snapshot
小结:卷快照过程中,cinder-api的操作总结为如下两个方面:
从消息队列拿到来自cinder-api
的请求后,cinder-volume
调用
VolumeManager.create_snapshot
方法处理该请求,如下:
#cinder/volume/manager.py/VolumeManager.create_snapshot
def create_snapshot(self, context, volume_id, snapshot):
"""输入参数说明如下:
context 请求上下文
volume_id 执行快照的卷id
snapshot 包含该次快照的详细信息
"""
#获取请求上下文的一个拷贝(设置了admin)
context = context.elevated()
#发送通知,给ceilometer用的
self._notify_about_snapshot_usage(
context, snapshot, "create.start")
"""省略异常处理代码,有任何异常则退出并设置快照状态为error
"""
#确保存储驱动已经初始化,否则抛异常
utils.require_driver_initialized(self.driver)
# Pass context so that drivers that want to use it, can,
# but it is not a requirement for all drivers.
snapshot.context = context
#调用后端存储驱动执行快照,我的例子中是RBDDriver,下文具体分析
model_update = self.driver.create_snapshot(snapshot)
#更新数据库条目信息, 我这里返回的是None,所以不执行该次更新
if model_update:
snapshot.update(model_update)
snapshot.save()
#从cinder数据库获取卷信息
vol_ref = self.db.volume_get(context, volume_id)
#是否是启动卷,我们是通过卷启动的,系统盘自然就是启动卷
#如果是非系统盘启动卷,则跳过该过程
if vol_ref.bootable:
#这里省略异常处理,如果异常则退出并设置快照状态为error
#用卷的metadata信息更新快照的metadata信息,毕竟启动卷是用来
#启动系统的,需要保证快照与原卷信息一致
self.db.volume_glance_metadata_copy_to_snapshot(
context, snapshot.id, volume_id)
#快照完成了,标记快照为可用
snapshot.status = ‘available‘
snapshot.progress = ‘100%‘
snapshot.save()
#发送通知,给ceilometer用的
self._notify_about_snapshot_usage(context, snapshot, "create.end")
#日志
LOG.info(_LI("Create snapshot completed successfully"),
resource=snapshot)
#返回快照id
return snapshot.id
--------------------------------------------------------------
#接上文:
#cinder/volume/drivers/rbd.py/RBDDriver.create_snapshot
def create_snapshot(self, snapshot):
"""Creates an rbd snapshot."""
"""创建一个Image对象,然后直接调用librbd相关的方法执行秒级快照"""
with RBDVolumeProxy(self, snapshot[‘volume_name‘]) as volume:
snap = utils.convert_str(snapshot[‘name‘])
volume.create_snap(snap)
volume.protect_snap(snap)
小结:cinder-volume
快照功能很简单:调用后端存储执行快照,然后更新glance数据库快照记录
阅读完上面的分析,相信读者会发现上面的快照过程中cinder
执行的就是卷的快照,nova
实现的是云主机信息及其镜像记录的处理。事实确实也如此:快照执行完成后,会在Dashboard的镜像面板显示一条镜像记录,在卷快照面板显示一条或者多条(如果有多个卷的话)卷快照记录。
标签:
原文地址:http://blog.csdn.net/lzw06061139/article/details/51754024