问题导读
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):
- 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服务的具体实现流程进行解析的工作。
|