2021SC@SDUSC
nova/scheduler
简介
在 openstack 中,scheduler 负责从宿主机(运行 nova-compute 的节点)中根据一系列的算法和参数(CPU 核数,可用 RAM,镜像类型等 )选择出来一个,来部署虚拟机(instance)。 scheduler 主要有两个步骤:过滤(filter) + 权重计算(weighting)。(nova scheduler 原理介绍和源码解析)
本篇主要分析过滤过程。
源码分析
nova/scheduler/filter_scheduler.py
class FilterScheduler(extended from driver.Scheduler)
1. __ init __()准备工作:
获取一个notifier
获取一个placement客户端(用于更新scheduler的客户端,其中封装了一个预备好的用于API通信的keystone授权适配器,一个包含了计算资源提供者和计算资源存量的缓冲,一个记录上一次更新提供者的集合和特征的时间变量,一个能够访问placement服务的HTTP会话,和一个记录report次数的计数器)
def __init__(self, *args, **kwargs):
super(FilterScheduler, self).__init__(*args, **kwargs)
self.notifier = rpc.get_notifier('scheduler')
self.placement_client = report.SchedulerReportClient()
2. select_destinations(…)
发了一个“开始选择目的地”的公告
将公告的动向和阶段切换成“开始选择目的地”
调用_scheduler真正干活
发了一个“结束选择目的地”的公告
将公告的动向和阶段切换成“结束选择目的地”
def select_destinations(self, context, spec_obj, instance_uuids,
alloc_reqs_by_rp_uuid, provider_summaries,
allocation_request_version=None, return_alternates=False):
self.notifier.info(
context, 'scheduler.select_destinations.start',
dict(request_spec=spec_obj.to_legacy_request_spec_dict()))
compute_utils.notify_about_scheduler_action(
context=context, request_spec=spec_obj,
action=fields_obj.NotificationAction.SELECT_DESTINATIONS,
phase=fields_obj.NotificationPhase.START)
host_selections = self._schedule(context, spec_obj, instance_uuids,
alloc_reqs_by_rp_uuid, provider_summaries,
allocation_request_version, return_alternates)
self.notifier.info(
context, 'scheduler.select_destinations.end',
dict(request_spec=spec_obj.to_legacy_request_spec_dict()))
compute_utils.notify_about_scheduler_action(
context=context, request_spec=spec_obj,
action=fields_obj.NotificationAction.SELECT_DESTINATIONS,
phase=fields_obj.NotificationPhase.END)
return host_selections
_scheduler将返回一个选中的对象列表,每个对象包括一个实例及实例对应的目标主机及候选列表。内部逻辑为:
先对上下文进行升权;
elevated = context.elevated()
用升权后的上下文取获取主机状态;
hosts = self._get_all_host_states(elevated, spec_obj,
provider_summaries)
统计instance_uuid(要安置或移动的实例)个数;
num_instances = (len(instance_uuids) if instance_uuids
else spec_obj.num_instances)
如果参数中return_alternates为True,则从CONF中获取最大尝试次数-1存入num_alts,否则该参数设为0。该参数用于指定获取候选host列表的长度。
num_alts = (CONF.scheduler.max_attempts - 1
if return_alternates else 0)
如果instance_uuid为空或USES_ALLOCATION_CANDIDATES为False或alloc_reqs_by_rp_uuid为空,说明使用了一个旧的conductor,因此调用_legacy_find_hosts返回不含候选列表的对象,函数退出。此举为了向前兼容。
if (instance_uuids is None or
not self.USES_ALLOCATION_CANDIDATES or
alloc_reqs_by_rp_uuid is None):
return self._legacy_find_hosts(context, num_instances, spec_obj,
hosts, num_alts,
instance_uuids=instance_uuids)
在instance_uuid不为空情况下,我们用claimed_instance_uuids数组存放成功通过placement API开辟出资源的实例;用claimed_hosts数组存放被选中的主机。
对于instance_uuids中的每一个实例,返回一个根据权重排好序的满足调度限制的HostState对象列表。如果某一个instance找不到响应的host,则跳出循环。如果顺利,对于与本循环轮次中处理的instance对应的host,如果某个host不匹配资源分配请求,则跳过该host;否则,以alloc_reqs_by_rp_uuid[host_uuid] [0]为参数,借助utils.claim_resources分配资源,当出现成功分配的host时,将该host存入claimed_host数组中,并跳出尝试。如果对于某个claimed_host是空的,说明没有一个host能分配资源给这个实例,分配失败,也不再尝试为其它instance分配资源;如果顺利,则将本instance追加进入已被分配资源的instance列表(claimed_instance_uuids)中,并将为本instance分配资源的host追加进入分配资源的主机列表(claimed_hosts)。然后改变本轮中被选中的主机的资源信息(必然是本instance将消耗一部分资源),以备下一轮筛选使用。
然后,检查一下我们是否为所有请求的instance分配了资源(如果未完全完成请求,则报NoValidHost错误)。
最后,获取候选主机列表,并返回该列表。(那之前分配的主机对应关系去哪了?我认为应该是存入全局信息,因此无需返回)
for num, instance_uuid in enumerate(instance_uuids):
spec_obj.obj_reset_changes(['instance_uuid'])
hosts = self._get_sorted_hosts(spec_obj, hosts, num)
if not hosts:
break
claimed_host = None
for host in hosts:
cn_uuid = host.uuid
if cn_uuid not in alloc_reqs_by_rp_uuid:
msg = ("A host state with uuid = '%s' that did not have a "
"matching allocation_request was encountered while "
"scheduling. This host was skipped.")
LOG.debug(msg, cn_uuid)
continue
alloc_reqs = alloc_reqs_by_rp_uuid[cn_uuid]
alloc_req = alloc_reqs[0]
if utils.claim_resources(elevated, self.placement_client,
spec_obj, instance_uuid, alloc_req,
allocation_request_version=allocation_request_version):
claimed_host = host
break
if claimed_host is None:
LOG.debug("Unable to successfully claim against any host.")
break
claimed_instance_uuids.append(instance_uuid)
claimed_hosts.append(claimed_host)
self._consume_selected_host(claimed_host, spec_obj,
instance_uuid=instance_uuid)
self._ensure_sufficient_hosts(context, claimed_hosts, num_instances,
claimed_instance_uuids)
selections_to_return = self._get_alternate_hosts(
claimed_hosts, spec_obj, hosts, num, num_alts,
alloc_reqs_by_rp_uuid, allocation_request_version)
return selections_to_return