這是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
記錄,然後啓動一個線程來執行熱遷移操作,所以整個遷移操作都是在一個新的線程內完成的;詳細的遷移過程,在一篇博文中分析,敬請期待!