感谢朋友支持本博客,欢迎共同探讨交流,由于能力和时间有限,错误之处在所难免,欢迎指正!
如果转载,请保留作者信息。
博客地址: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/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服务的具体实现流程进行解析的工作。