基於ocata版本進行跟蹤分析html
一、nova-api接受並處理請求的入口函數nova/api/openstack/compute/servers.py/ServersController._action_create_imageweb
nova/api/openstack/compute/servers.py class ServersController(wsgi.Controller): @wsgi.action('createImage') @common.check_snapshots_enabled @validation.schema(schema_servers.create_image, '2.0', '2.0') @validation.schema(schema_servers.create_image, '2.1') def _action_create_image(self, req, id, body): """Snapshot a server instance.""" # 從req中獲取請求的上下文,並驗證執行權限 context = req.environ['nova.context'] context.can(server_policies.SERVERS % 'create_image') # 從body中解析出傳遞的參數,快照名稱及屬性信息 entity = body["createImage"] image_name = common.normalize_name(entity["name"]) metadata = entity.get('metadata', {}) # Starting from microversion 2.39 we don't check quotas on createImage if api_version_request.is_supported( req, max_version= api_version_request.MAX_IMAGE_META_PROXY_API_VERSION): # 檢查快照屬性的相關配額信息 common.check_img_metadata_properties_quota(context, metadata) #經過虛機uuid,從數據庫中獲取虛機實例的信息,返回的是一個實例對象 instance = self._get_server(context, req, id) #從數據庫bloack_device_mapping表裏面獲取該虛機全部的塊設備映射信息 bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(context, instance.uuid) try: # 判斷虛機是基於鏡像啓動仍是基於磁盤啓動 if compute_utils.is_volume_backed_instance(context, instance,bdms): context.can(server_policies.SERVERS %'create_image:allow_volume_backed') #基於磁盤啓動的虛機快照入口 image = self.compute_api.snapshot_volume_backed( context, instance, image_name, extra_properties=metadata) else: #基於鏡像啓動的虛機快照入口 image = self.compute_api.snapshot(context, instance, image_name, extra_properties=metadata) ......... # build location of newly-created image entity image_id = str(image['id']) #根據glance配置爲該鏡像生成url image_ref = glance.generate_image_url(image_id) resp = webob.Response(status_int=202) resp.headers['Location'] = image_ref return resp
所以能夠看到,執行基於磁盤啓動虛機快照時,實際走的是「 compute_api.snapshot_volume_backed 」數據庫
nova/compute/api.py @profiler.trace_cls("compute_api") class API(base.Base): """API for interacting with the compute manager.""" def snapshot_volume_backed(self, context, instance, name, extra_properties=None): """Snapshot the given volume-backed instance. :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)------s1步 # 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 新鏡像只是一堆屬性(特別是塊設備映射、內核和ramdisk id),沒有映像數據,所以大小爲零 image_meta['size'] = 0 for attr in ('container_format', 'disk_format'):---清除鏡像metadata屬性中的 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 quiesced = False if instance.vm_state == vm_states.ACTIVE: try: # 判斷虛擬機的狀態,若是虛擬機處於active,則經過rpc通知虛擬機進入靜默狀態 self.compute_rpcapi.quiesce_instance(context, instance) quiesced = True except (exception.InstanceQuiesceNotSupported, exception.QemuGuestAgentNotEnabled, exception.NovaException, NotImplementedError) as err: if strutils.bool_from_string(instance.system_metadata.get( 'image_os_require_quiesce')): raise else: LOG.info(_LI('Skipping quiescing instance: ' '%(reason)s.'), {'reason': err}, instance=instance) # 從數據庫中獲取該虛機所關聯的全部塊設備,結果會返回一個BlockDeviceMappingList對象 bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(context, instance.uuid) mapping = [] #作快照的操做,虛機掛在了多少個卷設備,就要作多少次快照 for bdm in bdms:--------s2步 #映射關係中沒有塊設備,則忽略此條映射 if bdm.no_device: continue if bdm.is_volume:----這個讀取的是block_device_mapping表裏面的destination_type字段 # create snapshot based on volume_id volume = self.volume_api.get(context, bdm.volume_id)----調用cinderclient去,根據卷的volume_id從數據庫獲取卷的詳細信息---s4步 # 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 %s') % image_meta['name']----快照的名字進行組裝,好比快照名稱是snapshot1,則這裏就是snapshot for snapshot1 LOG.debug('Creating snapshot from volume %s.', volume['id'], instance=instance) snapshot = self.volume_api.create_snapshot_force(------------s3步,調用cinderclient,給cinder發送強制建立快照的請求 context, volume['id'], name, volume['display_description']) mapping_dict = block_device.snapshot_from_bdm(snapshot['id'],bdm)-----s5步 #過濾掉已經在數據庫中存在的字段 mapping_dict = mapping_dict.get_image_mapping() else: mapping_dict = bdm.get_image_mapping() mapping.append(mapping_dict)----將雲主機全部的映射關係都添加到mapping中 #經過rpc.case發送異步請求給nova-compute # nova-compute接收到消息後,會等到快照完成後對文件系統進行解凍( if quiesced: self.compute_rpcapi.unquiesce_instance(context, instance, mapping) # 更新雲主機metadata信息中的properties信息 if mapping: properties['block_device_mapping'] = mapping properties['bdm_v2'] = True """ #到這一步時,會到添加一條記錄到glance快照(鏡像)數據庫條目 #(會在Dashboard的鏡像面板顯示一條名爲snapshot1的快照記錄), # 快照的大部分信息都拷貝至系統盤屬性,這是由於卷快照是能夠直接用來啓動雲主機的, # 另外'block_device_mapping'屬性中包含全部的volume設備快照信息(若是有的話), # 每一個volume設備快照信息做爲一條記錄,記錄在image_properties數據表; # { 'name': u'snapshot1', 'min_ram': u'0', '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 }], 'base_image_ref': u'', 'root_device_name': u'/dev/vda' }, 'size': 0 } """ return self.image_api.create(context, image_meta)
s1步,主要做用是根據虛機的鏡像元數據初始化該虛機快照的元數據後端
nova/compute/api.py class API(base.Base): def _initialize_instance_snapshot_metadata(self, instance, name, extra_properties=None): """Initialize new metadata for a snapshot of the given instance. :param instance: nova.objects.instance.Instance object :param name: string for name of the snapshot :param extra_properties: dict of extra metadata properties to include :returns: the new instance snapshot metadata """ image_meta = utils.get_image_from_system_metadata(instance.system_metadata)-------獲取虛機的鏡像源數據 image_meta.update({'name': name,'is_public': False})----把鏡像元數據的中鏡像的名字,更改成快照的名字 # Delete properties that are non-inheritable properties = image_meta['properties'] for key in CONF.non_inheritable_image_properties:----刪除鏡像數據中不能繼承的屬性, properties.pop(key, None) # The properties in extra_properties have precedence properties.update(extra_properties or {}) return image_meta 返回值爲 { u'min_disk': u'20', 'is_public': False, 'min_ram': u'0', 'properties': { 'base_image_ref': u'' }, 'name': u'snapshot1' }
s2 步 block_device_mapping表裏面的數據樣例api
*************************** 376. row *************************** created_at: 2019-07-18 12:35:24 updated_at: 2019-07-18 12:35:46 deleted_at: 2019-07-18 12:35:46 id: 24081 device_name: /dev/vdb delete_on_termination: 0 snapshot_id: NULL volume_id: e6f0ddca-74cf-40c9-8db6-d64f32d8ded4 volume_size: NULL no_device: NULL connection_info: null instance_uuid: f9846a41-72c2-4e67-99d3-8391fae7a3ce deleted: 24081 source_type: volume destination_type: volume guest_format: NULL device_type: NULL disk_bus: NULL boot_index: NULL image_id: NULL tag: NULL *************************** 377. row *************************** created_at: 2019-07-19 03:02:57----------基於鏡像啓動block_device_mapping形式 updated_at: 2019-07-19 03:02:57 deleted_at: 2019-07-19 03:10:42 id: 24084 device_name: /dev/vda delete_on_termination: 1 snapshot_id: NULL volume_id: NULL volume_size: NULL no_device: 0 connection_info: NULL instance_uuid: 99f10424-2ad3-4cdb-8ca5-ebd166d5853c deleted: 24084 source_type: image destination_type: local guest_format: NULL device_type: disk disk_bus: NULL boot_index: 0 image_id: 3006d221-72a6-4fe0-bcdc-d4ace809d8c7 tag: NULL
s5步 block_device.snapshot_from_bdm(snapshot['id'],bdm)app
根據bdm信息,來構建快照的dict格式屬性信息,返回一個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 }
nova-api主要是 完成了如下工做:
1)若是是在線快照,則凍結/解凍結文件系統
2)建立glance數據庫鏡像記錄(包含全部卷的快照信息)異步
二、cinder-api服務的相關處理ide
nova-api服務裏面s3步的操做,調用cinder的api create_snapshot_force 建立新的卷,實爲cinder api的接受請求,進行相關的處理函數
其詳解以下, from nova.volume import cinder self.volume_api = volume_api or cinder.API() snapshot = self.volume_api.create_snapshot_force(context, volume['id'], name, volume['display_description']) nova/volume/cinder.py def create_snapshot_force(self, context, volume_id, name, description): item = cinderclient(context).volume_snapshots.create(volume_id,True,name,description) return _untranslate_snapshot_summary_view(context, item)
所以能夠看出,實際調用的是cinder client的 volume_snapshots 的 create 方法,其在cinder api的入口函數爲學習
cinder/api/v2/snapshots.py class SnapshotsController(wsgi.Controller): @wsgi.response(202) def create(self, req, body): """Creates a new snapshot.""" # 根據上下文的分析,當nova-api等其餘client在發送建立卷快照的請求以後,本方法會接受到請求 # 方法接收到的參數有: # 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信息和獲取快照屬性中的信息 context = req.environ['cinder.context'] self.assert_valid_body(body, 'snapshot') snapshot = body['snapshot'] #獲取快照的metadata信息,snapshot_id kwargs['metadata'] = snapshot.get('metadata', None) try: volume_id = snapshot['volume_id'] except KeyError: msg = _("'volume_id' must be specified") raise exc.HTTPBadRequest(explanation=msg) #從數據庫中獲取卷信息 volume = self.volume_api.get(context, volume_id) #獲取傳遞進來的參數中是否使用強制快照,force=True表示採起強制快照 force = snapshot.get('force', False) msg = _LI("Create snapshot from volume %s") LOG.info(msg, volume_id) #驗證快照名及快照描述是否合法,長度不能超過256個字符 self.validate_name_and_description(snapshot) # NOTE(thingee): v2 API allows name instead of display_name # 用display_name代替name參數 if 'name' in snapshot: snapshot['display_name'] = snapshot.pop('name') try: #參數類型轉換,若是是非True/False的值,則拋異常 force = strutils.bool_from_string(force, strict=True) except ValueError as error: err_msg = encodeutils.exception_to_unicode(error) msg = _("Invalid value for 'force': '%s'") % err_msg raise exception.InvalidParameterValue(err=msg) # 開始進行快照的操做,根據force值的不一樣走不通的分支 if force: new_snapshot = self.volume_api.create_snapshot_force(-----s2.1 context, volume, snapshot.get('display_name'), snapshot.get('description'), **kwargs) else: new_snapshot = self.volume_api.create_snapshot(-----s2.2 context, volume, snapshot.get('display_name'), snapshot.get('description'), **kwargs) req.cache_db_snapshot(new_snapshot) return self._view_builder.detail(req, new_snapshot) """ from cinder import volume self.volume_api = volume.API() """ cinder/volume/api.py class API(base.Base): def create_snapshot_force(self, context, volume, name, description, metadata=None): result = self._create_snapshot(context, volume, name, description, True, metadata) LOG.info(_LI("Snapshot force create request issued successfully."), resource=result) return result def create_snapshot(self, context, volume, name, description, metadata=None, cgsnapshot_id=None, group_snapshot_id=None): result = self._create_snapshot(context, volume, name, description, False, metadata, cgsnapshot_id, group_snapshot_id) LOG.info(_LI("Snapshot create request issued successfully."), resource=result) return result def _create_snapshot(self, context, volume, name, description, force=False, metadata=None, cgsnapshot_id=None, group_snapshot_id=None): #保證卷操做處於凍結狀態,而且是可進行快照,檢查配額是否可用 volume.assert_not_frozen() #在cinder的snapshot數據表中建立一條快照記錄,即會在雲硬盤快照面板顯示一條名爲「snapshot for snapshot1」的記錄 snapshot = self.create_snapshot_in_db(----s2.3 context, volume, name, description, force, metadata, cgsnapshot_id, True, group_snapshot_id) # 調用rpc.case將create_snapshot的消息投遞到消息隊列該消息 self.volume_rpcapi.create_snapshot(context, volume, snapshot)---s2.4 給cinder-volume發送rpc請求信息 return snapshot
能夠看到兩個方法都是調用了「 _create_snapshot 」,只是在傳遞第5個參數 force 時不同,同時force爲False時,
須要傳遞其餘幾個參數(實際上也爲空)
cinder-api的操做總結爲以下兩個方面:
1)卷狀態條件檢查及配額檢查
2)建立glance數據庫快照記錄(記錄的是單個卷快照的信息)
三、cinder-volume服務對快照的處理
s2.4步中,cinder-api經過rpc給cinder-volume服務發送建立快照的請求,cinder-volume服務接受到請求,並處理,其函數入口爲manager.py文件中create_snapshot方法
cinder/volume/manager.py class VolumeManager(manager.CleanableManager, manager.SchedulerDependentManager): """Manages attachable block storage devices.""" @objects.Snapshot.set_workers def create_snapshot(self, context, snapshot): """Creates and exports the snapshot.""" # 獲取請求上下文 context = context.elevated() # 經過消息隊列,通知ceilometer快照發生變化 self._notify_about_snapshot_usage( context, snapshot, "create.start") try: """異常處理代碼,有任何異常則退出並設置快照狀態爲error""" # NOTE(flaper87): Verify the driver is enabled # before going forward. The exception will be caught # and the snapshot status updated. # 確保存儲驅動已經初始化,不然拋出異常 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 # 調用後端存儲驅動執行快照 model_update = self.driver.create_snapshot(snapshot)---------s3.1 步 # 完成以後,更新數據庫條目,若返回的是None,則不執行 if model_update: snapshot.update(model_update) snapshot.save() except Exception as error: # 若以前幾步操做出現問題,則將快照的狀態置爲error with excutils.save_and_reraise_exception(): snapshot.status = fields.SnapshotStatus.ERROR snapshot.save() self.db.snapshot_metadata_update( context, snapshot.id, {'error': six.text_type(error)}, False) # 從cinder的數據庫中獲取卷的信息 vol_ref = self.db.volume_get(context, snapshot.volume_id) # 若是該卷的bootable屬性爲True,表示該卷是啓動卷,表示雲主機是經過卷啓動的,即系統盤, # 若是是非啓動卷,則跳過 if vol_ref.bootable: try: # 用卷的metadata信息來更新snapshot的metadata信息,須要保證系統盤的元數據與其快照的元數據一致 self.db.volume_glance_metadata_copy_to_snapshot( context, snapshot.id, snapshot.volume_id) except exception.GlanceMetadataNotFound: # 更新snapshot的元數據若是拋出GlanceMetadataNotFound, # 表示從glance中找不到卷的元數據信息,能夠直接跳過 # If volume is not created from image, No glance metadata # would be available for that volume in # volume glance metadata table pass except exception.CinderException as ex: LOG.exception(_LE("Failed updating snapshot" " metadata using the provided volumes" " %(volume_id)s metadata"), {'volume_id': snapshot.volume_id}, resource=snapshot) # 若是拋出cinder方面的異常,則有多是快照出現問題,則直接將快照的狀態置爲error snapshot.status = fields.SnapshotStatus.ERROR snapshot.save() self.db.snapshot_metadata_update(context, snapshot.id, {'error': six.text_type(ex)}, False) raise exception.MetadataCopyFailure(reason=six.text_type(ex)) # 若一路過來沒有出現異常,則表明快照完成,將快照狀態標記爲可用,進度爲100%,並保存狀態 snapshot.status = fields.SnapshotStatus.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) return snapshot.id
從上面的代碼中能夠找到,執行快照實際上是調用底層的後端存儲來作的,即s3.1 步的「driver.create_snapshot(snapshot)」,
針對不一樣的存儲類型,會有不一樣的處理方式
所以cinder-volume服務快照功能很簡單:調用後端存儲執行快照,而後更新glance數據庫快照記錄
學習過程當中,參考以下博客 https://www.cnblogs.com/qianyeliange/p/9713146.html