Openstack liberty 创建实例快照源码分析2

来源:互联网 发布:朝鲜族吃里扒外知乎 编辑:程序博客网 时间:2024/04/20 21:19

这是创建云主机实例快照源码分析系列的最后一篇,在第一篇文章中分析了从镜像启动云主机,创建在线/离线快照的过程;本篇将分析从启动盘启动的云主机创建快照的过程,下面请看正文:

磁盘启动云主机,离线(在线)快照

nova-api处理过程

函数入口和前述一样,还是
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_backeddef 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主要完成了如下的功能:

  • 如果是在线快照,则冻结/解冻结文件系统
  • 创建glance数据库镜像记录(包含所有卷的快照信息)

cinder创建磁盘快照

cinder-api处理过程

上文中cinderclient通过http发送快照请求后,cinder-api会接受到该请求,处理函数如下:

#cinder/api/v2/snapshots.py/SnapshotsController.createdef 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. 创建快照条目,我的例子中是(创建卷快照要先创建cinder数据库条    目):    {'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的操作总结为如下两个方面:

  • 卷状态条件检查及配额检查
  • 创建glance数据库快照记录(记录的是单个卷快照的信息)

cinder-volume处理过程

从消息队列拿到来自cinder-api的请求后,cinder-volume调用
VolumeManager.create_snapshot方法处理该请求,如下:

#cinder/volume/manager.py/VolumeManager.create_snapshotdef 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_snapshotdef 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的镜像面板显示一条镜像记录,在卷快照面板显示一条或者多条(如果有多个卷的话)卷快照记录。

1 0
原创粉丝点击