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:
parent
fb4e2de094
commit
0b0f1515ac
|
@ -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))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Reference in New Issue