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

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

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

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的镜像面板显示一条镜像记录,在卷快照面板显示一条或者多条(如果有多个卷的话)卷快照记录。

时间: 2024-12-15 08:27:08

Openstack liberty 创建实例快照源码分析2的相关文章

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

Openstack liberty中也支持对云主机执行快照,快照是备份系统中一种常用的数据保护技术,在生产系统故障后,能通过快照将系统快速恢复到快照时间点:那Openstack中的云主机快照是否也支持这种故障恢复呢?请看下文: Openstack支持对处于运行或者停止状态的云主机执行快照,另外Openstack既可以从镜像启动云主机,也可以从启动磁盘启动云主机,根据条件组合,可以执行下面4中快照: 镜像启动云主机的离线快照 镜像启动云主机的在线快照 磁盘启动云主机的离线快照 磁盘启动云主机的在线

Picasso源码分析(五):into方法追本溯源和责任链模式创建BitmapHunter

Picasso源码分析(一):单例模式.建造者模式.面向接口编程 Picasso源码分析(二):默认的下载器.缓存.线程池和转换器 Picasso源码分析(三):快照功能实现和HandlerThread的使用 Picasso源码分析(四):不变模式.建造者模式和Request的预处理 Picasso源码分析(五):into方法追本溯源和责任链模式创建BitmapHunter Picasso源码分析(六):BitmapHunter与请求结果的处理 Picasso异步加载图片流程回顾 首先通过wit

Hadoop2源码分析-准备篇

1.概述 我们已经能够搭建一个高可用的Hadoop平台了,也熟悉并掌握了一个项目在Hadoop平台下的开发流程,基于Hadoop的一些套件我们也能够使用,并且能利用这些套件进行一些任务的开发.在Hadoop的应用级别上,我们接着往后面去研究学习,那就是Hadoop的源码了,作为Hadoop开发人员,我们得去学习和研究Hadoop得实现原理,底层框架的设计,编码的实现过程等等,下面就开始我们今天的Hadoop源码分析之旅. 2.准备 在分析源码之前,我们需要准备好分析源码的环境,以及如何去分析(分

jQuery.lazyload使用及源码分析

前言: 貌似以前自己也写过图片懒加载插件,但是新公司使用的是jQuery.lazyload插件,为了更好的运用,自己还是把源码看了遍,分别记录了如何使用, 插件原理,各个配置属性的完整解释,demo实例,源码分析(较简短),源码分析可以配合使用,配置属性,原理进行阅读,如需转载,请注明出处 博客园 华子yjh 一.如何使用 // 最简单的使用,不带参数 $('img').lazyload(); // 带参数(配置对象),下面配置对象中的各个属性值都是默认的 $('img').lazyload({

Spark 源码分析系列

如下,是 spark 源码分析系列的一些文章汇总,持续更新中...... Spark RPC spark 源码分析之五--Spark RPC剖析之创建NettyRpcEnv spark 源码分析之六--Spark RPC剖析之Dispatcher和Inbox.Outbox剖析 spark 源码分析之七--Spark RPC剖析之RpcEndPoint和RpcEndPointRef剖析 spark 源码分析之八--Spark RPC剖析之TransportContext和TransportClie

Openstack liberty源码分析 之 云主机的启动过程3

接上篇Openstack liberty源码分析 之 云主机的启动过程2, 简单回顾下:nova-conductor收到nova-scheduler返回的主机列表后,依次发送异步rpc请求给目标主机的nova-compute服务,下面继续来看nova-compute服务的处理过程: nova-compute 根据路由映射,nova-compute中处理云主机启动请求的方法为 nova/compute/manager.py.ComputeManager.py.build_and_run_insta

Openstack liberty源码分析 之 云主机的启动过程2

接上一篇: Openstack liberty源码分析 之 云主机的启动过程1 nova-conductor nova-api通过rpc发送启动云主机请求后,nova-conductor会收到该请求,根据路由映射,该请求会递交给 nova/conductor/manager.py.ComputeTaskManager.build_instances处理,如下(函数说明见注释): def build_instances(self, context, instances, image, filter

OpenStack源码分析——Nova-Scheduler

一.服务启动 Nova-scheduler服务的启动入口脚本是cmd包下的scheduler.py,其主要监听来自于消息队列中topic=scheduler(可配置)的消息.在服务启动过程中,其将初始化一个SchedulerManager实例作为该服务的Handler,来处理接受到的消息请求. 同时,Nova-scheduler服务在启动的过程中,会将自己注册到DB中,即将自己的host.binary.topic.report_count信息添加到services表中,并将自己同样注册到Serv

openstack swift 源码分析之swift单机部署

本文对在单机部署swift 其中每一个细节做详细的介绍,并对配置做相应的解释 PC物理机    Ubuntu-12.04-desktop-64位 Swift 版本:1.13.1 Swift-client   1.2.0 注意:本文所有操作都是在root权限下进行的. 1 .下载swift 和swift-client 源代码,本文利用git从github获取其源代码 获取swift源代码 git clone https://github.com/openstack/swift.git 获取pyth