objects: introduce numa topology limits objects

To continue objectification of numa, this commit introduces a new object
NUMATopologyLimits responsible to handle cpu and memory limitation. Also
we now pass a dict to resource_tracker. Updates compute to handle old
compute nodes version during an upgrade.

Blueprint: kilo-objects

Change-Id: Ic4e77ad49148a33982d440c11ccb839c32136444
This commit is contained in:
Sahid Orentino Ferdjaoui 2014-11-28 05:01:07 -05:00 committed by Jay Pipes
parent fb4e2de094
commit 0b0f1515ac
14 changed files with 218 additions and 228 deletions

View File

@ -148,9 +148,6 @@ class Claim(NopClaim):
memory_mb_limit = limits.get('memory_mb')
disk_gb_limit = limits.get('disk_gb')
numa_topology_limit = limits.get('numa_topology')
if numa_topology_limit:
numa_topology_limit = hardware.VirtNUMALimitTopology.from_json(
numa_topology_limit)
msg = _("Attempting claim: memory %(memory_mb)d MB, disk %(disk_gb)d "
"GB")
@ -217,7 +214,7 @@ class Claim(NopClaim):
instance_topology = (
hardware.numa_fit_instance_to_host(
host_topology, requested_topology,
limits_topology=limit,
limits=limit,
pci_requests=pci_requests.requests,
pci_stats=pci_stats))

View File

@ -590,7 +590,7 @@ class ComputeVirtAPI(virtapi.VirtAPI):
class ComputeManager(manager.Manager):
"""Manages the running instances from creation to destruction."""
target = messaging.Target(version='3.39')
target = messaging.Target(version='3.40')
# How long to wait in seconds before re-issuing a shutdown
# signal to a instance during power off. The overall
@ -2026,6 +2026,13 @@ class ComputeManager(manager.Manager):
flavor = objects.Flavor.get_by_id(context, flavor['id'])
filter_properties = dict(filter_properties, instance_type=flavor)
# NOTE(sahid): Remove this in v4.0 of the RPC API
if (limits and 'numa_topology' in limits and
isinstance(limits['numa_topology'], six.string_types)):
db_obj = jsonutils.loads(limits['numa_topology'])
limits['numa_topology'] = (
objects.NUMATopologyLimits.obj_from_db_obj(db_obj))
@utils.synchronized(instance.uuid)
def _locked_do_build_and_run_instance(*args, **kwargs):
# NOTE(danms): We grab the semaphore with the instance uuid

View File

@ -283,6 +283,8 @@ class ComputeAPI(object):
shelve_offload
* 3.38 - Add clean_shutdown to prep_resize
* 3.39 - Add quiesce_instance and unquiesce_instance methods
* 3.40 - Make build_and_run_instance() take a new-world topology
limits object
'''
VERSION_ALIASES = {
@ -952,7 +954,24 @@ class ComputeAPI(object):
filter_properties, admin_password=None, injected_files=None,
requested_networks=None, security_groups=None,
block_device_mapping=None, node=None, limits=None):
version = '3.36'
version = '3.40'
if not self.client.can_send_version(version):
version = '3.36'
if 'numa_topology' in limits and limits['numa_topology']:
topology_limits = limits['numa_topology']
if node is not None:
cnode = objects.ComputeNode.get_by_host_and_nodename(
ctxt, host, node)
else:
cnode = (
objects.ComputeNode.
get_first_node_by_host_for_old_compat(
ctxt, host))
host_topology = objects.NUMATopology.obj_from_db_obj(
cnode.numa_topology)
limits['numa_topology'] = jsonutils.dumps(
topology_limits.to_dict_legacy(host_topology))
if not self.client.can_send_version(version):
version = '3.33'
if 'instance_type' in filter_properties:

View File

@ -182,3 +182,41 @@ class NUMATopology(base.NovaObject,
return cls(cells=[
NUMACell._from_dict(cell_dict)
for cell_dict in data_dict.get('cells', [])])
class NUMATopologyLimits(base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'cpu_allocation_ratio': fields.FloatField(),
'ram_allocation_ratio': fields.FloatField(),
}
def to_dict_legacy(self, host_topology):
cells = []
for cell in host_topology.cells:
cells.append(
{'cpus': hardware.format_cpu_spec(
cell.cpuset, allow_ranges=False),
'mem': {'total': cell.memory,
'limit': cell.memory * self.ram_allocation_ratio},
'cpu_limit': len(cell.cpuset) * self.cpu_allocation_ratio,
'id': cell.id})
return {'cells': cells}
@classmethod
def obj_from_db_obj(cls, db_obj):
if 'nova_object.name' in db_obj:
obj_topology = cls.obj_from_primitive(db_obj)
else:
# NOTE(sahid): This compatibility code needs to stay until we can
# guarantee that all compute nodes are using RPC API => 3.40.
cell = db_obj['cells'][0]
ram_ratio = cell['mem']['limit'] / float(cell['mem']['total'])
cpu_ratio = cell['cpu_limit'] / float(len(hardware.parse_cpu_spec(
cell['cpus'])))
obj_topology = NUMATopologyLimits(
cpu_allocation_ratio=cpu_ratio,
ram_allocation_ratio=ram_ratio)
return obj_topology

View File

@ -12,6 +12,7 @@
from oslo_config import cfg
from nova import objects
from nova.scheduler import filters
from nova.virt import hardware
@ -35,22 +36,17 @@ class NUMATopologyFilter(filters.BaseHostFilter):
if pci_requests:
pci_requests = pci_requests.requests
if requested_topology and host_topology:
limit_cells = []
for cell in host_topology.cells:
max_cell_memory = int(cell.memory * ram_ratio)
max_cell_cpu = len(cell.cpuset) * cpu_ratio
limit_cells.append(hardware.VirtNUMATopologyCellLimit(
cell.id, cell.cpuset, cell.memory,
max_cell_cpu, max_cell_memory))
limits = hardware.VirtNUMALimitTopology(cells=limit_cells)
limits = objects.NUMATopologyLimits(
cpu_allocation_ratio=cpu_ratio,
ram_allocation_ratio=ram_ratio)
instance_topology = (hardware.numa_fit_instance_to_host(
host_topology, requested_topology,
limits_topology=limits,
limits=limits,
pci_requests=pci_requests,
pci_stats=host_state.pci_stats))
if not instance_topology:
return False
host_state.limits['numa_topology'] = limits.to_json()
host_state.limits['numa_topology'] = limits
host_state.instance_numa_topology = instance_topology
return True
elif requested_topology:

View File

@ -27,7 +27,6 @@ from nova import objects
from nova.pci import manager as pci_manager
from nova import test
from nova.tests.unit.pci import fakes as pci_fakes
from nova.virt import hardware
class FakeResourceHandler(object):
@ -252,26 +251,20 @@ class ClaimTestCase(test.NoDBTestCase):
huge_instance = objects.InstanceNUMATopology(
cells=[objects.InstanceNUMACell(
id=1, cpuset=set([1, 2, 3, 4, 5]), memory=2048)])
limit_topo = hardware.VirtNUMALimitTopology(
cells=[hardware.VirtNUMATopologyCellLimit(
1, [1, 2], 512, cpu_limit=2, memory_limit=512),
hardware.VirtNUMATopologyCellLimit(
1, [3, 4], 512, cpu_limit=2, memory_limit=512)])
limit_topo = objects.NUMATopologyLimits(
cpu_allocation_ratio=1, ram_allocation_ratio=1)
self.assertRaises(exception.ComputeResourcesUnavailable,
self._claim,
limits={'numa_topology': limit_topo.to_json()},
limits={'numa_topology': limit_topo},
numa_topology=huge_instance)
def test_numa_topology_passes(self, mock_get):
huge_instance = objects.InstanceNUMATopology(
cells=[objects.InstanceNUMACell(
id=1, cpuset=set([1, 2]), memory=512)])
limit_topo = hardware.VirtNUMALimitTopology(
cells=[hardware.VirtNUMATopologyCellLimit(
1, [1, 2], 512, cpu_limit=5, memory_limit=4096),
hardware.VirtNUMATopologyCellLimit(
1, [3, 4], 512, cpu_limit=5, memory_limit=4096)])
self._claim(limits={'numa_topology': limit_topo.to_json()},
limit_topo = objects.NUMATopologyLimits(
cpu_allocation_ratio=1, ram_allocation_ratio=1)
self._claim(limits={'numa_topology': limit_topo},
numa_topology=huge_instance)
@pci_fakes.patch_pci_whitelist

View File

@ -38,7 +38,6 @@ from nova.tests.unit.compute.monitors import test_monitors
from nova.tests.unit.objects import test_migration
from nova.tests.unit.pci import fakes as pci_fakes
from nova.virt import driver
from nova.virt import hardware
FAKE_VIRT_MEMORY_MB = 5
@ -52,11 +51,9 @@ FAKE_VIRT_NUMA_TOPOLOGY = objects.NUMATopology(
objects.NUMACell(id=1, cpuset=set([3, 4]), memory=3072,
cpu_usage=0, memory_usage=0, mempages=[],
siblings=[], pinned_cpus=set([]))])
FAKE_VIRT_NUMA_TOPOLOGY_OVERHEAD = hardware.VirtNUMALimitTopology(
cells=[hardware.VirtNUMATopologyCellLimit(
0, set([1, 2]), 3072, 4, 10240),
hardware.VirtNUMATopologyCellLimit(
1, set([3, 4]), 3072, 4, 10240)])
FAKE_VIRT_NUMA_TOPOLOGY_OVERHEAD = objects.NUMATopologyLimits(
cpu_allocation_ratio=2, ram_allocation_ratio=2)
ROOT_GB = 5
EPHEMERAL_GB = 1
FAKE_VIRT_LOCAL_GB = ROOT_GB + EPHEMERAL_GB
@ -641,7 +638,7 @@ class BaseTrackerTestCase(BaseTestCase):
'memory_mb': memory_mb,
'disk_gb': disk_gb,
'vcpu': vcpus,
'numa_topology': numa_topology.to_json() if numa_topology else None
'numa_topology': numa_topology,
}
def assertEqualNUMAHostTopology(self, expected, got):
@ -971,7 +968,7 @@ class InstanceClaimTestCase(BaseTrackerTestCase):
limits = {'memory_mb': memory_mb + FAKE_VIRT_MEMORY_OVERHEAD,
'disk_gb': root_gb * 2,
'vcpu': vcpus,
'numa_topology': FAKE_VIRT_NUMA_TOPOLOGY_OVERHEAD.to_json()}
'numa_topology': FAKE_VIRT_NUMA_TOPOLOGY_OVERHEAD}
instance = self._fake_instance(memory_mb=memory_mb,
root_gb=root_gb, ephemeral_gb=ephemeral_gb,

View File

@ -25,7 +25,9 @@ from oslo_serialization import jsonutils
from nova.compute import rpcapi as compute_rpcapi
from nova import context
from nova.objects import block_device as objects_block_dev
from nova.objects import compute_node as objects_compute_node
from nova.objects import network_request as objects_network_request
from nova.objects import numa as objects_numa
from nova import test
from nova.tests.unit import fake_block_device
from nova.tests.unit import fake_instance
@ -76,6 +78,9 @@ class ComputeRpcAPITestCase(test.NoDBTestCase):
expected_kwargs['host'] = expected_kwargs.pop('host_param')
else:
expected_kwargs.pop('host', None)
if 'legacy_limits' in expected_kwargs:
expected_kwargs['limits'] = expected_kwargs.pop('legacy_limits')
kwargs.pop('legacy_limits', None)
expected_kwargs.pop('destination', None)
if assert_dict:
@ -548,7 +553,7 @@ class ComputeRpcAPITestCase(test.NoDBTestCase):
admin_password='passwd', injected_files=None,
requested_networks=['network1'], security_groups=None,
block_device_mapping=None, node='node', limits=[],
version='3.36')
version='3.40')
@mock.patch('nova.utils.is_neutron', return_value=True)
def test_build_and_run_instance_icehouse_compat(self, is_neutron):
@ -562,7 +567,7 @@ class ComputeRpcAPITestCase(test.NoDBTestCase):
network_id="fake_network_id", address="10.0.0.1",
port_id="fake_port_id")]),
security_groups=None,
block_device_mapping=None, node='node', limits=[],
block_device_mapping=None, node='node', limits={},
version='3.23')
def test_quiesce_instance(self):
@ -572,3 +577,66 @@ class ComputeRpcAPITestCase(test.NoDBTestCase):
def test_unquiesce_instance(self):
self._test_compute_api('unquiesce_instance', 'cast',
instance=self.fake_instance_obj, mapping=None, version='3.39')
@mock.patch('nova.utils.is_neutron', return_value=True)
def test_build_and_run_instance_juno_compat(self, is_neutron):
self.flags(compute='juno', group='upgrade_levels')
self._test_compute_api('build_and_run_instance', 'cast',
instance=self.fake_instance_obj, host='host', image='image',
request_spec={'request': 'spec'}, filter_properties=[],
admin_password='passwd', injected_files=None,
requested_networks= objects_network_request.NetworkRequestList(
objects=[objects_network_request.NetworkRequest(
network_id="fake_network_id", address="10.0.0.1",
port_id="fake_port_id")]),
security_groups=None,
block_device_mapping=None, node='node', limits={},
version='3.33')
@mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
@mock.patch('nova.utils.is_neutron', return_value=True)
def test_build_and_run_instance_limits_juno_compat(
self, is_neutron, get_by_host_and_nodename):
host_topology = objects_numa.NUMATopology(cells=[
objects_numa.NUMACell(
id=0, cpuset=set([1, 2]), memory=512,
cpu_usage=2, memory_usage=256,
pinned_cpus=set([1])),
objects_numa.NUMACell(
id=1, cpuset=set([3, 4]), memory=512,
cpu_usage=1, memory_usage=128,
pinned_cpus=set([]))
])
limits = objects_numa.NUMATopologyLimits(
cpu_allocation_ratio=16,
ram_allocation_ratio=2)
cnode = objects_compute_node.ComputeNode(
numa_topology=jsonutils.dumps(
host_topology.obj_to_primitive()))
get_by_host_and_nodename.return_value = cnode
legacy_limits = jsonutils.dumps(
limits.to_dict_legacy(host_topology))
self.flags(compute='juno', group='upgrade_levels')
netreqs = objects_network_request.NetworkRequestList(objects=[
objects_network_request.NetworkRequest(
network_id="fake_network_id",
address="10.0.0.1",
port_id="fake_port_id")])
self._test_compute_api('build_and_run_instance', 'cast',
instance=self.fake_instance_obj,
host='host',
image='image',
request_spec={'request': 'spec'},
filter_properties=[],
admin_password='passwd',
injected_files=None,
requested_networks=netreqs,
security_groups=None,
block_device_mapping=None,
node='node',
limits={'numa_topology': limits},
legacy_limits={'numa_topology': legacy_limits},
version='3.33')

View File

@ -10,7 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from nova import exception
from nova import objects
from nova.tests.unit.objects import test_objects
@ -37,6 +36,35 @@ class _TestNUMA(object):
self.assertEqual(d1, d2)
def test_from_legacy_limits(self):
old_style = {"cells": [
{"mem": {
"total": 1024,
"limit": 2048},
"cpu_limit": 96.0,
"cpus": "0,1,2,3,4,5",
"id": 0}]}
limits = objects.NUMATopologyLimits.obj_from_db_obj(old_style)
self.assertEqual(16.0, limits.cpu_allocation_ratio)
self.assertEqual(2.0, limits.ram_allocation_ratio)
def test_to_legacy_limits(self):
limits = objects.NUMATopologyLimits(
cpu_allocation_ratio=16,
ram_allocation_ratio=2)
host_topo = objects.NUMATopology(cells=[
objects.NUMACell(id=0, cpuset=set([1, 2]), memory=1024)
])
old_style = {'cells': [
{'mem': {'total': 1024,
'limit': 2048.0},
'id': 0,
'cpus': '1,2',
'cpu_limit': 32.0}]}
self.assertEqual(old_style, limits.to_dict_legacy(host_topo))
def test_free_cpus(self):
obj = objects.NUMATopology(cells=[
objects.NUMACell(

View File

@ -1189,6 +1189,7 @@ object_data = {
'NUMACell': '1.2-cb9c3b08cc1c418d021492f788d04173',
'NUMAPagesTopology': '1.0-97d93f70a68625b5f29ff63a40a4f612',
'NUMATopology': '1.2-790f6bdff85bf6e5677f409f3a4f1c6a',
'NUMATopologyLimits': '1.0-201845851897940c0a300e3d14ebf04a',
'PciDevice': '1.3-e059641df10e85d464672c5183a9473b',
'PciDeviceList': '1.1-38cbe2d3c23b9e46f7a74b486abcad85',
'PciDevicePool': '1.0-d6ed1abe611c9947345a44155abe6f11',

View File

@ -21,7 +21,6 @@ from nova.pci import stats
from nova.pci import whitelist
from nova import test
from nova.tests.unit.pci import fakes
from nova.virt import hardware
fake_pci_1 = {
'compute_node_id': 1,
'address': '0000:00:00.1',
@ -148,39 +147,39 @@ class PciDeviceStatsTestCase(test.NoDBTestCase):
pci_requests_multiple)
def test_support_requests_numa(self):
cells = [hardware.VirtNUMATopologyCell(0, None, None),
hardware.VirtNUMATopologyCell(1, None, None)]
cells = [objects.NUMACell(id=0, cpuset=set(), memory=0),
objects.NUMACell(id=1, cpuset=set(), memory=0)]
self.assertEqual(True, self.pci_stats.support_requests(
pci_requests, cells))
def test_support_requests_numa_failed(self):
cells = [hardware.VirtNUMATopologyCell(0, None, None)]
cells = [objects.NUMACell(id=0, cpuset=set(), memory=0)]
self.assertEqual(False, self.pci_stats.support_requests(
pci_requests, cells))
def test_support_requests_no_numa_info(self):
cells = [hardware.VirtNUMATopologyCell(0, None, None)]
cells = [objects.NUMACell(id=0, cpuset=set(), memory=0)]
pci_request = [objects.InstancePCIRequest(count=1,
spec=[{'vendor_id': 'v3'}])]
self.assertEqual(True, self.pci_stats.support_requests(
pci_request, cells))
def test_consume_requests_numa(self):
cells = [hardware.VirtNUMATopologyCell(0, None, None),
hardware.VirtNUMATopologyCell(1, None, None)]
cells = [objects.NUMACell(id=0, cpuset=set(), memory=0),
objects.NUMACell(id=1, cpuset=set(), memory=0)]
devs = self.pci_stats.consume_requests(pci_requests, cells)
self.assertEqual(2, len(devs))
self.assertEqual(set(['v1', 'v2']),
set([dev['vendor_id'] for dev in devs]))
def test_consume_requests_numa_failed(self):
cells = [hardware.VirtNUMATopologyCell(0, None, None)]
cells = [objects.NUMACell(id=0, cpuset=set(), memory=0)]
self.assertRaises(exception.PciDeviceRequestFailed,
self.pci_stats.consume_requests,
pci_requests, cells)
def test_consume_requests_no_numa_info(self):
cells = [hardware.VirtNUMATopologyCell(0, None, None)]
cells = [objects.NUMACell(id=0, cpuset=set(), memory=0)]
pci_request = [objects.InstancePCIRequest(count=1,
spec=[{'vendor_id': 'v3'}])]
devs = self.pci_stats.consume_requests(pci_request, cells)

View File

@ -19,7 +19,6 @@ from nova.scheduler.filters import numa_topology_filter
from nova import test
from nova.tests.unit import fake_instance
from nova.tests.unit.scheduler import fakes
from nova.virt import hardware
class TestNUMATopologyFilter(test.NoDBTestCase):
@ -151,9 +150,6 @@ class TestNUMATopologyFilter(test.NoDBTestCase):
self.assertTrue(self.filt_cls.host_passes(host, filter_properties))
self.assertIsInstance(host.instance_numa_topology,
objects.InstanceNUMATopology)
limits_topology = hardware.VirtNUMALimitTopology.from_json(
host.limits['numa_topology'])
self.assertEqual(limits_topology.cells[0].cpu_limit, 42)
self.assertEqual(limits_topology.cells[1].cpu_limit, 42)
self.assertEqual(limits_topology.cells[0].memory_limit, 665)
self.assertEqual(limits_topology.cells[1].memory_limit, 665)
limits = host.limits['numa_topology']
self.assertEqual(limits.cpu_allocation_ratio, 21)
self.assertEqual(limits.ram_allocation_ratio, 1.3)

View File

@ -1316,13 +1316,12 @@ class VirtNUMATopologyCellUsageTestCase(test.NoDBTestCase):
def test_fit_instance_cell_success_w_limit(self):
host_cell = objects.NUMACell(id=4, cpuset=set([1, 2]), memory=1024,
cpu_usage=2,
memory_usage=1024,
cpu_usage=2,
memory_usage=1024,
mempages=[], siblings=[],
pinned_cpus=set([]))
limit_cell = hw.VirtNUMATopologyCellLimit(
4, cpuset=set([1, 2]), memory=1024,
cpu_limit=4, memory_limit=2048)
limit_cell = objects.NUMATopologyLimits(
cpu_allocation_ratio=2, ram_allocation_ratio=2)
instance_cell = objects.InstanceNUMACell(
id=0, cpuset=set([1, 2]), memory=1024)
fitted_cell = hw._numa_fit_instance_cell(
@ -1334,9 +1333,8 @@ class VirtNUMATopologyCellUsageTestCase(test.NoDBTestCase):
host_cell = objects.NUMACell(id=4, cpuset=set([1, 2]), memory=1024,
cpu_usage=0, memory_usage=0, mempages=[],
siblings=[], pinned_cpus=set([]))
limit_cell = hw.VirtNUMATopologyCellLimit(
4, cpuset=set([1, 2]), memory=1024,
cpu_limit=4, memory_limit=2048)
limit_cell = objects.NUMATopologyLimits(
cpu_allocation_ratio=2, ram_allocation_ratio=2)
instance_cell = objects.InstanceNUMACell(
id=0, cpuset=set([1, 2, 3]), memory=4096)
fitted_cell = hw._numa_fit_instance_cell(
@ -1345,15 +1343,14 @@ class VirtNUMATopologyCellUsageTestCase(test.NoDBTestCase):
def test_fit_instance_cell_fail_w_limit(self):
host_cell = objects.NUMACell(id=4, cpuset=set([1, 2]), memory=1024,
cpu_usage=2,
memory_usage=1024,
cpu_usage=2,
memory_usage=1024,
mempages=[], siblings=[],
pinned_cpus=set([]))
limit_cell = hw.VirtNUMATopologyCellLimit(
4, cpuset=set([1, 2]), memory=1024,
cpu_limit=4, memory_limit=2048)
instance_cell = objects.InstanceNUMACell(
id=0, cpuset=set([1, 2]), memory=4096)
limit_cell = objects.NUMATopologyLimits(
cpu_allocation_ratio=2, ram_allocation_ratio=2)
fitted_cell = hw._numa_fit_instance_cell(
host_cell, instance_cell, limit_cell=limit_cell)
self.assertIsNone(fitted_cell)
@ -1380,15 +1377,8 @@ class VirtNUMAHostTopologyTestCase(test.NoDBTestCase):
mempages=[], siblings=[],
pinned_cpus=set([]))])
self.limits = hw.VirtNUMALimitTopology(
cells=[
hw.VirtNUMATopologyCellLimit(
1, cpuset=set([1, 2]), memory=2048,
cpu_limit=4, memory_limit=4096),
hw.VirtNUMATopologyCellLimit(
2, cpuset=set([3, 4]), memory=2048,
cpu_limit=4, memory_limit=3072)])
self.limits = objects.NUMATopologyLimits(
cpu_allocation_ratio=2, ram_allocation_ratio=2)
self.instance1 = objects.InstanceNUMATopology(
cells=[
objects.InstanceNUMACell(
@ -1431,7 +1421,7 @@ class VirtNUMAHostTopologyTestCase(test.NoDBTestCase):
self.host = hw.numa_usage_from_instances(self.host,
[fitted_instance1])
fitted_instance2 = hw.numa_fit_instance_to_host(
self.host, self.instance1, self.limits)
self.host, self.instance2, self.limits)
self.assertIsNone(fitted_instance2)
def test_get_fitting_culmulative_success_limits(self):

View File

@ -599,88 +599,6 @@ def get_best_cpu_topology(flavor, image_meta, allow_threads=True,
allow_threads, numa_topology)[0]
class VirtNUMATopologyCell(object):
"""Class for reporting NUMA resources in a cell
The VirtNUMATopologyCell class represents the
hardware resources present in a NUMA cell.
"""
def __init__(self, id, cpuset, memory):
"""Create a new NUMA Cell
:param id: integer identifier of cell
:param cpuset: set containing list of CPU indexes
:param memory: RAM measured in MiB
Creates a new NUMA cell object to record the hardware
resources.
:returns: a new NUMA cell object
"""
super(VirtNUMATopologyCell, self).__init__()
self.id = id
self.cpuset = cpuset
self.memory = memory
def _to_dict(self):
return {'cpus': format_cpu_spec(self.cpuset, allow_ranges=False),
'mem': {'total': self.memory},
'id': self.id}
@classmethod
def _from_dict(cls, data_dict):
cpuset = parse_cpu_spec(data_dict.get('cpus', ''))
memory = data_dict.get('mem', {}).get('total', 0)
cell_id = data_dict.get('id')
return cls(cell_id, cpuset, memory)
class VirtNUMATopologyCellLimit(VirtNUMATopologyCell):
def __init__(self, id, cpuset, memory, cpu_limit, memory_limit):
"""Create a new NUMA Cell with usage
:param id: integer identifier of cell
:param cpuset: set containing list of CPU indexes
:param memory: RAM measured in MiB
:param cpu_limit: maximum number of CPUs allocated
:param memory_limit: maxumum RAM allocated in MiB
Creates a new NUMA cell object to represent the max hardware
resources and utilization. The number of CPUs specified
by the @cpu_usage parameter may be larger than the number
of bits set in @cpuset if CPU overcommit is used. Likewise
the amount of RAM specified by the @memory_limit parameter
may be larger than the available RAM in @memory if RAM
overcommit is used.
:returns: a new NUMA cell object
"""
super(VirtNUMATopologyCellLimit, self).__init__(
id, cpuset, memory)
self.cpu_limit = cpu_limit
self.memory_limit = memory_limit
def _to_dict(self):
data_dict = super(VirtNUMATopologyCellLimit, self)._to_dict()
data_dict['mem']['limit'] = self.memory_limit
data_dict['cpu_limit'] = self.cpu_limit
return data_dict
@classmethod
def _from_dict(cls, data_dict):
cpuset = parse_cpu_spec(data_dict.get('cpus', ''))
memory = data_dict.get('mem', {}).get('total', 0)
cpu_limit = data_dict.get('cpu_limit', len(cpuset))
memory_limit = data_dict.get('mem', {}).get('limit', memory)
cell_id = data_dict.get('id')
return cls(cell_id, cpuset, memory, cpu_limit, memory_limit)
def _numa_cell_supports_pagesize_request(host_cell, inst_cell):
"""Determines whether the cell can accept the request.
@ -816,7 +734,7 @@ def _numa_fit_instance_cell(host_cell, instance_cell, limit_cell=None):
:param host_cell: host cell to fit the instance cell onto
:param instance_cell: instance cell we want to fit
:param limit_cell: cell with limits of the host_cell if any
:param limit_cell: an objects.NUMATopologyLimit or None
Make sure we can fit the instance cell onto a host cell and if so,
return a new objects.InstanceNUMACell with the id set to that of
@ -841,8 +759,9 @@ def _numa_fit_instance_cell(host_cell, instance_cell, limit_cell=None):
elif limit_cell:
memory_usage = host_cell.memory_usage + instance_cell.memory
cpu_usage = host_cell.cpu_usage + len(instance_cell.cpuset)
if (memory_usage > limit_cell.memory_limit or
cpu_usage > limit_cell.cpu_limit):
cpu_limit = len(host_cell.cpuset) * limit_cell.cpu_allocation_ratio
ram_limit = host_cell.memory * limit_cell.ram_allocation_ratio
if memory_usage > ram_limit or cpu_usage > cpu_limit:
return None
pagesize = None
@ -857,49 +776,6 @@ def _numa_fit_instance_cell(host_cell, instance_cell, limit_cell=None):
return instance_cell
class VirtNUMATopology(object):
"""Base class for tracking NUMA topology information
The VirtNUMATopology class represents the NUMA hardware
topology for memory and CPUs in any machine. It is
later specialized for handling either guest instance
or compute host NUMA topology.
"""
def __init__(self, cells=None):
"""Create a new NUMA topology object
:param cells: list of VirtNUMATopologyCell instances
"""
super(VirtNUMATopology, self).__init__()
self.cells = cells or []
def __len__(self):
"""Defined so that boolean testing works the same as for lists."""
return len(self.cells)
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, str(self._to_dict()))
def _to_dict(self):
return {'cells': [cell._to_dict() for cell in self.cells]}
@classmethod
def _from_dict(cls, data_dict):
return cls(cells=[cls.cell_class._from_dict(cell_dict)
for cell_dict in data_dict.get('cells', [])])
def to_json(self):
return jsonutils.dumps(self._to_dict())
@classmethod
def from_json(cls, json_string):
return cls._from_dict(jsonutils.loads(json_string))
def _numa_get_flavor_or_image_prop(flavor, image_meta, propname):
"""Return the value of propname from flavor or image
@ -1117,22 +993,14 @@ def numa_get_constraints(flavor, image_meta):
return _add_cpu_pinning_constraint(flavor, image_meta, numa_topology)
class VirtNUMALimitTopology(VirtNUMATopology):
"""Class to represent the max resources of a compute node used
for checking oversubscription limits.
"""
cell_class = VirtNUMATopologyCellLimit
def numa_fit_instance_to_host(
host_topology, instance_topology, limits_topology=None,
host_topology, instance_topology, limits=None,
pci_requests=None, pci_stats=None):
"""Fit the instance topology onto the host topology given the limits
:param host_topology: objects.NUMATopology object to fit an instance on
:param instance_topology: objects.InstanceNUMATopology to be fitted
:param limits_topology: VirtNUMALimitTopology that defines limits
:param limits: objects.NUMATopologyLimits that defines limits
:param pci_requests: instance pci_requests
:param pci_stats: pci_stats for the host
@ -1146,22 +1014,15 @@ def numa_fit_instance_to_host(
len(host_topology) < len(instance_topology)):
return
else:
if limits_topology is None:
limits_topology_cells = itertools.repeat(
None, len(host_topology))
else:
limits_topology_cells = limits_topology.cells
# TODO(ndipanov): We may want to sort permutations differently
# depending on whether we want packing/spreading over NUMA nodes
for host_cell_perm in itertools.permutations(
zip(host_topology.cells, limits_topology_cells),
len(instance_topology)
):
host_topology.cells, len(instance_topology)):
cells = []
for (host_cell, limit_cell), instance_cell in zip(
for host_cell, instance_cell in zip(
host_cell_perm, instance_topology.cells):
got_cell = _numa_fit_instance_cell(
host_cell, instance_cell, limit_cell)
host_cell, instance_cell, limits)
if got_cell is None:
break
cells.append(got_cell)
@ -1333,7 +1194,7 @@ def get_host_numa_usage_from_instance(host, instance, free=False,
This is a convenience method to help us handle the fact that we use several
different types throughout the code (ComputeNode and Instance objects,
dicts, scheduler HostState) which may have both json and deserialized
versions of VirtNUMATopology classes.
versions of objects.numa classes.
Handles all the complexity without polluting the class method with it.