分享

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

tntzbzc 发表于 2014-11-18 18:04:09 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 0 17831
问题导读
1.nova-cell服务的具体实现包含哪些流程?
2.哪个类定义了当路由信息到特定的cell上时,需要调用的方法?
3.schedule_run_instance实现了什么?









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


1.以示例说明,nova-cell源码结构
/nova/cells/manager.py----class CellsManager(manager.Manager):
  1. def schedule_run_instance(self, ctxt, host_sched_kwargs):  
  2.     """
  3.     选择一个合适的cell来建立新的实例,并转发相应的请求;
  4.     """  
  5.     # 首先获取本cell的信息;  
  6.     our_cell = self.state_manager.get_my_state()  
  7.     self.msg_runner.schedule_run_instance(ctxt, our_cell, host_sched_kwargs)  
复制代码



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

  1. def schedule_run_instance(self, ctxt, target_cell, host_sched_kwargs):  
  2.     """
  3.     这个方法被调度器所调用,通知子cell来调度一个新的实例用于建立;
  4.     """  
  5.     method_kwargs = dict(host_sched_kwargs=host_sched_kwargs)  
  6.     message = _TargetedMessage(self, ctxt, 'schedule_run_instance',  
  7.                                method_kwargs, 'down',  
  8.                                target_cell)  
  9.     message.process()  
复制代码





类_TargetedMessage是针对于发送目标是特定的cell的情况,它继承自cell通信模块的基类_BaseMessage。在这个方法中调用类_TargetedMessage中的方法process,来实现调用类class _TargetedMessageMethods(_BaseMessageMethods)中的方法schedule_run_instance。
  1. def schedule_run_instance(self, message, host_sched_kwargs):  
  2.     """
  3.     父cell通知本cell来调度新的实例用于建立;
  4.     """  
  5.     # scheduler是类nova.cells.scheduler.CellsScheduler的实例化对象;  
  6.     # 所以这里调用的是类nova.cells.scheduler.CellsScheduler下的run_instance方法;  
  7.     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):
  1. def instance_update_at_top(self, ctxt, instance):  
  2.     """
  3.     在top等级的cell上,更新实例;
  4.     """  
  5.     self.msg_runner.instance_update_at_top(ctxt, instance)  
复制代码






这里调用了/nova/cells/messaging.py----class MessageRunner(object)中的方法instance_update_at_top,类MessageRunner是建立消息和调用方法处理消息的主要接口类。
  1. def instance_update_at_top(self, ctxt, instance):  
  2.     """
  3.     在top等级的cell上,更新实例;
  4.     这里实际上执行的是/nova/cell/messaging.py中的类_BroadcastMessageMethods下的方法instance_update_at_top;
  5.     """  
  6.     message = _BroadcastMessage(self, ctxt, 'instance_update_at_top',  
  7.                                 dict(instance=instance), 'up',  
  8.                                 run_locally=False)  
  9.     message.process()  
复制代码




类_BroadcastMessage是针对于发送目标是特定的cell的情况,它继承自cell通信模块的基类_BaseMessage。在这个方法中调用类_BroadcastMessage中的方法process,来实现调用类class _BroadcastMessageMethods(_BaseMessageMethods)中的方法instance_update_at_top。


  1. def instance_update_at_top(self, message, instance, **kwargs):  
  2.     """
  3.     如果是top级别的cell,则更新数据库中的实例;
  4.     """  
  5.     # 确定是否是API级别cell;  
  6.     if not self._at_the_top():  
  7.         return  
  8.     # 获取实例的uuid值;  
  9.     instance_uuid = instance['uuid']  
  10.   
  11.     # 在顶层cell中,删除不能更新的信息;  
  12.     items_to_remove = ['id', 'security_groups', 'volumes', 'cell_name',  
  13.                        'name', 'metadata']  
  14.     # 删除instance中在items_to_remove中标注的各个元素;  
  15.     for key in items_to_remove:  
  16.         instance.pop(key, None)  
  17.     instance['cell_name'] = _reverse_path(message.routing_path)  
  18.   
  19.     # 如果instance字典中有'info_cache'元素,则删除它,并且返回None值;  
  20.     info_cache = instance.pop('info_cache', None)  
  21.     if info_cache is not None:  
  22.         info_cache.pop('id', None)  
  23.         info_cache.pop('instance', None)  
  24.   
  25.     if ('system_metadata' in instance and isinstance(instance['system_metadata'], list)):  
  26.         sys_metadata = dict([(md['key'], md['value'])   
  27.         for md in instance['system_metadata']])  
  28.             instance['system_metadata'] = sys_metadata  
  29.   
  30.     LOG.debug(_("Got update for instance %(instance_uuid)s: "  
  31.             "%(instance)s") % locals())  
  32.   
  33.     with utils.temporary_mutation(message.ctxt, read_deleted="yes"):  
  34.         try:  
  35.             # 在一个实例上设置给定的属性并更新它;  
  36.             # 如果实例没有找到,则引发异常;  
  37.             # 返回更新信息后的实例;  
  38.             self.db.instance_update(message.ctxt, instance_uuid, instance, update_cells=False)  
  39.         except exception.NotFound:  
  40.             # 根据context和values建立一个新的实例,并记录在数据库中  
  41.             # 建立了实例相关的数据表;  
  42.             # 补充修正字典values(instance)中的参数信息;  
  43.             self.db.instance_create(message.ctxt, instance)  
  44.     if info_cache:  
  45.         try:  
  46.             # 更新数据表中表示实例缓存信息的记录;  
  47.             self.db.instance_info_cache_update(message.ctxt,  
  48.                     instance_uuid, info_cache, update_cells=False)  
  49.         except exception.InstanceInfoCacheNotFound:  
  50.             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):

  1. def schedule_run_instance(self, ctxt, host_sched_kwargs):  
  2.     """
  3.     选择一个合适的cell来建立新的实例,并转发相应的请求;
  4.     """  
  5.     # 首先获取本cell的信息;  
  6.     our_cell = self.state_manager.get_my_state()  
  7.     self.msg_runner.schedule_run_instance(ctxt, our_cell, host_sched_kwargs)  
复制代码



  1. def schedule_run_instance(self, ctxt, target_cell, host_sched_kwargs):  
  2.     """
  3.     这个方法被调度器所调用,通知子cell来调度一个新的实例用于建立;
  4.     """  
  5.     method_kwargs = dict(host_sched_kwargs=host_sched_kwargs)  
  6.     message = _TargetedMessage(self, ctxt, 'schedule_run_instance',  
  7.                                method_kwargs, 'down', target_cell)  
  8.     message.process()  
复制代码






对于执行方法schedule_run_instance的cell来讲,这个方法schedule_run_instance实现了父cell通知本cell来调度新的实例用于建立。具体来看方法schedule_run_instance的代码实现:


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





这里调用了类nova.cells.scheduler.CellsScheduler下的run_instance方法来执行在cell上虚拟机实例的建立操作。具体来看方法run_instance的代码实现:
  1. def run_instance(self, message, host_sched_kwargs):  
  2.     """
  3.     选择一个cell,在它上面我们要建立一个新的实例;
  4.     """  
  5.     try:  
  6.         # scheduler_retries:这个参数定义了当没有找到cell的时候,要重复的次数;  
  7.         # 参数的默认值为10;  
  8.         # 所以这里应该是进行10次for循环操作;  
  9.         for i in xrange(max(0, CONF.cells.scheduler_retries) + 1):  
  10.             try:  
  11.                 return self._run_instance(message, host_sched_kwargs)  
  12.             except exception.NoCellsAvailable:  
  13.                 if i == max(0, CONF.cells.scheduler_retries):  
  14.                     raise  
  15.                 sleep_time = max(1, CONF.cells.scheduler_retry_delay)  
  16.                 LOG.info(_("No cells available when scheduling.  Will "  
  17.                         "retry in %(sleep_time)s second(s)"), locals())  
  18.                 time.sleep(sleep_time)  
  19.                 continue  
  20.     except Exception:  
  21.         request_spec = host_sched_kwargs['request_spec']  
  22.         instance_uuids = request_spec['instance_uuids']  
  23.         LOG.exception(_("Error scheduling instances %(instance_uuids)s"),  
  24.                 locals())  
  25.         ctxt = message.ctxt  
  26.         for instance_uuid in instance_uuids:  
  27.             self.msg_runner.instance_update_at_top(ctxt,  
  28.                         {'uuid': instance_uuid,  
  29.                          'vm_state': vm_states.ERROR})  
  30.             try:  
  31.                 self.db.instance_update(ctxt,  
  32.                                         instance_uuid,  
  33.                                         {'vm_state': vm_states.ERROR})  
  34.             except Exception:  
  35.                 pass  
复制代码















在这个方法中,若干次尝试调用方法_run_instance来实现虚拟机实例的建立操作。具体来看方法_run_instance的代码实现:
  1. def _run_instance(self, message, host_sched_kwargs):  
  2.     """
  3.     尝试调度实例;
  4.     如果没有合适的cell使用,则引发异常;
  5.     """  
  6.     ctxt = message.ctxt  
  7.     request_spec = host_sched_kwargs['request_spec']  
  8.   
  9.     # 获取所有合适的cell;  
  10.     cells = self._get_possible_cells()  
  11.     if not cells:  
  12.         raise exception.NoCellsAvailable()  
  13.     cells = list(cells)  
  14.   
  15.     # 随即选择合适的cell;  
  16.     # 这里现在只是随即选取cell,以后会改进的;  
  17.     random.shuffle(cells)  
  18.     target_cell = cells[0]  
  19.   
  20.     LOG.debug(_("Scheduling with routing_path=%(routing_path)s"),  
  21.             locals())  
  22.   
  23.     # 如果合适的cell是本cell,则:  
  24.     # 建立实例DB条目作为host调度器;  
  25.     # 获取instance_uuids中各个实例的启动信息,并把这些信息加入到要启动的实例属性中;  
  26.     # 实现发送要建立实例请求到指定的节点,之后会由指定的计算节点执行建立实例的操作;  
  27.     if target_cell.is_me:  
  28.         # 需要建立实例DB条目作为host调度器,除非实例已经存在;  
  29.         self._create_instances_here(ctxt, request_spec)  
  30.         # 获取instance_uuids中各个实例的启动信息;  
  31.         # 并把这些信息加入到要启动的实例属性中;  
  32.         self._create_action_here(ctxt, request_spec['instance_uuids'])  
  33.         # 实现发送要建立实例请求到指定的节点,之后会由指定的计算节点执行建立实例的操作;   
  34.         self.scheduler_rpcapi.run_instance(ctxt, **host_sched_kwargs)  
  35.         return  
  36.     # schedule_run_instance:选择一个合适的cell来建立新的实例,并转发相应的请求;  
  37.     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的代码实现:
  1. def _get_possible_cells(self):  
  2.     """
  3.     获取所有合适的cell;
  4.     """  
  5.     # get_child_cells:获取本cell的子cell信息;  
  6.     cells = set(self.state_manager.get_child_cells())  
  7.     # get_my_state:获取本cell的信息;  
  8.     our_cell = self.state_manager.get_my_state()  
  9.     if not cells or our_cell.capacities:  
  10.         cells.add(our_cell)  
  11.     return cells  
复制代码



2.2 self._create_instances_here(ctxt, request_spec)


这条语句调用了方法_create_instances_here实现了为建立新的实例在数据库中建立新的条目,并对实例进行更新操作。具体来看方法_create_instances_here的代码实现:
  1. def _create_instances_here(self, ctxt, request_spec):  
  2.     instance_values = request_spec['instance_properties']  
  3.     num_instances = len(request_spec['instance_uuids'])  
  4.     # 为建立新的实例在数据库中建立新的条目,并对实例进行更新;  
  5.     for i, instance_uuid in enumerate(request_spec['instance_uuids']):  
  6.         instance_values['uuid'] = instance_uuid  
  7.         # create_db_entry_for_new_instance:为新的实例在数据库中建立新的条目,包括任何更新的表(如安全组等等);  
  8.         # 这个方法将会被scheduler调用的,当一个实例的位置确定之后。  
  9.         instance = self.compute_api.create_db_entry_for_new_instance(  
  10.                     ctxt,  
  11.                     request_spec['instance_type'],  
  12.                     request_spec['image'],  
  13.                     instance_values,  
  14.                     request_spec['security_group'],  
  15.                     request_spec['block_device_mapping'],  
  16.                     num_instances, i)  
  17.   
  18.         # 在top等级的cell上,更新实例;  
  19.         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的代码实现:

  1. def create_db_entry_for_new_instance(self, context, instance_type, image,  
  2.             base_options, security_group, block_device_mapping, num_instances,  
  3.             index):  
  4.     """
  5.     为新的实例在数据库中建立新的条目,包括任何更新的表(如安全组等等);
  6.     这个方法将会被scheduler调用的,当一个实例的位置确定之后。
  7.     """  
  8.          
  9.     #_populate_instance_for_create:建立一个新的实例的开端;  
  10.     #也就是首先执行instance = base_options;  
  11.     #然后再补充一些实例的相关信息到instance这个字典中;  
  12.     #返回设置好信息的实例字典给;                           
  13.     #期间还进行了一些操作,包括:  
  14.     #存储image镜像的属性信息,以便后面我们能够用到它们;  
  15.     #对目前这个image镜像实例的来源,即那个基础镜像的记录信息进行保存;            
  16.     instance = self._populate_instance_for_create(base_options,image, security_group)  
  17.   
  18.     #确定实例的显示名称和主机名称(display_name和hostname);  
  19.     self._populate_instance_names(instance, num_instances)  
  20.   
  21.     #确定实例的关机和终止状态信息(shutdown_terminate);  
  22.     #如果块设备映射(block_device_mapping)为真或者是镜像属性信息中的映射属性(image_properties.get('mappings'))为真  
  23.     #或者是镜像属性信息中的块设备映射属性(image_properties.get('block_device_mapping'))为真的时候,  
  24.     #实例的关机和终止状态instance['shutdown_terminate']就为假(是不是就是表示实例系统正在创建或者是运行中);  
  25.     self._populate_instance_shutdown_terminate(instance, image,block_device_mapping)  
  26.   
  27.     # ensure_default安全组在实例建立前会被调用;  
  28.     # ensure_default:确保context有一个安全组,如果没有就建立一个;  
  29.     self.security_group_api.ensure_default(context)  
  30.          
  31.     #根据context和values建立一个新的实例,并记录在数据库中  
  32.     #建立了实例相关的数据表;  
  33.     #补充修正字典instance中的参数信息;  
  34.     instance = self.db.instance_create(context, instance)  
  35.   
  36.     # 如果要建立实例的最大数目大于1;  
  37.     # 这里涉及到一个一个请求生成多个实例的情况,这种情况下实例的命名相关遵循名称模板来进行;  
  38.     # 当一个请求创建多个实例的时候,multi_instance_display_name_template这个参数的设置能够使所有的实例都具有唯一的主机名和DISPLAY_NAME;  
  39.     # 默认的格式是'%(name)s-%(uuid)s';  
  40.     # 我的理解是uuid应该是不同的;  
  41.     # 根据名称模板生成实例的相关名称属性,并更新实例的这个属性;  
  42.     if num_instances > 1:  
  43.         instance = self._apply_instance_name_template(context, instance, index)  
  44.   
  45.     # 确定实例块设备映射信息;  
  46.     self._populate_instance_for_bdm(context, instance, instance_type, image, block_device_mapping)  
  47.   
  48.     return instance  
复制代码


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

  1. def instance_create(context, values):  
  2.     """
  3.     根据values这个字典建立一个实例;
  4.     """  
  5.     return IMPL.instance_create(context, values)  
复制代码


  1. def instance_create(context, values):  
  2.     """
  3.     根据context和values建立一个新的实例,并记录在数据库中
  4.     建立了实例相关的数据表;
  5.     补充修正字典values(instance)中的参数信息;
  6.      
  7.     注:在/nova/compute/api.py的create_db_entry_for_new_instance方法中,是这样调用这个方法的:
  8.     instance_create(context, instance)
  9.     所以说这个values传进来的是字典instance;
  10.     """  
  11.       
  12.     # 补充修正字典values(instance)中的参数信息;  
  13.     # (???)为什么拷贝,拷贝过后也都是指向同一个对象;  
  14.     values = values.copy()  
  15.       
  16.     # 获取values(instance)的'metadata';  
  17.     # _metadata_refs返回的是一个列表,作为values['metadata']的key-values中的values;  
  18.     # 列表中每一个值都是一个models.InstanceMetadata建立的数据结构;  
  19.     # _metadata_refs把values.get('metadata')这个字典中的key-values对对应的赋值到了models.InstanceMetadata所建立的数据结构中的字段key和字段values中;  
  20.     # 每一个新建数据结构都是列表(字典values(instance)中'metadata'所对应的的键值是一个列表)中的一个元素;  
  21.     # 目前values(instance)的数据结构是:本身是字典,字典的每个键值是列表,列表中的每个元素是models.InstanceMetadata所建立的数据表;  
  22.     values['metadata'] = _metadata_refs(values.get('metadata'), models.InstanceMetadata)  
  23.       
  24.     # 获取values(instance)的'system_metadata';  
  25.     values['system_metadata'] = _metadata_refs(values.get('system_metadata'), models.InstanceSystemMetadata)  
  26.   
  27.     # Instance:表示一个来宾VM的类;  
  28.     # 构建数据表instances的数据结构;  
  29.     instance_ref = models.Instance()  
  30.       
  31.     # 如果values(instance)的uuid值没有定义,则随机生成一个uuid值;  
  32.     if not values.get('uuid'):  
  33.         values['uuid'] = str(uuid.uuid4())  
  34.          
  35.     # 从InstanceInfoCache类中获取实例的缓存信息;  
  36.     # InstanceInfoCache:表示一个实例的缓存信息的类;  
  37.     # 构建数据表instance_info_caches的数据结构;  
  38.     instance_ref['info_cache'] = models.InstanceInfoCache()  
  39.     info_cache = values.pop('info_cache', None)  
  40.     if info_cache is not None:  
  41.         instance_ref['info_cache'].update(info_cache)  
  42.       
  43.     # 获取安全组的相关信息,以字典的形式存储;  
  44.     security_groups = values.pop('security_groups', [])  
  45.     # 注:这里是否应该是instance_ref.update(security_groups)  
  46.     instance_ref.update(values)  
复制代码


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

  1. def _create_action_here(self, ctxt, instance_uuids):  
  2.     """
  3.     获取instance_uuids中各个实例的启动信息;
  4.     并把这些信息加入到要启动的实例属性中;
  5.     """  
  6.     for instance_uuid in instance_uuids:  
  7.         # 获取实例启动的一些信息,并以字典的形式返回;  
  8.         action = compute_utils.pack_action_start(ctxt, instance_uuid,  
  9.                  instance_actions.CREATE)  
  10.         # 获取要启动的实例action的一些相关信息;  
  11.         self.db.action_start(ctxt, action)  
复制代码



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

  1. def run_instance(self, ctxt, request_spec, admin_password,  
  2.             injected_files, requested_networks, is_first_time,  
  3.             filter_properties):  
  4.     # 实现了发送要运行实例('run_instance')的请求消息到远程节点;  
  5.     # make_msg:封装所要运行实例的这个请求的所有信息到make_msg;  
  6.     # 调用这个远程方法cast,但是不返回任何信息;  
  7.     # 发送一个主题的消息make_msg,不用等待任何信息的返回;  
  8.     #注:远程节点会在队列中提取这条消息,然后调用相应资源,实现运行实例的这个请求,这部分代码只是实现了请求信息的发送;  
  9.     return self.cast(ctxt, self.make_msg('run_instance',  
  10.             request_spec=request_spec, admin_password=admin_password,  
  11.             injected_files=injected_files,  
  12.             requested_networks=requested_networks,  
  13.             is_first_time=is_first_time,  
  14.             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服务的具体实现流程进行解析的工作。








没找到任何评论,期待你打破沉寂

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条