将模型资源表示为对象

https://blueprints.launchpad.net/nova/+spec/resource-objects

添加模型对象来表示实例可以请求和使用的资源。

问题描述

在 Nova 中,我们对虚拟机实例消耗的资源以及计算节点提供的资源进行建模的方式非常宽松。Flavor 对象具有许多静态字段,这些字段对应于 CPU、RAM 和本地磁盘等简单资源的数量。我们使用键/值对字典和 JSON 序列化的 BLOB 数据来建模其他类型的资源,例如 PCIe 设备或 NUMA 单元布局。

计算节点上的资源跟踪器跟踪节点上消耗的资源集合。 ResourceTracker.old_resources 属性是一个字典,包含大量嵌套字典。其中一些嵌套字典包括可扩展资源跟踪器的 ‘stats’ 字典;各种 ‘pci_devices’、‘pci_stats’ 和 ‘pci_passthrough_devices’ 内容;一个 ‘numa_topology’ blob,它存储了 nova.virt.hardware 中的对象的一个 JSON 序列化表示;以及一个包含完全非结构化和未记录的键/值对的 ‘metrics’ 字典。除了这些之外,ResourceTracker.old_resources 字典还包含一些与 Flavor 对象公开的简单资源类型匹配的顶级键

  • local_gb_used: 计算节点上使用的磁盘空间(GB)

  • local_gb: 计算节点提供的本地磁盘总容量(GB)

  • free_disk_gb: 计算节点可用的磁盘空间(GB)

  • vcpus_used: 计算节点上消耗的虚拟 CPU 数量

  • vcpus: 计算节点提供的虚拟 CPU 总数

  • free_vcpus: 计算节点可用的虚拟 CPU 数量

  • memory_mb_used: 计算节点上使用的 RAM 容量(MB)

  • memory_mb: 计算节点提供的 RAM 总容量(MB)

  • free_ram_mb: 计算节点可用的 RAM 容量(MB)

  • running_vms: 在节点上运行的虚拟机实例数量

  • current_workload: 节点上工作负载的某个计算值

不幸的是,上述内容都没有在代码中记录,为了向调度器添加新功能,人们继续向字典中添加自由形式的键和嵌套字典。这使得将实际使用量告知调度器容易出错:资源跟踪器调用 scheduler_client.update_resource_stats() 方法,将这种非结构化、未版本化的信息字典原样传递给调度器。这意味着调度器接口非常脆弱,因为任何决定向自由形式资源字典添加新键的开发人员都可以随意更改接口。资源字典键中的拼写错误很容易在代码审查中被忽略,而且坦率地说,对于资源跟踪器中可扩展资源跟踪器周围的许多边缘情况代码,几乎没有功能测试。

除了接口脆弱性问题之外,资源字典的自由形式也意味着不同的资源以不同的方式被跟踪。PCI 资源以一种方式被跟踪,NUMA 拓扑使用情况以另一种方式被跟踪,CPU/RAM/磁盘以再次不同的方式被跟踪,而任何在可扩展资源跟踪器的完全自由混乱中建模的资源都以完全不同的方式被跟踪,使用修改提供的 ‘stats’ 嵌套字典的插件。

资源跟踪器中由此造成的混乱的一个例子可以在这里看到

def _update(self, context, values):
    """Update partial stats locally and populate them to Scheduler."""
    self._write_ext_resources(values)
    # NOTE(pmurray): the stats field is stored as a json string. The
    # json conversion will be done automatically by the ComputeNode object
    # so this can be removed when using ComputeNode.
    values['stats'] = jsonutils.dumps(values['stats'])

    if not self._resource_change(values):
        return
    if "service" in self.compute_node:
        del self.compute_node['service']
    # NOTE(sbauza): Now the DB update is asynchronous, we need to locally
    #               update the values
    self.compute_node.update(values)
    # Persist the stats to the Scheduler
    self._update_resource_stats(context, values)
    if self.pci_tracker:
        self.pci_tracker.save(context)

如果资源实际上以一致的方式建模,上述代码将如下所示

def _update(self, context, resources):
    if not self._resource_change(resources):
        return
    # Notify the scheduler about changed resources
    scheduler_client.update_usage_for_compute_node(
        context, self.compute_node, resources)

同样,以下代码(同样来自资源跟踪器)

def _update_usage(self, context, resources, usage, sign=1):
    mem_usage = usage['memory_mb']

    overhead = self.driver.estimate_instance_overhead(usage)
    mem_usage += overhead['memory_mb']

    resources['memory_mb_used'] += sign * mem_usage
    resources['local_gb_used'] += sign * usage.get('root_gb', 0)
    resources['local_gb_used'] += sign * usage.get('ephemeral_gb', 0)

    # free ram and disk may be negative, depending on policy:
    resources['free_ram_mb'] = (resources['memory_mb'] -
                                resources['memory_mb_used'])
    resources['free_disk_gb'] = (resources['local_gb'] -
                                 resources['local_gb_used'])

    resources['running_vms'] = self.stats.num_instances
    self.ext_resources_handler.update_from_instance(usage, sign)

    # Calculate the numa usage
    free = sign == -1
    updated_numa_topology = hardware.get_host_numa_usage_from_instance(
            resources, usage, free)
    resources['numa_topology'] = updated_numa_topology

将改为如下所示

def _update_usage(self, context, amounts):
    for resource, amount in amounts.items():
        self.inventories[resource].consume(amount)

用例

Nova 贡献者希望扩展调度器的功能,并打算将调度器分解为 Gantt 项目。为了有效地做到这一点,资源跟踪器和调度器周围的内部接口必须清理,以使用结构化对象。

提议的变更

建模请求的和使用的资源量是必须首先完成的基础构建块,然后才能对调度器或资源跟踪器接口进行任何进一步的重构或清理。

此蓝图包含添加一组类来表示

  • 不同数据类型的数量,例如 IntegerAmountNUMATopologyAmount

  • 不同数据类型的库存,描述实际容量、已使用的数量和任何超额承诺比率。例如 IntegerInventoryNUMAInventory

  • 不同类型的资源,例如使用 IntegerAmountIntegerInventory 的 RAM,或使用 NUMAAmountNUMAInventory 的 NUMA 拓扑。

这些数量、库存和资源类将是 nova.objects 对象类,并将使 Nova 能够以版本化的方式演进其跟踪资源和公开资源消耗的方式。

可扩展资源跟踪器 (ERT) 的目标是建立一个框架,允许添加新的资源类型并允许以不同的方式计算这些资源。虽然此蓝图确实删除了 ERT,但由于这些资源、数量和库存类被添加为 nova.object 对象,我们将获得 ERT 想要获得的灵活性,但具有 nova 对象系统的稳定性。

资源跟踪器代码然后将转换为使用上述类来表示计算节点上所有资源的库存。目前,这些将通过简单地调用 compute_node.save() 来持久化。

我们没有提出对 compute_nodes 表的数据库模式或 nova.objects.ComputeNode 中的字段进行更改,但是我们添加了翻译方法到 nova.objects.ComputeNode,这些方法能够从计算节点生成一个 Inventory 对象字典(以 Resource 为键),并从类似的结构更新计算节点。

备选方案

无。

数据模型影响

无。此蓝图添加的对象未存储在数据库中。这些对象是用于替换当前用于表示资源数量的非结构化嵌套字典的。

REST API 影响

无。

安全影响

无。

通知影响

无。

其他最终用户影响

无。

性能影响

无。

其他部署者影响

在完成此蓝图后,将删除 ERT。

开发人员影响

完成此蓝图后,处理请求规范的代码将更加结构化,资源跟踪器中围绕 ERT、PCI 跟踪器和 NUMA 拓扑怪癖的大量意大利面代码将消失。

实现

将提供以下抽象类

class Amount(object):
   """Represents a quantity of a resource."""

   def __eq__(self, other):
       raise NotImplementedError

   def __ne__(self, other):
       return not self == other

   def __hash__(self, other):
       raise NotImplementedError

   def __neg__(self, other):
       raise NotImplementedError


class Inventory(object):
   """Describes the capacity, available and used amounts for a resource."""

   def consume(self, amount):
       """Update (i.e. add) the given amount to the used amount in this
       inventory. If the amount is negative, more resources will be available
       afterwards than were before.

       :param amount 'Amount' to add to the usage.
       :raises ValueError if amount is the wrong type for this inventory.
       :raises CapacityException if accommodating this request would cause
               either available or used resources to go negative.
       """
       raise NotImplementedError

   def can_provide(self, amount):
       """Determine if this inventory can provide the given amount of
       resources. An overcommit ratio may be applied.

       :param amount 'Amount' to determine if there is room for.
       :raises ValueError if amount is the wrong type for this inventory or is
               negative.
       :returns True if the requested amount of resources may be consumed,
                False otherwise.
       """
       raise NotImplementedError


class Resource(object):
   """Describes a particular kind of resource."""

   @classmethod
   def make_amount(cls, *args, **kwargs):
       """Makes an Amount of the type appropriate to this resource."""
       raise NotImplementedError

   @classmethod
   def make_inventory(cls, *args, **kwargs):
       """Makes an Inventory of the type appropriate to this resource."""
       raise NotImplementedError

库存类的每个具体特化都必须能够处理其处理的资源类型的超额承诺比率。

考虑到所有实例请求的资源都应该能够以相同的方式与所有计算节点的资源库存进行比较,使用如下代码

for resource, amount in request_spec.resources.items():
   if compute_node.inventories[resource].can_provide(amount):
       # do something... perhaps claim resources on the compute
       # node, which might eventually call:
       compute_node.inventories[resource].consume(amount)

负责人

主要负责人

jaypipes

其他贡献者

lxsli

工作项

  • 添加用于数量和库存表示的类。

  • 添加用于资源表示的类。

  • 添加翻译方法(get_inventoriesupdate_inventories)到 nova.objects.ComputeNode 以返回或更新 Resource 键的 Inventory 对象字典,并进行单元测试。

  • 将资源跟踪器转换为使用库存,而不是非 PCI、非 ERT、非 NUMA 资源的字典中的自由/总数/已用数量的三元组。

  • 删除可扩展资源跟踪器代码。

  • 将资源跟踪器转换为使用库存,而不是 old_resources objects.ComputeNode 对象中的 ‘numa_topology’ 键和 nova.objects.NUMATopology 对象。

  • 将资源跟踪器转换为使用库存,而不是资源跟踪器的 pci_tracker 属性中的 nova.pci.pci_stats.PciDeviceStats 对象。

  • 将 virt 驱动程序的 get_available_resources 方法转换为返回资源对象字典。

  • 弃用旧的 update_resource_stats() 调度程序 RPC API 方法。

  • 将调度程序的 HostStateManager 转换为利用新的 ComputeNode.get_inventories()ComputeNode.update_inventories 方法。

  • 添加开发人员参考文档,说明如何建模资源。

依赖项

  • 在以一致的方式处理 PCI 设备之前,需要进行 PCI 清理。PCI 清理应该是一个单独的蓝图。

测试

将添加用于对象的新的单元测试。资源跟踪器的现有单元测试将在将资源跟踪器转换为使用新的资源对象模型而不是其当前自由形式字典的补丁集中进行修改。

文档影响

目前没有开发人员参考文档解释 Nova 中如何跟踪不同的资源。解释新的资源类型和数量类的开发人员参考材料将作为此蓝图的一部分提供。

参考资料

历史

可选部分,用于 Mitaka,每次更新规范时使用,以描述新的设计、API 或任何数据库模式更新。有助于让读者了解随着时间的推移发生了什么。

修订版

Kilo

引入但未实现

Liberty

重新批准但未合并

Mitaka

重新提交