OpenStack Nova-cell服务的源码解析(2)

感谢朋友支持本博客,欢迎共同探讨交流,由于能力和时间有限,错误之处在所难免,欢迎指正!
如果转载,请保留作者信息。
博客地址:http://blog.csdn.net/gaoxingnengjisuan
邮箱地址:dong.liu@siat.ac.cn


在上一篇博客中,我们解析了OpenStack Nova-cell服务的源码结构,在这篇博客中我将以几个例子对nova-cell服务的源码架构和nova-cell服务的具体实现流程进行解析。

1.以示例说明,nova-cell源码结构

/nova/cells/manager.py----class CellsManager(manager.Manager):

def schedule_run_instance(self, ctxt, host_sched_kwargs):
    """
    选择一个合适的cell来建立新的实例,并转发相应的请求;
    """
    # 首先获取本cell的信息;
    our_cell = self.state_manager.get_my_state()
    self.msg_runner.schedule_run_instance(ctxt, our_cell, host_sched_kwargs)
这里调用了/nova/cells/messaging.py----class MessageRunner(object)中的方法schedule_run_instance,类MessageRunner是建立消息和调用方法处理消息的主要接口类。

def schedule_run_instance(self, ctxt, target_cell, host_sched_kwargs):
    """
    这个方法被调度器所调用,通知子cell来调度一个新的实例用于建立;
    """
    method_kwargs = dict(host_sched_kwargs=host_sched_kwargs)
    message = _TargetedMessage(self, ctxt, 'schedule_run_instance',
                               method_kwargs, 'down',
                               target_cell)
    message.process()
类_TargetedMessage是针对于发送目标是特定的cell的情况,它继承自cell通信模块的基类_BaseMessage。在这个方法中调用类_TargetedMessage中的方法process,来实现调用类class _TargetedMessageMethods(_BaseMessageMethods)中的方法schedule_run_instance。

def schedule_run_instance(self, message, host_sched_kwargs):
    """
    父cell通知本cell来调度新的实例用于建立;
    """
    # scheduler是类nova.cells.scheduler.CellsScheduler的实例化对象;
    # 所以这里调用的是类nova.cells.scheduler.CellsScheduler下的run_instance方法;
    self.msg_runner.scheduler.run_instance(message, host_sched_kwargs)

类_TargetedMessageMethods定义了当路由信息到特定的cell上时,需要调用的方法。

这个示例说明了OpenStack Nova-cell服务中类CellsManager、类MessageRunner、类_TargetedMessage和类_TargetedMessageMethods之间的方法调用流程。


再来看一个示例:

/nova/cells/manager.py----class CellsManager(manager.Manager):

def instance_update_at_top(self, ctxt, instance):
    """
    在top等级的cell上,更新实例;
    """
    self.msg_runner.instance_update_at_top(ctxt, instance)

这里调用了/nova/cells/messaging.py----class MessageRunner(object)中的方法instance_update_at_top,类MessageRunner是建立消息和调用方法处理消息的主要接口类。

def instance_update_at_top(self, ctxt, instance):
    """
    在top等级的cell上,更新实例;
    这里实际上执行的是/nova/cell/messaging.py中的类_BroadcastMessageMethods下的方法instance_update_at_top;
    """
    message = _BroadcastMessage(self, ctxt, 'instance_update_at_top',
                                dict(instance=instance), 'up',
                                run_locally=False)
    message.process()
类_BroadcastMessage是针对于发送目标是特定的cell的情况,它继承自cell通信模块的基类_BaseMessage。在这个方法中调用类_BroadcastMessage中的方法process,来实现调用类class _BroadcastMessageMethods(_BaseMessageMethods)中的方法instance_update_at_top。

def instance_update_at_top(self, message, instance, **kwargs):
    """
    如果是top级别的cell,则更新数据库中的实例;
    """
    # 确定是否是API级别cell;
    if not self._at_the_top():
        return
    # 获取实例的uuid值;
    instance_uuid = instance['uuid']

    # 在顶层cell中,删除不能更新的信息;
    items_to_remove = ['id', 'security_groups', 'volumes', 'cell_name',
                       'name', 'metadata']
    # 删除instance中在items_to_remove中标注的各个元素;
    for key in items_to_remove:
        instance.pop(key, None)
    instance['cell_name'] = _reverse_path(message.routing_path)

    # 如果instance字典中有'info_cache'元素,则删除它,并且返回None值;
    info_cache = instance.pop('info_cache', None)
    if info_cache is not None:
        info_cache.pop('id', None)
        info_cache.pop('instance', None)

    if ('system_metadata' in instance and isinstance(instance['system_metadata'], list)):
        sys_metadata = dict([(md['key'], md['value']) 
        for md in instance['system_metadata']])
            instance['system_metadata'] = sys_metadata

    LOG.debug(_("Got update for instance %(instance_uuid)s: "
            "%(instance)s") % locals())

    with utils.temporary_mutation(message.ctxt, read_deleted="yes"):
        try:
            # 在一个实例上设置给定的属性并更新它;
            # 如果实例没有找到,则引发异常;
            # 返回更新信息后的实例;
            self.db.instance_update(message.ctxt, instance_uuid, instance, update_cells=False)
        except exception.NotFound:
            # 根据context和values建立一个新的实例,并记录在数据库中
            # 建立了实例相关的数据表;
            # 补充修正字典values(instance)中的参数信息;
            self.db.instance_create(message.ctxt, instance)
    if info_cache:
        try:
            # 更新数据表中表示实例缓存信息的记录;
            self.db.instance_info_cache_update(message.ctxt,
                    instance_uuid, info_cache, update_cells=False)
        except exception.InstanceInfoCacheNotFound:
            pass

类_BroadcastMessageMethods定义了当以广播的方式路由信息到所有的cell上时,需要调用的方法。

这个示例说明了OpenStack Nova-cell服务中类CellsManager、类MessageRunner、类_BroadcastMessage和类_BroadcastMessageMethods之间的方法调用流程。


2.以示例说明,nova-cell服务的具体实现流程

这里我们来分析一下在nova-cell服务上,如何在树形结构的cell上选取合适的cell,实现建立虚拟机实例,以此来进一步理解nova-cell服务的具体实现流程。

/nova/cells/manager.py----class CellsManager(manager.Manager):

def schedule_run_instance(self, ctxt, host_sched_kwargs):
    """
    选择一个合适的cell来建立新的实例,并转发相应的请求;
    """
    # 首先获取本cell的信息;
    our_cell = self.state_manager.get_my_state()
    self.msg_runner.schedule_run_instance(ctxt, our_cell, host_sched_kwargs)
def schedule_run_instance(self, ctxt, target_cell, host_sched_kwargs):
    """
    这个方法被调度器所调用,通知子cell来调度一个新的实例用于建立;
    """
    method_kwargs = dict(host_sched_kwargs=host_sched_kwargs)
    message = _TargetedMessage(self, ctxt, 'schedule_run_instance',
                               method_kwargs, 'down', target_cell)
    message.process()
对于执行方法schedule_run_instance的cell来讲,这个方法schedule_run_instance实现了父cell通知本cell来调度新的实例用于建立。具体来看方法schedule_run_instance的代码实现:

def schedule_run_instance(self, message, host_sched_kwargs):
    """
    父cell通知本cell来调度新的实例用于建立;
    """
    # scheduler是类nova.cells.scheduler.CellsScheduler的实例化对象;
    # 所以这里调用的是类nova.cells.scheduler.CellsScheduler下的run_instance方法;
    self.msg_runner.scheduler.run_instance(message, host_sched_kwargs)
这里调用了类nova.cells.scheduler.CellsScheduler下的run_instance方法来执行在cell上虚拟机实例的建立操作。具体来看方法run_instance的代码实现:

def run_instance(self, message, host_sched_kwargs):
    """
    选择一个cell,在它上面我们要建立一个新的实例;
    """
    try:
        # scheduler_retries:这个参数定义了当没有找到cell的时候,要重复的次数;
        # 参数的默认值为10;
        # 所以这里应该是进行10次for循环操作;
        for i in xrange(max(0, CONF.cells.scheduler_retries) + 1):
            try:
                return self._run_instance(message, host_sched_kwargs)
            except exception.NoCellsAvailable:
                if i == max(0, CONF.cells.scheduler_retries):
                    raise
                sleep_time = max(1, CONF.cells.scheduler_retry_delay)
                LOG.info(_("No cells available when scheduling.  Will "
                        "retry in %(sleep_time)s second(s)"), locals())
                time.sleep(sleep_time)
                continue
    except Exception:
        request_spec = host_sched_kwargs['request_spec']
        instance_uuids = request_spec['instance_uuids']
        LOG.exception(_("Error scheduling instances %(instance_uuids)s"),
                locals())
        ctxt = message.ctxt
        for instance_uuid in instance_uuids:
            self.msg_runner.instance_update_at_top(ctxt,
                        {'uuid': instance_uuid,
                         'vm_state': vm_states.ERROR})
            try:
                self.db.instance_update(ctxt,
                                        instance_uuid,
                                        {'vm_state': vm_states.ERROR})
            except Exception:
                pass
在这个方法中,若干次尝试调用方法_run_instance来实现虚拟机实例的建立操作。具体来看方法_run_instance的代码实现:

def _run_instance(self, message, host_sched_kwargs):
    """
    尝试调度实例;
    如果没有合适的cell使用,则引发异常;
    """
    ctxt = message.ctxt
    request_spec = host_sched_kwargs['request_spec']

    # 获取所有合适的cell;
    cells = self._get_possible_cells()
    if not cells:
        raise exception.NoCellsAvailable()
    cells = list(cells)

    # 随即选择合适的cell;
    # 这里现在只是随即选取cell,以后会改进的;
    random.shuffle(cells)
    target_cell = cells[0]

    LOG.debug(_("Scheduling with routing_path=%(routing_path)s"),
            locals())

    # 如果合适的cell是本cell,则:
    # 建立实例DB条目作为host调度器;
    # 获取instance_uuids中各个实例的启动信息,并把这些信息加入到要启动的实例属性中;
    # 实现发送要建立实例请求到指定的节点,之后会由指定的计算节点执行建立实例的操作;
    if target_cell.is_me:
        # 需要建立实例DB条目作为host调度器,除非实例已经存在;
        self._create_instances_here(ctxt, request_spec)
        # 获取instance_uuids中各个实例的启动信息;
        # 并把这些信息加入到要启动的实例属性中;
        self._create_action_here(ctxt, request_spec['instance_uuids'])
        # 实现发送要建立实例请求到指定的节点,之后会由指定的计算节点执行建立实例的操作;  
        self.scheduler_rpcapi.run_instance(ctxt, **host_sched_kwargs)
        return
    # schedule_run_instance:选择一个合适的cell来建立新的实例,并转发相应的请求;
    self.msg_runner.schedule_run_instance(ctxt, target_cell, host_sched_kwargs)
在这个方法中,我们来对几条重要的语句进行代码实现的解析工作。

2.1 cells = self._get_possible_cells()

这条语句调用了方法_get_possible_cells实现了获取所有合适的cell的操作(从本cell中的子cell中获取),具体来看方法_get_possible_cells的代码实现:

def _get_possible_cells(self):
    """
    获取所有合适的cell;
    """
    # get_child_cells:获取本cell的子cell信息;
    cells = set(self.state_manager.get_child_cells())
    # get_my_state:获取本cell的信息;
    our_cell = self.state_manager.get_my_state()
    if not cells or our_cell.capacities:
        cells.add(our_cell)
    return cells
2.2 self._create_instances_here(ctxt, request_spec)

这条语句调用了方法_create_instances_here实现了为建立新的实例在数据库中建立新的条目,并对实例进行更新操作。具体来看方法_create_instances_here的代码实现:

def _create_instances_here(self, ctxt, request_spec):
    instance_values = request_spec['instance_properties']
    num_instances = len(request_spec['instance_uuids'])
    # 为建立新的实例在数据库中建立新的条目,并对实例进行更新;
    for i, instance_uuid in enumerate(request_spec['instance_uuids']):
        instance_values['uuid'] = instance_uuid
        # create_db_entry_for_new_instance:为新的实例在数据库中建立新的条目,包括任何更新的表(如安全组等等);
        # 这个方法将会被scheduler调用的,当一个实例的位置确定之后。
        instance = self.compute_api.create_db_entry_for_new_instance(
                    ctxt,
                    request_spec['instance_type'],
                    request_spec['image'],
                    instance_values,
                    request_spec['security_group'],
                    request_spec['block_device_mapping'],
                    num_instances, i)

        # 在top等级的cell上,更新实例;
        self.msg_runner.instance_update_at_top(ctxt, instance)
在这个方法中,我们来看语句:

instance=self.compute_api.create_db_entry_for_new_instance(......)

这条语句调用了方法create_db_entry_for_new_instance实现了为新的实例在数据库中建立新的条目,包括任何更新的表(如安全组等等),这个方法将会被scheduler调用的,当一个实例的位置确定之后。具体来看方法create_db_entry_for_new_instance的代码实现:

def create_db_entry_for_new_instance(self, context, instance_type, image,
            base_options, security_group, block_device_mapping, num_instances,
            index):
    """
    为新的实例在数据库中建立新的条目,包括任何更新的表(如安全组等等);
    这个方法将会被scheduler调用的,当一个实例的位置确定之后。
    """
        
    #_populate_instance_for_create:建立一个新的实例的开端;
    #也就是首先执行instance = base_options;
    #然后再补充一些实例的相关信息到instance这个字典中;
    #返回设置好信息的实例字典给;                         
    #期间还进行了一些操作,包括:
    #存储image镜像的属性信息,以便后面我们能够用到它们;
    #对目前这个image镜像实例的来源,即那个基础镜像的记录信息进行保存;          
    instance = self._populate_instance_for_create(base_options,image, security_group)

    #确定实例的显示名称和主机名称(display_name和hostname);
    self._populate_instance_names(instance, num_instances)

    #确定实例的关机和终止状态信息(shutdown_terminate);
    #如果块设备映射(block_device_mapping)为真或者是镜像属性信息中的映射属性(image_properties.get('mappings'))为真
    #或者是镜像属性信息中的块设备映射属性(image_properties.get('block_device_mapping'))为真的时候,
    #实例的关机和终止状态instance['shutdown_terminate']就为假(是不是就是表示实例系统正在创建或者是运行中);
    self._populate_instance_shutdown_terminate(instance, image,block_device_mapping)

    # ensure_default安全组在实例建立前会被调用;
    # ensure_default:确保context有一个安全组,如果没有就建立一个;
    self.security_group_api.ensure_default(context)
        
    #根据context和values建立一个新的实例,并记录在数据库中
    #建立了实例相关的数据表;
    #补充修正字典instance中的参数信息;
    instance = self.db.instance_create(context, instance)

    # 如果要建立实例的最大数目大于1;
    # 这里涉及到一个一个请求生成多个实例的情况,这种情况下实例的命名相关遵循名称模板来进行;
    # 当一个请求创建多个实例的时候,multi_instance_display_name_template这个参数的设置能够使所有的实例都具有唯一的主机名和DISPLAY_NAME;
    # 默认的格式是'%(name)s-%(uuid)s';
    # 我的理解是uuid应该是不同的;
    # 根据名称模板生成实例的相关名称属性,并更新实例的这个属性;
    if num_instances > 1:
        instance = self._apply_instance_name_template(context, instance, index)

    # 确定实例块设备映射信息;
    self._populate_instance_for_bdm(context, instance, instance_type, image, block_device_mapping)

    return instance
具体来看方法中的语句instance= self.db.instance_create(context,instance),实现了根据context和values建立一个新的实例,并记录在数据库中,建立了实例相关的数据表,补充修正字典instance中的参数信息。具体来看方法instance_create的代码实现:

def instance_create(context, values):
    """
    根据values这个字典建立一个实例;
    """
    return IMPL.instance_create(context, values)
def instance_create(context, values):
    """
    根据context和values建立一个新的实例,并记录在数据库中
    建立了实例相关的数据表;
    补充修正字典values(instance)中的参数信息;
    
    注:在/nova/compute/api.py的create_db_entry_for_new_instance方法中,是这样调用这个方法的:
    instance_create(context, instance)
    所以说这个values传进来的是字典instance;
    """
    
    # 补充修正字典values(instance)中的参数信息;
    # (???)为什么拷贝,拷贝过后也都是指向同一个对象;
    values = values.copy()
    
    # 获取values(instance)的'metadata';
    # _metadata_refs返回的是一个列表,作为values['metadata']的key-values中的values;
    # 列表中每一个值都是一个models.InstanceMetadata建立的数据结构;
    # _metadata_refs把values.get('metadata')这个字典中的key-values对对应的赋值到了models.InstanceMetadata所建立的数据结构中的字段key和字段values中;
    # 每一个新建数据结构都是列表(字典values(instance)中'metadata'所对应的的键值是一个列表)中的一个元素;
    # 目前values(instance)的数据结构是:本身是字典,字典的每个键值是列表,列表中的每个元素是models.InstanceMetadata所建立的数据表;
    values['metadata'] = _metadata_refs(values.get('metadata'), models.InstanceMetadata)
    
    # 获取values(instance)的'system_metadata';
    values['system_metadata'] = _metadata_refs(values.get('system_metadata'), models.InstanceSystemMetadata)

    # Instance:表示一个来宾VM的类;
    # 构建数据表instances的数据结构;
    instance_ref = models.Instance()
    
    # 如果values(instance)的uuid值没有定义,则随机生成一个uuid值;
    if not values.get('uuid'):
        values['uuid'] = str(uuid.uuid4())
        
    # 从InstanceInfoCache类中获取实例的缓存信息;
    # InstanceInfoCache:表示一个实例的缓存信息的类;
    # 构建数据表instance_info_caches的数据结构;
    instance_ref['info_cache'] = models.InstanceInfoCache()
    info_cache = values.pop('info_cache', None)
    if info_cache is not None:
        instance_ref['info_cache'].update(info_cache)
    
    # 获取安全组的相关信息,以字典的形式存储;
    security_groups = values.pop('security_groups', [])
    # 注:这里是否应该是instance_ref.update(security_groups)
    instance_ref.update(values)
2.3 self._create_action_here(ctxt, request_spec['instance_uuids'])

这条语句调用方法_create_action_here实现了获取instance_uuids中各个实例的启动信息,并把这些信息加入到要启动的实例属性中。具体来看方法_create_action_here的代码实现:

def _create_action_here(self, ctxt, instance_uuids):
    """
    获取instance_uuids中各个实例的启动信息;
    并把这些信息加入到要启动的实例属性中;
    """
    for instance_uuid in instance_uuids:
        # 获取实例启动的一些信息,并以字典的形式返回;
        action = compute_utils.pack_action_start(ctxt, instance_uuid,
                 instance_actions.CREATE)
        # 获取要启动的实例action的一些相关信息;
        self.db.action_start(ctxt, action)
2.4 self.scheduler_rpcapi.run_instance(ctxt, **host_sched_kwargs)

这条语句调用方法run_instance实现了发送要建立实例的请求信息到消息队列,再由合适的节点从消息队列中取出请求信息,在本地执行建立虚拟机实例的操作。具体来看方法run_instance的代码解析:

def run_instance(self, ctxt, request_spec, admin_password,
            injected_files, requested_networks, is_first_time,
            filter_properties):
    # 实现了发送要运行实例('run_instance')的请求消息到远程节点;
    # make_msg:封装所要运行实例的这个请求的所有信息到make_msg;
    # 调用这个远程方法cast,但是不返回任何信息;
    # 发送一个主题的消息make_msg,不用等待任何信息的返回;
    #注:远程节点会在队列中提取这条消息,然后调用相应资源,实现运行实例的这个请求,这部分代码只是实现了请求信息的发送;
    return self.cast(ctxt, self.make_msg('run_instance',
            request_spec=request_spec, admin_password=admin_password,
            injected_files=injected_files,
            requested_networks=requested_networks,
            is_first_time=is_first_time,
            filter_properties=filter_properties))
以前的博客中,我们对建立虚拟机实例的相关代码进行过解析,这里就不对后面的代码进行解析了。

2.5 self.msg_runner.schedule_run_instance(ctxt, target_cell, host_sched_kwargs)

我们回到方法_run_instance中,我们可以看到,如果要建立虚拟机实例的目标cell不是本cell,则在方法的最后再一次调用了方法schedule_run_instance,实现继续在本cell的子cell中进行建立虚拟机的操作(这里就是应用cell的树形结构)。

到此为止,就完成了在树形结构的cell服务中进行虚拟机建立的代码解析工作。


好啦,现在我们完成了以示例对nova-cell服务的源码架构和nova-cell服务的具体实现流程进行解析的工作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值