首页 > 网络 > 云计算 >

Openstackliberty创建实例快照源码分析2

2016-07-02

这是创建云主机实例快照源码分析系列的最后一篇,在第一篇文章中分析了从镜像启动云主机,创建在线 离线快照的过程; 磁盘启动云主机,离线(在

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

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_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主要完成了如下的功能:

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

cinder创建磁盘快照

cinder-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的操作总结为如下两个方面:

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

cinder-volume处理过程

从消息队列拿到来自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的镜像面板显示一条镜像记录,在卷快照面板显示一条或者多条(如果有多个卷的话)卷快照记录。

相关文章
最新文章
热点推荐