您的位置:首页 > 运维架构

Openstack liberty 云主机迁移源码分析之在线迁移1

2016-07-29 10:19 651 查看
这是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
记录,然后启动一个线程来执行热迁移操作,所以整个迁移操作都是在一个新的线程内完成的;详细的迁移过程,在一篇博文中分析,敬请期待!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息