Openstack liberty 雲主機遷移源碼分析之在線遷移1

這是Openstack liberty雲主機遷移源碼分析的第二部分 - 在線遷移(熱遷移/動態遷移)源碼分析;和之前的靜態遷移(離線遷移)源碼分析一樣,也用兩篇博文詳細闡述liberty中熱遷移的過程,兩篇博文的內容劃分如下:

  • 第一篇:分析nova-api,nova-conductor的處理過程
  • 第二篇:分析nova-compute的處理過程

下面來看第一篇,在線遷移過程中nova-api,nova-conductor的處理過程:

發起在線遷移

用戶可以通過 nova live-migration發起在線遷移操作,如:

#nova --debug live-migration  57fe59d1-2566-42c1-8b17-b2a1e50c889e

可以通過nova help live-migration幫助命令查看使用規則

--debug選項用來打印客戶端調試日誌:

POST http://10.240.xxx.xxx:8774/v2.1/25520b29dce346d38bc4b055c5ffbfcb/servers/57fe59d1-2566-42c1-8b17-b2a1e50c889e/action -H "User-Agent: python-novaclient" -H "Content-Type: application/json" -H "Accept: application/json" -H "X-OpenStack-Nova-API-Version: 2.6" -H "X-Auth-Token: {SHA1}6b48595f60d527e2c55a182c65c32c6f9cc76e67" -d '{"os-migrateLive": {"disk_over_commit": false, "block_migration": false, "host": null}}'

從上面的日誌以及nova-api啓動時建立的路由映射,我們很容易的知道消息的入口是:nova/api/openstack/compute/migrate_server.py/MigrateServerController._migrate_live,下面一起來看看具體的處理過程:

nova-api處理階段

#這裏省略裝飾器定義(知道:裝飾器在函數之前執行,並且各個裝飾器的執行順序與#聲明順序相反即可)
def _migrate_live(self, req, id, body):
    """Permit admins to (live) migrate a server to a new 
    host.

    參數如下:
    req Request對象,包含請求的詳細信息
    id 待遷移的雲主機的uuid 57fe59d1-2566-42c1-8b17-b2a1e50c889e
    body 請求體,包含本次請求的參數 {"os-migrateLive": 
    {"disk_over_commit": false, "block_migration": false, 
    "host": null}}
    """
    #得到請求上下文
    context = req.environ["nova.context"]

    """根據CONF.policy_file=`/etc/nova/policy.json`文件及
    CONF.policy_dirs目錄下策略文件中定義的策略認證該action在context上
    下文中是否合法,適用的規則是:
    "os_compute_api:os-migrate-server:migrate_live": 
    "rule:admin_api",如果沒有定義相應的規則,則嘗試執行默認規則
    (CONF.policy_default_rule, 適用的規則
    是:"default":"rule:admin_or_owner"),認證失敗,拋異常
    """
    authorize(context, action='migrate_live')

    #根據請求體body獲取配置參數
    block_migration = body["os-migrateLive"]["block_migration"]
    disk_over_commit = body["os-migrateLive"]["disk_over_commit"]
    host = body["os-migrateLive"]["host"]

    #參數類型轉換
    block_migration = strutils.bool_from_string(block_migration,
                                                   strict=True)
    disk_over_commit = strutils.bool_from_string(disk_over_commit,
                                                   strict=True)

    """此處省略異常處理
    常見的異常有:找不到合適的目標主機,目標主機上`compute`服務不可用,
    hypervisor不兼容,hypervisor版本太舊,CPU不兼容,雲主機處於鎖定狀
    態,雲主機狀態不對等,可以在`nova-api`的日誌文件中看到具體的異常類型
    及信息

    先從nova.instance數據表獲取id指定的實例信息(返回InstanceV2對
    象),然後調用`nova/compute/api.py/API.live_migrate`執行後續的
    操作,下文具體分析
    """
    instance = common.get_instance(self.compute_api, context, id)
    self.compute_api.live_migrate(context, instance, block_migration,
                                          disk_over_commit, host)

--------------------------------------------------------------
#接上文:`nova/compute/api.py/API.live_migrate`
#省略裝飾器:判斷雲主機是否鎖定,狀態是否正常(Active或者Paused)等
def live_migrate(self, context, instance, block_migration,
                     disk_over_commit, host_name):
    """Migrate a server lively to a new host.
    輸入參數如下:
    context 請求上下文
    instance 實例對象
    block_migration  塊遷移標誌,False
    disk_over_commit False
    host_name 遷移的目標主機,NULL
    """
    #修改雲主機任務狀態:正在遷移
    instance.task_state = task_states.MIGRATING
    instance.save(expected_task_state=[None])

    #在`nova.instance_actions`數據表添加`live-migration`操作記錄
    self._record_action_start(context, instance,
                           instance_actions.LIVE_MIGRATION)
    #將請求轉交給  #`nova/conductor/api.py/ComputeTaskAPI.live_migrate_instance`
    #處理,下文分析
    self.compute_task_api.live_migrate_instance(context,
                 instance,
                 host_name, block_migration=block_migration,
                 disk_over_commit=disk_over_commit)

--------------------------------------------------------------
#接上文:`nova/conductor/api.py/ComputeTaskAPI.live_migrate_instance`
def live_migrate_instance(self, context, instance, host_name,
                              block_migration, 
                              disk_over_commit):
    """輸入參數如下:
    context 請求上下文
    instance 實例對象
    block_migration  塊遷移標誌,False
    disk_over_commit False
    host_name 遷移的目標主機,NULL
    """
    #過濾屬性,`nova-scheduler`選擇目標主機的時候會用到
    scheduler_hint = {'host': host_name}
    """參數:
    True,表示在線遷移;
    False 表示非resize操作
    第一個None 表示不指定配置模板flavor
    第二個None 表示不停機

    發送同步`migrate_server`消息到消息隊列,消費者`nova-conductor`
    會處理該消息
    """
    self.conductor_compute_rpcapi.migrate_server(
            context, instance, scheduler_hint, True, False, 
            None,
            block_migration, disk_over_commit, None)

小結:nova-api的處理比較簡單,先認證權限及轉換輸入參數,之後更新實例任務狀態(migrating)及nova.instance_actions數據庫,最後發起同步rpc請求migrate_server,由nova-conductor執行後續的處理

nova-conductor處理階段

接上文,由nova-conductor服務啓動時建立的路由映射我們知道migrate_server消息的,處理入口如下:

#`nova/conductor/manager.py/ComputeTaskManager.migrate_server
#省略裝飾器定義,添加了try {} except 異常處理,相關的異常信息會返回給`nova-api`
def migrate_server(self, context, instance, scheduler_hint, 
            live, rebuild,
            flavor, block_migration, disk_over_commit, 
            reservations=None,
            clean_shutdown=True):
    """參數說明:
    scheduler_hint 過濾屬性 {'host': host_name}
    live True 在線遷移
    rebuild False 非resize
    flavor None 
    block_migration False,塊遷移
    disk_over_commit False,塊遷移時,計算磁盤空間時使用實際大小還是虛
    擬大小(=True表示使用實際大小,=False表示使用虛擬大小)
    reservations None
    clean_shutdown False 在線遷移,不需要停機
    """

    #省略instance及flavor參數的異常處理
    ......

    #熱遷移,下文具體分析
    if live and not rebuild and not flavor:
        self._live_migrate(context, instance, scheduler_hint,
                               block_migration, 
                               disk_over_commit)
    #離線遷移,在之前的文章中已經分析過
    elif not live and not rebuild and flavor:
        instance_uuid = instance.uuid
        with compute_utils.EventReporter(context, 
                                            'cold_migrate',
                                             instance_uuid):
            self._cold_migrate(context, instance, flavor,
                          scheduler_hint['filter_properties'],
                          reservations, clean_shutdown)
    else:
        raise NotImplementedError()

---------------------------------------------------------------
#接上文:`nova/conductor/manager.py/ComputeTaskManager._live_migrate`
def _live_migrate(self, context, instance, scheduler_hint,
                      block_migration, disk_over_commit):
    #得到熱遷移的目標主機
    destination = scheduler_hint.get("host")

    """定義一個輔助函數:
    1. 更新實例狀態,
    2. 更新`nova.instance_faults`記錄異常堆棧
    3. 發送通知給ceilometer
    """
    def _set_vm_state(context, instance, ex, vm_state=None,
                          task_state=None):
        request_spec = {'instance_properties': {
                'uuid': instance.uuid, },
        }
        scheduler_utils.set_vm_state_and_notify(context,
                instance.uuid,
                'compute_task', 'migrate_server',
                dict(vm_state=vm_state,
                     task_state=task_state,
                   expected_task_state=task_states.MIGRATING,),
                ex, request_spec, self.db)

    #創建一個遷移對象Migration,包含:源端主機,目的主機,實例id,遷移類
    #型,遷移狀態,配置模板id
    migration = objects.Migration(context=context.elevated())
    migration.dest_compute = destination
    migration.status = 'pre-migrating'
    migration.instance_uuid = instance.uuid
    migration.source_compute = instance.host
    migration.migration_type = 'live-migration'
    if instance.obj_attr_is_set('flavor'):
        migration.old_instance_type_id = instance.flavor.id
        migration.new_instance_type_id = instance.flavor.id
    else:
        migration.old_instance_type_id = instance.instance_type_id
        migration.new_instance_type_id = instance.instance_type_id
    #在`nova.migrations`數據表添加一條遷移記錄,初始狀態爲:pre-
    #migrating
    migration.create()
    #生成遷移任務對象LiveMigrationTask
    task = self._build_live_migrate_task(context, instance, 
                                             destination,
                                             block_migration, 
                                             disk_over_commit,
                                             migration)

    """此處省略異常處理
    常見的異常有:找不到合適的目標主機,目標主機上`compute`服務不可用,
    hypervisor不兼容,hypervisor版本太舊,CPU不兼容,雲主機處於鎖定狀
    態,雲主機狀態不對等 - 對於上述異常,會調用上文定義的_set_vm_state輔
    助方法:還原實例狀態,更新`nova.instance_faults`及發送錯誤類型通知
    給ceilometer,同時更新`nova.migrations`遷移狀態爲`error`

    對於其他的未指明異常,會調用上文定義的_set_vm_state輔助方法:設置實
    例狀態爲Error,實例任務狀態爲(`migrating`),更新
    `nova.instance_faults`及發送錯誤類型通知給ceilometer, 同時更新
    `nova.migrations`遷移狀態爲`failed`

    最後會將異常上拋給`nova-api`

    啓動遷移任務,TaskBase.execute -> LiveMigrationTask._execute, 
    下文具體分析
    """
    task.execute()

-------------------------------------------------------------
#接上文:`nova/conductor/tasks/live_migrate.py/LiveMigrationTask._execute`
def _execute(self):
    #判斷實例的狀態是否爲運行或者暫停狀態,
    #否則拋InstanceInvalidState異常
    self._check_instance_is_active()

    #從數據表`nova.services`獲取源端主機上`nova-compute`服務的信息,
    #出錯,拋ComputeServiceUnavailable異常,如果`nova-compute`服務
    #未啓動,也拋ComputeServiceUnavailable異常
    self._check_host_is_up(self.source)

    """如果沒有指定目標主機,則循環通過`nova-scheduler`選擇目標主機
    選定一個主機後需要檢查:
    1.該主機上的hypervisor是否與源主機上的兼容,過程如下:
        1.從`nova.compute_nodes`數據表獲取源端和目的端節點信息,出錯拋
        ComputeHostNotFound異常
        2.比較源端和目的端節點上hypervisor類型,如果不同則拋
        InvalidHypervisorType異常
        3.比較源端和目的端節點上hypervisor的版本,如果源端的比目的端的
        新,則拋DestinationHypervisorTooOld異常

    2.檢測選擇的目標主機是否支持在線遷移,過程如下:
        1. 發送同步`check_can_live_migrate_destination`消息到消息
        隊列,消費者`nova-compute`會處理該消息
        2. 從`nova.compute_nodes`數據表獲取源端和目的端節點信息
        3. 判斷實例所使用的vcpu類型與目標主機的cpu類型是否兼容,如果不兼
        容拋InvalidCPUInfo異常
        4.發送同步`check_can_live_migrate_source`消息到消息隊列,消
        費者`nova-compute`會處理該消息,以便判斷實例的磁盤配置是否支持
        在線遷移,包括兩種情況:
            1. 塊遷移(block_migration=True),滿足下述所有條件:
                1.不能有共享磁盤,不符合拋InvalidLocalStorage異常
                2.目標主機上有足夠的磁盤空間,不足拋
                                    MigrationPreCheckError異常
                3.不能有卷設備,不符合拋MigrationPreCheckError異常
            2. 非塊遷移(block_migration=False),滿足下述條件之一:
                1.從卷啓動並且沒有本地磁盤,
                2.從鏡像啓動並且使用的是共享磁盤
            不符合上述條件,拋InvalidSharedStorage異常

    上述動作如果超時,則拋MigrationPreCheckError異常

    選擇目標主機時,會排除源主機以及前一次選擇的主機,如果超過最大重試次
    數(配置了migrate_max_retries > 0),還沒有得到合適的目標主機,拋
    MaxRetriesExceeded異常,如果所有的主機節點都試過了,還是沒有找到合適
    的目標主機,拋NoInvalidHost異常
    """
    if not self.destination:
        self.destination = self._find_destination()
        #設置遷移模板主機,更新`nova.migrations`數據表
        self.migration.dest_compute = self.destination
        self.migration.save()
    else:
    """指定了目標主機,需要執行如下判斷:
    1.源端主機與目標主機不同,不符合拋UnableToMigrateToSelf異常
    2.目標主機上的`nova-compute`存在且以啓動,不符合拋
                            ComputeServiceUnavailable異常
    3.從`nova.compute_nodes`數據表獲取目標主機信息,並判斷是否內存
    足夠完成該次遷移,不符合拋MigrationPreCheckError異常
    4.與上述沒有指定目標主機情況一樣,判斷目標主機上的hypervisor是否與
    源主機上的兼容,具體分析如上文
    5.與上述沒有指定目標主機情況一樣,判斷目標主機是否支持在線遷移,具體分
    析如上文
    """
        self._check_requested_destination()

    #發送異步`live_migration`到消息隊列,消費者`nova-compute`會處理該
    #消息
    return self.compute_rpcapi.live_migration(
                self.context,
                host=self.source,
                instance=self.instance,
                dest=self.destination,
                block_migration=self.block_migration,
                migration=self.migration,
                migrate_data=self.migrate_data)

小結:nova-conductor的處理過程略顯複雜,與nova-compute的交互比較多,主要是判斷通過nova-scheduler選擇的候選目標主機是否滿足執行在線遷移的條件,另外會在數據表nova.migrations創建一條遷移記錄;在遷移發生異常是也會更新nova.instance_faults數據表,最後發起異步rpc請求,由nova-compute完成後續的遷移操作

nova-compute處理階段

從消息隊列拿到live_migration消息後,nova-compute通過如下方法繼續處理遷移請求:

#`nova/compute/manager.py/ComputeManager.live_migration`
#省略裝飾器定義
def live_migration(self, context, dest, instance, 
                        block_migration,
                        migration, migrate_data):
    """Executing live migration.

    :param context: security context
    :param dest: destination host
    param instance: a nova.objects.instance.Instance object
    :param block_migration: if true, prepare for block 
    migration
    :param migration: an nova.objects.Migration object
    :param migrate_data: implementation specific params

    """

    # NOTE(danms): Remove these guards in v5.0 of the RPC API
    #更新`nova.migrations`記錄,更新遷移狀態爲:queued
    if migration:
        migration.status = 'queued'
        migration.save()

    """線程函數,使用信號量臨界保護,根據配置的
    CONF.max_concurrent_live_migrations(默認1)參數使能節點上併發的
    遷移操作,如果信號量飽和了,就會等待
    """
    def dispatch_live_migration(*args, **kwargs):
        with self._live_migration_semaphore:
            self._do_live_migration(*args, **kwargs)

    """ NOTE(danms): We spawn here to return the RPC worker 
    thread back to the pool. Since what follows could take a 
    really long time, we don't want to tie up RPC workers.
    """
    #創建一個線程執行遷移操作,線程函數爲上文定義的
    #`dispatch_live_migration`,如果成功拿到了信號量,就調用
    `_do_live_migration`繼續執行遷移,否則等待
    utils.spawn_n(dispatch_live_migration,
                      context, dest, instance,
                      block_migration, migration,
                      migrate_data)

小結:nova-compute收到live_migration消息後,更新nova.migrations記錄,然後啓動一個線程來執行熱遷移操作,所以整個遷移操作都是在一個新的線程內完成的;詳細的遷移過程,在一篇博文中分析,敬請期待!

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