引言
当我们在使用那些建设在OpenStack
之上的云平台服务的时候,往往在概览页面都有一个明显的位置用来展示当前集群的一些资源使用情况,如,CPU,内存,硬盘等资源的总量、使用量、剩余量。而且,每当我们拓展集群规模之后,概览页面上的资源总量也会自动增加,我们都熟知,OpenStack
中的Nova
服务负责管理这些计算资源,那么你有没有想过,它们是如何被Nova
服务获取的吗?
Nova
如何统计资源
我们知道,统计资源的操作属于Nova
服务内部的机制,考虑到资源统计结果对后续操作(如创建虚拟机,创建硬盘)的重要性,我们推断该机制的运行顺序一定先于其他服务。
通过上述简单的分析,再加上一些必要的Debug操作,我们得出:
该机制的触发点位于nova.service.WSGIService.start
方法中:
def start(self):
"""Start serving this service using loaded configuration.
Also, retrieve updated port number in case '0' was passed in, which
indicates a random port should be used.
:returns: None
"""
if self.manager:
self.manager.init_host()
self.manager.pre_start_hook()
if self.backdoor_port is not None:
self.manager.backdoor_port = self.backdoor_port
self.server.start()
if self.manager:
self.manager.post_start_hook()
其中,self.manager.pre_start_hook()
的作用就是去获取资源信息,它的直接调用为nova.compute.manager.pre_start_hook
如下:
def pre_start_hook(self):
"""After the service is initialized, but before we fully bring
the service up by listening on RPC queues, make sure to update
our available resources (and indirectly our available nodes).
"""
self.update_available_resource(nova.context.get_admin_context())
...
@periodic_task.periodic_task
def update_available_resource(self, context):
"""See driver.get_available_resource()
Periodic process that keeps that the compute host's understanding of
resource availability and usage in sync with the underlying hypervisor.
:param context: security context
"""
new_resource_tracker_dict = {}
nodenames = set(self.driver.get_available_nodes())
for nodename in nodenames:
rt = self._get_resource_tracker(nodename)
rt.update_available_resource(context)
new_resource_tracker_dict[nodename] = rt
# Delete orphan compute node not reported by driver but still in db
compute_nodes_in_db = self._get_compute_nodes_in_db(context,
use_slave=True)
for cn in compute_nodes_in_db:
if cn.hypervisor_hostname not in nodenames:
LOG.audit(_("Deleting orphan compute node %s") % cn.id)
cn.destroy()
self._resource_tracker_dict = new_resource_tracker_dict
上述代码中的rt.update_available_resource()
的直接调用实为nova.compute.resource_tracker.update_available_resource()
如下:
def update_available_resource(self, context):
"""Override in-memory calculations of compute node resource usage based
on data audited from the hypervisor layer.
Add in resource claims in progress to account for operations that have
declared a need for resources, but not necessarily retrieved them from
the hypervisor layer yet.
"""
LOG.audit(_("Auditing locally available compute resources"))
resources = self.driver.get_available_resource(self.nodename)
if not resources:
# The virt driver does not support this function
LOG.audit(_("Virt driver does not support "
"'get_available_resource' Compute tracking is disabled."))
self.compute_node = None
return
resources['host_ip'] = CONF.my_ip
# TODO(berrange): remove this once all virt drivers are updated
# to report topology
if "numa_topology" not in resources:
resources["numa_topology"] = None
self._verify_resources(resources)
self._report_hypervisor_resource_view(resources)
return self._update_available_resource(context, resources)
上述代码中的self._update_available_resource
的作用是根据计算节点上的资源实际使用结果来同步数据库记录,这里我们不做展开;self.driver.get_available_resource()
的作用就是获取节点硬件资源信息,它的实际调用为:
class LibvirtDriver(driver.ComputeDriver):
def get_available_resource(self, nodename):
"""Retrieve resource information.
This method is called when nova-compute launches, and
as part of a periodic task that records the results in the DB.
:param nodename: will be put in PCI device
:returns: dictionary containing resource info
"""
# Temporary: convert supported_instances into a string, while keeping
# the RPC version as JSON. Can be changed when RPC broadcast is removed
stats = self.get_host_stats(refresh=True)
stats['supported_instances'] = jsonutils.dumps(
stats['supported_instances'])
return stats
def get_host_stats(self, refresh=False):
"""Return the current state of the host.
If 'refresh' is True, run update the stats first.
"""
return self.host_state.get_host_stats(refresh=refresh)
def _get_vcpu_total(self):
"""Get available vcpu number of physical computer.
:returns: the number of cpu core instances can be used.
"""
if self._vcpu_total != 0:
return self._vcpu_total
try:
total_pcpus = self._conn.getInfo()[2] + 1
except libvirt.libvirtError:
LOG.warn(_LW("Cannot get the number of cpu, because this "
"function is not implemented for this platform. "))
return 0
if CONF.vcpu_pin_set is None:
self._vcpu_total = total_pcpus
return self._vcpu_total
available_ids = hardware.get_vcpu_pin_set()
if sorted(available_ids)[-1] >= total_pcpus:
raise exception.Invalid(_("Invalid vcpu_pin_set config, "
"out of hypervisor cpu range."))
self._vcpu_total = len(available_ids)
return self._vcpu_total
.....
class HostState(object):
"""Manages information about the compute node through libvirt."""
def __init__(self, driver):
super(HostState, self).__init__()
self._stats = {}
self.driver = driver
self.update_status()
def get_host_stats(self, refresh=False):
"""Return the current state of the host.
If 'refresh' is True, run update the stats first.
"""
if refresh or not self._stats:
self.update_status()
return self._stats
def update_status(self):
"""Retrieve status info from libvirt."""
...
data["vcpus"] = self.driver._get_vcpu_total()
data["memory_mb"] = self.driver._get_memory_mb_total()
data["local_gb"] = disk_info_dict['total']
data["vcpus_used"] = self.driver._get_vcpu_used()
data["memory_mb_used"] = self.driver._get_memory_mb_used()
data["local_gb_used"] = disk_info_dict['used']
data["hypervisor_type"] = self.driver._get_hypervisor_type()
data["hypervisor_version"] = self.driver._get_hypervisor_version()
data["hypervisor_hostname"] = self.driver._get_hypervisor_hostname()
data["cpu_info"] = self.driver._get_cpu_info()
data['disk_available_least'] = _get_disk_available_least()
...
注意get_available_resource
方法的注释信息,完全符合我们开始的推断。我们下面单以vcpus
为例继续调查资源统计流程,self.driver._get_vcpu_total
的实际调用为LibvirtDriver._get_vcpu_total
(上述代码中已给出),如果配置项vcpu_pin_set
没有生效,那么得到的_vcpu_total
的值为self._conn.getInfo()[2]
(self._conn
可以理解为libvirt的适配器,它代表与kvm
,qemu
等底层虚拟化工具的抽象连接,getInfo()
就是对libvirtmod.virNodeGetInfo
的一次简单的封装,它的返回值是一组数组,其中第三个元素就是vcpus
的数量),我们看到这里基本就可以了,再往下就是libvirt的C语言代码而不是Python的范畴了。
另一方面,如果我们配置了vcpu_pin_set
配置项,那么该配置项就被hardware.get_vcpu_pin_set
方法解析成一个可用CPU位置索引的集合,再通过对该集合求长后,我们也能得到最终想要的vcpus
的数量。
如上,就是Nova统计节点硬件资源的整个逻辑过程(vcpus
为例)。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。