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记录,然后启动一个线程来执行热迁移操作,所以整个迁移操作都是在一个新的线程内完成的;详细的迁移过程,在一篇博文中分析,敬请期待!

猜你喜欢

转载自blog.csdn.net/lzw06061139/article/details/52044003