diff --git a/nova/compute/claims.py b/nova/compute/claims.py index 602ca25680b2..a0123673782d 100644 --- a/nova/compute/claims.py +++ b/nova/compute/claims.py @@ -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)) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 98cafa091b56..1e9dd80fb848 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -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 diff --git a/nova/compute/rpcapi.py b/nova/compute/rpcapi.py index 2982cb41498a..e8232f7f5c01 100644 --- a/nova/compute/rpcapi.py +++ b/nova/compute/rpcapi.py @@ -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: diff --git a/nova/objects/numa.py b/nova/objects/numa.py index 4d699ea7da4e..39a638e3f877 100644 --- a/nova/objects/numa.py +++ b/nova/objects/numa.py @@ -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 diff --git a/nova/scheduler/filters/numa_topology_filter.py b/nova/scheduler/filters/numa_topology_filter.py index b94ed4866cae..769f191cbede 100644 --- a/nova/scheduler/filters/numa_topology_filter.py +++ b/nova/scheduler/filters/numa_topology_filter.py @@ -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: diff --git a/nova/tests/unit/compute/test_claims.py b/nova/tests/unit/compute/test_claims.py index 50dcd3556587..f9dff9bc33c4 100644 --- a/nova/tests/unit/compute/test_claims.py +++ b/nova/tests/unit/compute/test_claims.py @@ -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 diff --git a/nova/tests/unit/compute/test_resource_tracker.py b/nova/tests/unit/compute/test_resource_tracker.py index c5495be41393..68063b14d3ad 100644 --- a/nova/tests/unit/compute/test_resource_tracker.py +++ b/nova/tests/unit/compute/test_resource_tracker.py @@ -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, diff --git a/nova/tests/unit/compute/test_rpcapi.py b/nova/tests/unit/compute/test_rpcapi.py index 24995e2a681f..436a331ca7e4 100644 --- a/nova/tests/unit/compute/test_rpcapi.py +++ b/nova/tests/unit/compute/test_rpcapi.py @@ -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') diff --git a/nova/tests/unit/objects/test_numa.py b/nova/tests/unit/objects/test_numa.py index 329ce70a3ca1..f4eaa933e649 100644 --- a/nova/tests/unit/objects/test_numa.py +++ b/nova/tests/unit/objects/test_numa.py @@ -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( diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py index 217b69613a05..c03e7a9e4b45 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py @@ -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', diff --git a/nova/tests/unit/pci/test_stats.py b/nova/tests/unit/pci/test_stats.py index d05e1d2e64e4..cfc9c3779cfa 100644 --- a/nova/tests/unit/pci/test_stats.py +++ b/nova/tests/unit/pci/test_stats.py @@ -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) diff --git a/nova/tests/unit/scheduler/filters/test_numa_topology_filters.py b/nova/tests/unit/scheduler/filters/test_numa_topology_filters.py index 98b314961993..8a842a925fa0 100644 --- a/nova/tests/unit/scheduler/filters/test_numa_topology_filters.py +++ b/nova/tests/unit/scheduler/filters/test_numa_topology_filters.py @@ -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) diff --git a/nova/tests/unit/virt/test_hardware.py b/nova/tests/unit/virt/test_hardware.py index 85507ed25ef3..9579a76549d9 100644 --- a/nova/tests/unit/virt/test_hardware.py +++ b/nova/tests/unit/virt/test_hardware.py @@ -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): diff --git a/nova/virt/hardware.py b/nova/virt/hardware.py index e2bb618c2f26..2e37db6ec41d 100644 --- a/nova/virt/hardware.py +++ b/nova/virt/hardware.py @@ -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.