Openstack liberty 雲主機遷移源碼分析之靜態遷移2

接上一篇Openstack liberty 雲主機遷移源碼分析之靜態遷移1,上篇文中主要介紹了nova-apinova-conductor的處理過程,本文將重點分析在雲主機遷移過程中nova-compute所做的工作,可以分爲如下三個部分:

  • prepare準備階段
  • execute執行階段
  • complete完成階段

下面一起來看看具體的內容:

prepare準備階段

nova-compute從消息隊列拿到prep_resize請求後,將由下述方法處理該請求:

#/nova/compute/manager.py/ComputeManager.prep_resize
def prep_resize(self, context, image, instance, instance_type,
                    reservations, request_spec, 
                    filter_properties, node,
                    clean_shutdown):
    """Initiates the process of moving a running instance to 
    another host.Possibly changes the RAM and disk size in the 
    process.

    輸入參數,來自`nova-conductor`,如下:
    image 雲主機所使用的鏡像信息
    instance InstanceV2對象,包含雲主機詳細信息
    instance_type, Flavor對象,雲主機所使用的配置模板
    reservations = []  資源保留配額
    request_spec 請求參數,包含:鏡像信息,雲主機信息,配置模板
    filter_properties 過濾屬性
    node 目標節點名, 這裏是`devstack`
    """

    #如果沒有指定指定node,則通過hypervisor獲取(這裏是
    #LibvirtDriver)
    if node is None:
        node = self.driver.get_available_nodes(refresh=True)[0]
        LOG.debug("No node specified, defaulting to %s", node,
                      instance=instance)

    """NOTE(melwitt): Remove this in version 5.0 of the RPC API
    Code downstream may expect extra_specs to be populated 
    since it is receiving an object, so lookup the flavor to 
    ensure this.
    """
    #如果instance_type不是合法的Flavor對象,則從nova.instance_types
    #表中獲取配置模板信息
    if not isinstance(instance_type, objects.Flavor):
        instance_type = objects.Flavor.get_by_id(context,
                                       instance_type['id'])
    #從保留配額生成配額對象Quotas
    quotas = objects.Quotas.from_reservations(context,
                                                  reservations,
                                                  instance=instance)

    #異常上下文:遷移發生異常時回滾配額及雲主機狀態
    with self._error_out_instance_on_exception(context, instance,
                                                   quotas=quotas):
        #發送`compute.instance.exists`通知給ceilometer,
        #通知包含:雲主機的詳細配置信息;默認審計週期爲(month),  
        #current_period=True,表示添加該通知到當前統計週期                                      
        compute_utils.notify_usage_exists(self.notifier, 
                                            context, instance,
                                           current_period=True)
        #發送`compute.instance.resize.prep.start`通知ceilometer,
        #通知包含:雲主機詳細信息
        self._notify_about_instance_usage(
                    context, instance, "resize.prep.start")

        try:
            #調用_prep_resize執行後續的遷移操作,下文具體分析
            self._prep_resize(context, image, instance,
                                  instance_type, quotas,
                                  request_spec, 
                                  filter_properties,
                                  node, clean_shutdown)
        # NOTE(dgenin): This is thrown in LibvirtDriver when the
        #               instance to be migrated is backed by LVM.
        #               Remove when LVM migration is implemented.
        #如果nova使用lvm作爲後端存儲,從鏡像啓動的雲主機將不支持遷移
        except exception.MigrationPreCheckError:
            raise
        except Exception:
            # try to re-schedule the resize elsewhere:
            #獲取具體的異常信息,如:UnableToMigrateToSelf
            exc_info = sys.exc_info()
            """重新調度:如果包含retry則執行重新調度,實際上就是通過`
            `nova/conductor/rpcapi.py/ComputeTaskAPI`重新發起
            遷移請求,再次進入雲主機遷移源碼分析之靜態遷移1的`nova-
            conductor`部分,與前一次不同的是,重試請求包含了前一次請求
            的異常信息並且在選擇目標主機的時候會排除前一次已選擇的目標主
            機
            """
            self._reschedule_resize_or_reraise(context, image,
                        instance,
                        exc_info, instance_type, quotas, 
                        request_spec,
                        filter_properties)
        finally:
            #模板信息:名稱及id
            extra_usage_info = dict(
                        new_instance_type=instance_type.name,
                        new_instance_type_id=instance_type.id)
            #發送`compute.instance.resize.prep.end`通知給
            #ceilometer,通知包含:雲主機詳細信息及配置模板名和id
            self._notify_about_instance_usage(
                    context, instance, "resize.prep.end",
                    extra_usage_info=extra_usage_info)

---------------------------------------------------------------
#接上文:
def _prep_resize(self, context, image, instance, instance_type,
            quotas, request_spec, filter_properties, node,
            clean_shutdown=True):

    if not filter_properties:
        filter_properties = {}

    if not instance.host:
        self._set_instance_obj_error_state(context, instance)
        msg = _('Instance has no source host')
        raise exception.MigrationError(reason=msg)

    #檢查源主機是否和目標主機是否是同一個
    same_host = instance.host == self.host

    # if the flavor IDs match, it's migrate; otherwise resize
    #在同一個主機上遷移!!!,需要檢查hypervisor是否支持同一主機上的遷移
    if same_host and instance_type.id == 
                                instance['instance_type_id']:
        # check driver whether support migrate to same host
        #libvirt默認不支持同一主機上的遷移,這裏會拋異常
        if not self.driver.
                capabilities['supports_migrate_to_same_host']:
            raise exception.UnableToMigrateToSelf(
                    instance_id=instance.uuid, host=self.host)

    # NOTE(danms): Stash the new instance_type to avoid having 
    #to look it up in the database later
    #添加'new_flavor'屬性到Instance,包含配置模板instance_type信息
    instance.set_flavor(instance_type, 'new')

    # NOTE(mriedem): Stash the old vm_state so we can set the
    # resized/reverted instance back to the same state later.
    #保留當前的雲主機狀態,用於回滾或者遷移完成後設置新主機狀態
    vm_state = instance.vm_state
    LOG.debug('Stashing vm_state: %s', vm_state, instance=instance)
    instance.system_metadata['old_vm_state'] = vm_state
    instance.save()

    #創建節點資源跟蹤器ResourceTracker
    limits = filter_properties.get('limits', {})
    rt = self._get_resource_tracker(node)
    #保存遷移上下文MigrationContext到instance,並添加一條記錄到
    #nova.migrations數據表中
    #根據配置模板(instance_type)校驗目標主機上資源情況(日誌文件中打印
    #Attemping Claim之類的日誌,如果資源不足會拋異常)
    #根據配置模板(instance_type)在目標主機上保留遷移所需的資源
    with rt.resize_claim(context, instance, instance_type,
                           image_meta=image, limits=limits) as claim:
        LOG.info(_LI('Migrating'), context=context, 
                                            instance=instance)
        #通過異步rpc發送`resize_instance`請求給`nova-compute`
        #下文具體分析                                 
        self.compute_rpcapi.resize_instance(
                    context, instance, claim.migration, image,
                    instance_type, quotas.reservations,
                    clean_shutdown)

小結: 該階段主要是完成一些前期的條件判斷、參數設置、資源校驗及保留及發送ceilometer審計通知,爲後續的執行階段做準備

execute執行階段

從消息隊列拿到前述的resize_instance消息後,nova-compute通過下述方法來執行該請求:

#nova/compute/manager.py/ComputeManager.resize_instance
def resize_instance(self, context, instance, image,
                        reservations, migration, instance_type,
                        clean_shutdown):
    """Starts the migration of a running instance to another 
     host. 

    migration 上文的rpc投遞過來的遷移參數,包含:遷移所需的詳細信息            
    """  
    #基於資源保留生成配額對象Quotas
    quotas = objects.Quotas.from_reservations(context,
                                                  reservations,
                                                  instance=instance)       
    #異常上下文:遷移發生異常時回滾配額及設置雲主機狀態                                                      
    with self._error_out_instance_on_exception(context, 
                                        instance,
                                         quotas=quotas):
        """ TODO(chaochin) Remove this until v5 RPC API
        Code downstream may expect extra_specs to be 
        populated since it is receiving an object, so lookup 
        """
        #如果沒有指定配置模板,從migration參數中提取
        if (not instance_type or
            not isinstance(instance_type, objects.Flavor)):
            instance_type = objects.Flavor.get_by_id(
                    context, migration['new_instance_type_id'])

        #從neutron數據庫中獲取與雲主機關聯的所有網卡信息(VIF信息)
        network_info = self.network_api.get_instance_nw_info(context,
                                                   instance)
        #更新遷移狀態(nova.migrations數據表)
        migration.status = 'migrating'
        with migration.obj_as_admin():
            migration.save()

        #更新雲主機狀態:雲主機狀態:重建/遷移,任務狀態:正在重建或者遷移
        instance.task_state = task_states.RESIZE_MIGRATING
        instance.save(expected_task_state=
                                    task_states.RESIZE_PREP)

        #發送`compute.instance.resize.start`通知給ceilometer,
        #通知包含:雲主機詳細信息及網卡信息
        self._notify_about_instance_usage(
                context, instance, "resize.start", 
                network_info=network_info)
        #從nova.block_device_mapping數據表中獲取雲主機關聯的塊設備映
        #射信息
        bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
                    context, instance.uuid)
        #將上述的塊設備映射信息轉換成設備驅動所需要的格式,如:
        """
        {'swap' = None,
         'root_device_name' : u'/dev/vda',
         'ephemerals' = [],
         'block_device_mapping' = []
        }
        block_device_mapping包含,雲主機上的卷設備信息列表
        """
        block_device_info = self._get_instance_block_device_info(
                                context, instance, bdms=bdms)

        #從雲主機的system_metadata中獲取關機超時及重試信息(如果
        #clean_shutdown = True),否則設置爲0,0
        timeout, retry_interval = self._get_power_off_values(context,
                                            instance, clean_shutdown)
        #通過LibvirtDriver執行關機遷移,下文具體分析                                 
        disk_info = self.driver.migrate_disk_and_power_off(
                    context, instance, migration.dest_host,
                    instance_type, network_info,
                    block_device_info,
                    timeout, retry_interval)   
        #通過cinder斷開卷設備連接           
        self._terminate_volume_connections(context, instance, bdms)
        #遷移網卡(空操作,在`complete`結束階段在目的主機上重新配置網卡)
        migration_p = obj_base.obj_to_primitive(migration)
        self.network_api.migrate_instance_start(context,
                                                    instance,
                                                    migration_p)
        #更新遷移狀態(nova.migrations數據表)                                            
        migration.status = 'post-migrating'
        with migration.obj_as_admin():
            migration.save()
        #更新雲主機的主機信息及狀態:雲主機狀態:重建/遷移,任務狀態:正在
        #完成重建或者遷移
        instance.host = migration.dest_compute
        instance.node = migration.dest_node
        instance.task_state = task_states.RESIZE_MIGRATED
        instance.save(expected_task_state=
                                task_states.RESIZE_MIGRATING)
        #通過異步rpc發起`finish_resize`請求,`nova-compute`會處理該
        #請求,下文具體分析
        self.compute_rpcapi.finish_resize(context, 
                        instance,
                        migration, image, disk_info,
                        migration.dest_compute, 
                        reservations=quotas.reservations)
        #發送`compute.instance.resize.end`通知給ceilometer
        #通知包含:雲主機詳細信息及網卡信息
        self._notify_about_instance_usage(context, instance, 
                        "resize.end",network_info=network_info) 
        #移除所有的pending事件
        self.instance_events.clear_events_for_instance(
                                                    instance)

---------------------------------------------------------------

#接上文:nova/virt/libvirt/driver.py/LibvirtDriver
def migrate_disk_and_power_off(self, context, instance, dest,
                                   flavor, network_info,
                                   block_device_info=None,
                                   timeout=0, retry_interval=0):  
    LOG.debug("Starting migrate_disk_and_power_off",
                   instance=instance)
    #獲取外部設備
    ephemerals =  
     driver.block_device_info_get_ephemerals(block_device_info)  
    # get_bdm_ephemeral_disk_size() will return 0 if the new
    # instance's requested block device mapping contain no
    # ephemeral devices. However, we still want to check if
    # the original instance's ephemeral_gb property was set and
    # ensure that the new requested flavor ephemeral size is 
    #greater
    eph_size = (block_device.get_bdm_ephemeral_disk_size(ephemerals) 
                                      or instance.ephemeral_gb) 
    # Checks if the migration needs a disk resize down.
    root_down = flavor.root_gb < instance.root_gb
    ephemeral_down = flavor.ephemeral_gb < eph_size

    #從雲主機的xml配置中獲取塊非volume設備信息,我的例子中爲[]
    disk_info_text = self.get_instance_disk_info(
            instance, block_device_info=block_device_info)

    #檢查雲主機是否從卷啓動(如果instance中不包含image_ref鏡像屬性或者
    #塊設備信息中不包含'disk'信息,就是從卷啓動的)        
    booted_from_volume = self._is_booted_from_volume(instance,
                                    disk_info_text)
    #如果從鏡像啓動則根磁盤不支持收縮;外部設備也不支持收縮                                
    if (root_down and not booted_from_volume) or ephemeral_down:
        reason = _("Unable to resize disk down.")
        raise exception.InstanceFaultRollback(
                exception.ResizeError(reason=reason))

    disk_info = jsonutils.loads(disk_info_text)
    # NOTE(dgenin): Migration is not implemented for LVM backed 
    #instances.
    #如果nova使用lvm作爲後端存儲,從鏡像啓動的雲主機將不支持遷移
    if CONF.libvirt.images_type == 'lvm' and not 
                                        booted_from_volume:
        reason = _("Migration is not supported for LVM backed 
                                                instances")
        raise exception.InstanceFaultRollback(
                exception.MigrationPreCheckError(reason=reason))    

    # copy disks to destination
    # rename instance dir to +_resize at first for using
    # shared storage for instance dir (eg. NFS).
    #獲取雲主機本地配置路徑,
    #如:/opt/stack/data/nova/instances/{uuid}
    inst_base = libvirt_utils.get_instance_path(instance)
    inst_base_resize = inst_base + "_resize"    

    #判斷目標主機和源主機是否共享存儲
    shared_storage = self._is_storage_shared_with(dest, inst_base)  

    # try to create the directory on the remote compute node
    # if this fails we pass the exception up the stack so we 
    #can catch failures here earlier
    #如果是共享存儲,則通過ssh在目標主機上創建雲主機根目錄,失敗則拋異常
    if not shared_storage:
        try:
            self._remotefs.create_dir(dest, inst_base)
        except processutils.ProcessExecutionError as e:
            reason = _("not able to execute ssh command: %s") % e
            raise exception.InstanceFaultRollback(
                    exception.ResizeError(reason=reason))   
    #執行遷移前,通過libvirt關閉雲主機
    self.power_off(instance, timeout, retry_interval)  

    #從塊設備信息字典中獲取卷設備映射字典
    block_device_mapping = driver.block_device_info_get_mapping(
            block_device_info)
    #遷移前,通過特定類型的卷驅動卸載卷設備。對於
    #rbd(LibvirtNetVolumeDriver),什麼都沒有做;
    #對於iscsi(LibvirtISCSIVolumeDriver),做了兩個工作:
    #1. echo '1' > /sys/block/{dev_name}/device/delete
    #2. 通過iscsiadm工具刪除相關的端點信息
    for vol in block_device_mapping:
        connection_info = vol['connection_info']
        disk_dev = vol['mount_device'].rpartition("/")[2]
        self._disconnect_volume(connection_info, disk_dev)

    try:
        #重命名雲主機配置目錄
        utils.execute('mv', inst_base, inst_base_resize)
        """ if we are migrating the instance with shared 
        storage then create the directory.  If it is a remote 
        has already been created
        """
        #創建新的雲主機配置目錄
        if shared_storage:
            dest = None
            utils.execute('mkdir', '-p', inst_base)

        #創建一個作業跟蹤器(添加/刪除),用於後面的磁盤拷貝
        on_execute = lambda process: \
                self.job_tracker.add_job(instance, process.pid)
        on_completion = lambda process: \
                self.job_tracker.remove_job(instance, process.pid)
        #獲取雲主機配置模板        
        active_flavor = instance.get_flavor()

        #遷移非volume設備
        for info in disk_info:
            # assume inst_base == dirname(info['path'])
            img_path = info['path']
            fname = os.path.basename(img_path)
            from_path = os.path.join(inst_base_resize, fname)

            """ To properly resize the swap partition, it must 
            be re-created with the proper size.  This is 
            acceptable because when an OS is shut down, the 
            contents of the swap space are just garbage, the OS 
            doesn't bother about what is in it.
            We will not copy over the swap disk here, and rely 
            on finish_migration/_create_image to re-create it 
            for us.
            """
            if not (fname == 'disk.swap' and
                    active_flavor.get('swap', 0) != 
                        flavor.get('swap', 0)):
                compression = info['type'] not in 
                                    NO_COMPRESSION_TYPES
                libvirt_utils.copy_image(from_path, img_path, 
                                        host=dest,
                                        on_execute=on_execute,
                                   on_completion=on_completion,
                                     compression=compression)
        """ Ensure disk.info is written to the new path to 
        avoid disks being reinspected and potentially 
        changing format.
        """
        #拷貝disk_info到目的主機(如果有的話),我是採用ceph 作爲nova
        #後端存儲,在根目錄上只有console.log和libvirt.xml兩個文件
        src_disk_info_path = os.path.join(inst_base_resize, 
                                                   'disk.info')
        if os.path.exists(src_disk_info_path):
            dst_disk_info_path = os.path.join(inst_base, 
                                                   'disk.info')
            libvirt_utils.copy_image(src_disk_info_path,
                                         dst_disk_info_path,
                                         host=dest, 
                                         on_execute=on_execute,
                                on_completion=on_completion)

    except Exception:
        #發生異常回滾上述的操作,並上拋異常
        with excutils.save_and_reraise_exception():
            self._cleanup_remote_migration(dest, inst_base,
                                              inst_base_resize,
                                               shared_storage)

    return disk_info_text

小結:該階段主要完成non-volume塊設備的複製,同時更新雲主機狀態併發送ceilometer審計通知,最後通過異步rpc發起finish_resize請求,進入下一階段

complete完成階段

從消息隊列拿到上述的finish_resize消息後,nova-compute通過下述方法來執行該請求:

#nova/compute/manager.py/ComputeManager.finish_resize
def finish_resize(self, context, disk_info, image, instance,
                      reservations, migration):
    """Completes the migration process.
    Sets up the newly transferred disk and turns on the 
    instance at its new host machine.
    """
    #生成配額對象Quotas
    quotas = objects.Quotas.from_reservations(context,
                                                  reservations,
                                                  instance=instance)
    try:
        #完成遷移的結尾操作,下文具體分析
        self._finish_resize(context, instance, migration,
                                disk_info, image)
        #提交資源配額,更新數據庫
        quotas.commit()
    except Exception:
        LOG.exception(_LE('Setting instance vm_state to ERROR'),
                          instance=instance)
        #發生異常,回滾配額
        with excutils.save_and_reraise_exception():
            try:
                quotas.rollback()
            except Exception:
                LOG.exception(_LE("Failed to rollback quota" 
                                 "for failed finish_resize"),
                                  instance=instance)
            #設置雲主機狀態爲error(錯誤)                      
            self._set_instance_obj_error_state(context, instance)

------------------------------------------------------------
#接上文:
def _finish_resize(self, context, instance, migration, disk_info,
                       image):
    #默認是遷移操作
    resize_instance = False
    #從遷移參數字典中提取新舊配置模板id
    old_instance_type_id = migration['old_instance_type_id']
    new_instance_type_id = migration['new_instance_type_id']
    #從雲主機實例對象提取配置模板信息
    old_instance_type = instance.get_flavor()
    """ NOTE(mriedem): Get the old_vm_state so we know if we 
    should power on the instance. If old_vm_state is not set we 
    need to default to ACTIVE for backwards compatibility
    """
    #從雲主機實例對象的系統元信息中提取遷移前雲主機的狀態,默認爲運行態
    #在`prepare`準備階段,會設置該值
    old_vm_state = instance.system_metadata.get('old_vm_state',
                                                   vm_states.ACTIVE)
    #添加old_flavor屬性,保存舊的配置模板到雲主機實例對象中                                               
    instance.set_flavor(old_instance_type, 'old')

    #如果新舊配置模板id不同,則用新模板配置instance
    if old_instance_type_id != new_instance_type_id:
        #從new_flavor屬性獲取新配置模板,該值在`prepare`準備階段設置
        instance_type = instance.get_flavor('new')
        self._set_instance_info(instance, instance_type)
        #如果新舊配置中根磁盤、交換分區、外設不同,則認爲是resize操作
        for key in ('root_gb', 'swap', 'ephemeral_gb'):
            if old_instance_type[key] != instance_type[key]:
                resize_instance = True
                break
    #應用遷移上下文MigrationContext,上下文在`prepare`準備階段設置
    #更新instance的numa拓撲信息
    instance.apply_migration_context()

    # NOTE(tr3buchet): setup networks on destination host
    #在目標主機上爲新的雲主機配置網卡(這是空操作,下面的
    #migrate_instance_finish才執行正在的網卡配置)
    self.network_api.setup_networks_on_host(context, instance,
                                     migration['dest_compute'])
    #遷移網卡,更新數據庫(設置'binding:host_id'屬性爲目標主機)
    migration_p = obj_base.obj_to_primitive(migration)
    self.network_api.migrate_instance_finish(context,
                                                 instance,
                                                 migration_p)
    #獲取雲主機的網卡信息,發送錯誤則拋異常
    network_info = self.network_api.get_instance_nw_info(context, instance)

    #更新雲主機狀態: 雲主機狀態:重建/遷移,任務狀態:完成重建或者遷移
    instance.task_state = task_states.RESIZE_FINISH
    instance.save(expected_task_state=
                            task_states.RESIZE_MIGRATED)

    #發送`compute_instance.finish_resize.start`通知給ceilometer
    #通知包含雲主機詳細信息及網卡信息
    self._notify_about_instance_usage(
            context, instance, "finish_resize.start",
            network_info=network_info)

    #獲取雲主機的塊設備信息,並更新卷設備的'connection_info'信息
    block_device_info = self._get_instance_block_device_info(
                            context, instance, 
                            refresh_conn_info=True)

    """ NOTE(mriedem): If the original vm_state was STOPPED, we 
    don't automatically power on the instance after it's 
    migrated
    """
    #需要啓動雲主機
    power_on = old_vm_state != vm_states.STOPPED
    try:
        #在目標主機上配置雲主機,並啓動雲主機(如果需要)
        """這部分的處理代碼與之前寫的[雲主機啓動過程源碼分析3](http://blog.csdn.net/lzw06061139/article/details/51505514)
        中的最後一部分代碼相似,都是調用相同的接口完成磁盤配置,網絡配置,
        xml配置等,最後啓動雲主機;
        """
        self.driver.finish_migration(context, migration, 
                                        instance,
                                         disk_info,
                                         network_info,
                                         image, 
                                         resize_instance,
                                         block_device_info, 
                                         power_on)
    except Exception:
       #異常,還原雲主機配置模板
        with excutils.save_and_reraise_exception():
            if old_instance_type_id != new_instance_type_id:
                self._set_instance_info(instance,
                                            old_instance_type)
    #更新遷移狀態(nova.migrations數據表)
    migration.status = 'finished'
    with migration.obj_as_admin():
        migration.save()

    #更新雲主機狀態:任務狀態爲:None
    instance.vm_state = vm_states.RESIZED
    instance.task_state = None
    instance.launched_at = timeutils.utcnow()
    instance.save(expected_task_state=
                            task_states.RESIZE_FINISH)

    #發送消息給`nova-scheduler`,更新節點上的主機信息
    self._update_scheduler_instance_info(context, instance)

    #發送`compute_instance.finish_resize.end`通知給ceilometer,
    #通知內容包含雲主機詳細信息及網卡信息
    self._notify_about_instance_usage(
            context, instance, "finish_resize.end",
            network_info=network_info)

小結:該階段主要是配置雲主機:磁盤,網卡,生成xml文件,然後啓動雲主機。

到這裏雲主機靜態遷移的源碼分析就完成了,縱觀整個遷移過程:openstack的大部分工作是在處理各種資源配置,一切資源就緒後,其實就是啓動一個新的雲主機。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章