From 88b6c816a846276e2476a4d5594499cd303d26ad Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Mon, 6 May 2019 17:12:33 -0500 Subject: [PATCH] Add NUMANetworkFixture for gabbits Adds an APIFixture representing compute hosts with characteristics such as: * A root compute node provider with no resources (VCPU/MEMORY_MB in NUMA providers, DISK_GB provided by a sharing provider). * NUMA nodes, providing VCPU and MEMORY_MB resources (with interesting min_unit and step_size values), both decorated with the HW_NUMA_ROOT trait, one decorated with a CUSTOM trait. * Each NUMA node is associated with some devices. * Two network agents, themselves devoid of resources, parenting different kinds of network devices. * A more "normal" compute node provider with VCPU/MEMORY_MB/DISK_GB resources. * Some NIC subtree roots, themselves devoid of resources, decorated with the HW_NIC_ROOT trait, parenting PF providers, on different physical networks, with VF resources. * A sharing DISK_GB provider associated with both compute nodes. (It is the only thing that can provide DISK_GB to the first compute, but the second compute also has DISK_GB of its own.) This will be used by subsequent patches to create test cases including things like subtree affinity, resourceless request groups / providers, can_split, etc. Also includes a couple of tweaks to the base test helpers to make the fixture easier to set up. Change-Id: I65dbbff1d58a486941f085e89c8e32fc071b6d4b Story: #2005575 --- placement/tests/functional/db/test_base.py | 23 +- .../functional/db/test_resource_provider.py | 18 +- .../tests/functional/fixtures/gabbits.py | 199 ++++++++++++++++++ 3 files changed, 228 insertions(+), 12 deletions(-) diff --git a/placement/tests/functional/db/test_base.py b/placement/tests/functional/db/test_base.py index 43a1fbe9d..9ba2fadf3 100644 --- a/placement/tests/functional/db/test_base.py +++ b/placement/tests/functional/db/test_base.py @@ -22,6 +22,7 @@ from placement.objects import allocation as alloc_obj from placement.objects import consumer as consumer_obj from placement.objects import inventory as inv_obj from placement.objects import project as project_obj +from placement.objects import resource_class as rc_obj from placement.objects import resource_provider as rp_obj from placement.objects import trait as trait_obj from placement.objects import user as user_obj @@ -57,7 +58,15 @@ def create_provider(context, name, *aggs, **kwargs): return rp +def ensure_rc(context, name): + try: + rc_obj.ResourceClass.get_by_name(context, name) + except exception.NotFound: + rc_obj.ResourceClass(context, name=name).create() + + def add_inventory(rp, rc, total, **kwargs): + ensure_rc(rp._context, rc) kwargs.setdefault('max_unit', total) inv = inv_obj.Inventory(rp._context, resource_provider=rp, resource_class=rc, total=total, **kwargs) @@ -102,6 +111,14 @@ def set_allocation(ctx, rp, consumer, rc_used_dict): return alloc +def create_user_and_project(ctx, prefix='fake'): + user = user_obj.User(ctx, external_id='%s-user' % prefix) + user.create() + proj = project_obj.Project(ctx, external_id='%s-project' % prefix) + proj.create() + return user, proj + + class PlacementDbBaseTestCase(base.TestCase): def setUp(self): @@ -109,11 +126,7 @@ class PlacementDbBaseTestCase(base.TestCase): # we use context in some places and ctx in other. We should only use # context, but let's paper over that for now. self.ctx = self.context - self.user_obj = user_obj.User(self.ctx, external_id='fake-user') - self.user_obj.create() - self.project_obj = project_obj.Project( - self.ctx, external_id='fake-project') - self.project_obj.create() + self.user_obj, self.project_obj = create_user_and_project(self.ctx) # For debugging purposes, populated by _create_provider and used by # _validate_allocation_requests to make failure results more readable. self.rp_uuid_to_name = {} diff --git a/placement/tests/functional/db/test_resource_provider.py b/placement/tests/functional/db/test_resource_provider.py index 915332a7b..c858042bb 100644 --- a/placement/tests/functional/db/test_resource_provider.py +++ b/placement/tests/functional/db/test_resource_provider.py @@ -461,13 +461,17 @@ class ResourceProviderTestCase(tb.PlacementDbBaseTestCase): an exception. """ rp = self._create_provider('compute-host') - self.assertRaises(exception.ResourceClassNotFound, - tb.add_inventory, rp, 'UNKNOWN', 1024, - reserved=15, - min_unit=10, - max_unit=100, - step_size=10, - allocation_ratio=1.0) + inv = inv_obj.Inventory( + rp._context, resource_provider=rp, + resource_class='UNKNOWN', + total=1024, + reserved=15, + min_unit=10, + max_unit=100, + step_size=10, + allocation_ratio=1.0) + self.assertRaises( + exception.ResourceClassNotFound, rp.add_inventory, inv) def test_set_inventory_fail_in_use(self): """Test attempting to set inventory which would result in removing an diff --git a/placement/tests/functional/fixtures/gabbits.py b/placement/tests/functional/fixtures/gabbits.py index 24607dd3a..8eea0d21a 100644 --- a/placement/tests/functional/fixtures/gabbits.py +++ b/placement/tests/functional/fixtures/gabbits.py @@ -15,6 +15,7 @@ import os from gabbi import fixture import os_resource_classes as orc +import os_traits as ot from oslo_config import cfg from oslo_config import fixture as config_fixture from oslo_log.fixture import logging_error @@ -360,6 +361,204 @@ class NUMAAggregateFixture(APIFixture): tb.set_traits(ss, 'MISC_SHARES_VIA_AGGREGATE') +class NUMANetworkFixture(APIFixture): + """An APIFixture representing compute hosts with characteristics such as: + + * A root compute node provider with no resources (VCPU/MEMORY_MB in NUMA + providers, DISK_GB provided by a sharing provider). + * NUMA nodes, providing VCPU and MEMORY_MB resources (with interesting + min_unit and step_size values), decorated with the HW_NUMA_ROOT trait. + * Each NUMA node is associated with some devices. + * Two network agents, themselves devoid of resources, parenting different + kinds of network devices. + + * A more "normal" compute node provider with VCPU/MEMORY_MB/DISK_GB + resources. + * Some NIC subtree roots, themselves devoid of resources, decorated with + the HW_NIC_ROOT trait, parenting PF providers, on different physical + networks, with VF resources. + + +-----------------------+ + | sharing storage (ss1) | + | MISC_SHARES_VIA_AGG.. | + | DISK_GB:2000 |............(to cn2)......>> + | agg: [aggA] | + +-----------+-----------+ + : + +-----------------------------+ + | compute node (cn1) | + | COMPUTE_VOLUME_MULTI_ATTACH | + | (no inventory) | + | agg: [aggA] | + +---------------+-------------+ + | + +--------------------+------+--------+-------------------+ + | | | | + +---------+--------+ +---------+--------+ | | + | numa0 | | numa1 | | +-----------+------+ + | HW_NUMA_ROOT | | HW_NUMA_ROOT, FOO| | | ovs_agent | + | VCPU: 4 (2 used) | | VCPU: 4 | | | VNIC_TYPE_NORMAL | + | MEMORY_MB: 2048 | | MEMORY_MB: 2048 | | +-----------+------+ + | min_unit: 512 | | min_unit: 256 | | | + | step_size: 256 | | max_unit: 1024 | | | + +---+----------+---+ +---+----------+---+ +---+--------------+ | + | | | | | sriov_agent | | + +---+---+ +---+---+ +---+---+ +---+---+ | VNIC_TYPE_DIRECT | | + |fpga0 | |pgpu0 | |fpga1_0| |fpga1_1| +---+--------------+ | + |FPGA:1 | |VGPU:8 | |FPGA:1 | |FPGA:1 | | | + +-------+ +-------+ +-------+ +-------+ | +----+------+ + +-----+------+ |br_int | + | | |PHYSNET1 | + +------+-----++-----+------+|BW_EGR:1000| + |esn1 ||esn2 |+-----------+ + |PHYSNET1 ||PHYSNET2 | + |BW_EGR:10000||BW_EGR:20000| + +------------++------------+ + + + +--------------------+ + | compute node (cn2) | + | VCPU: 8 (3 used) | + | MEMORY_MB: 2048 | + >>....(from ss1)........| min_unit: 1024 | + | step_size: 128 | + | DISK_GB: 1000 | + | agg: [aggA] | + +---------+----------+ + | + +--------------------------+----+---------------------------+ + | | | + +-----+-----+ +-----+-----+ +-----+-----+ + |nic1 | |nic2 | |nic3 | + |HW_NIC_ROOT| |HW_NIC_ROOT| |HW_NIC_ROOT| + +-----+-----+ +-----+-----+ +-----+-----+ + | | | + +----+----+ +---------+---+-----+---------+ | + | | | | | | | + +--+--+ +--+--+ +--+--+ +--+--+ +--+--+ +--+--+ +--+--+ + |pf1_1| |pf1_2| |pf2_1| |pf2_2| |pf2_3| |pf2_4| |pf3_1| + |NET1 | |NET2 | |NET1 | |NET2 | |NET1 | |NET2 | |NET1 | + |VF:4 | |VF:4 | |VF:2 | |VF:2 | |VF:2 | |VF:2 | |VF:8 | + +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ + """ + def start_fixture(self): + super(NUMANetworkFixture, self).start_fixture() + + aggA_uuid = uuidutils.generate_uuid() + os.environ['AGGA_UUID'] = aggA_uuid + + ss1 = tb.create_provider(self.context, 'ss1', aggA_uuid) + tb.set_traits(ss1, ot.MISC_SHARES_VIA_AGGREGATE) + tb.add_inventory(ss1, orc.DISK_GB, 2000) + os.environ['SS1_UUID'] = ss1.uuid + + # CN1 + cn1 = tb.create_provider(self.context, 'cn1', aggA_uuid) + tb.set_traits(cn1, ot.COMPUTE_VOLUME_MULTI_ATTACH) + os.environ['CN1_UUID'] = cn1.uuid + + numas = [] + for i in (0, 1): + numa = tb.create_provider( + self.context, 'numa%d' % i, parent=cn1.uuid) + traits = [ot.HW_NUMA_ROOT] + if i == 1: + traits.append('CUSTOM_FOO') + tb.set_traits(numa, *traits) + tb.add_inventory(numa, orc.VCPU, 4) + numas.append(numa) + os.environ['NUMA%d_UUID' % i] = numa.uuid + tb.add_inventory( + numas[0], orc.MEMORY_MB, 2048, min_unit=512, step_size=256) + tb.add_inventory( + numas[1], orc.MEMORY_MB, 2048, min_unit=256, max_unit=1024) + user, proj = tb.create_user_and_project(self.context, prefix='numafx') + consumer = tb.ensure_consumer(self.context, user, proj) + tb.set_allocation(self.context, numas[0], consumer, {orc.VCPU: 2}) + + fpga = tb.create_provider(self.context, 'fpga0', parent=numas[0].uuid) + # TODO(efried): Use standard FPGA resource class + tb.add_inventory(fpga, 'CUSTOM_FPGA', 1) + os.environ['FPGA0_UUID'] = fpga.uuid + + pgpu = tb.create_provider(self.context, 'pgpu0', parent=numas[0].uuid) + tb.add_inventory(pgpu, orc.VGPU, 8) + os.environ['PGPU0_UUID'] = pgpu.uuid + + for i in (0, 1): + fpga = tb.create_provider( + self.context, 'fpga1_%d' % i, parent=numas[1].uuid) + # TODO(efried): Use standard FPGA resource class + tb.add_inventory(fpga, 'CUSTOM_FPGA', 1) + os.environ['FPGA1_%d_UUID' % i] = fpga.uuid + + agent = tb.create_provider( + self.context, 'sriov_agent', parent=cn1.uuid) + tb.set_traits(agent, 'CUSTOM_VNIC_TYPE_DIRECT') + os.environ['SRIOV_AGENT_UUID'] = agent.uuid + + for i in (1, 2): + dev = tb.create_provider( + self.context, 'esn%d' % i, parent=agent.uuid) + tb.set_traits(dev, 'CUSTOM_PHYSNET%d' % i) + tb.add_inventory(dev, orc.NET_BW_EGR_KILOBIT_PER_SEC, 10000 * i) + os.environ['ESN%d_UUID' % i] = dev.uuid + + agent = tb.create_provider( + self.context, 'ovs_agent', parent=cn1.uuid) + tb.set_traits(agent, 'CUSTOM_VNIC_TYPE_NORMAL') + os.environ['OVS_AGENT_UUID'] = agent.uuid + + dev = tb.create_provider(self.context, 'br_int', parent=agent.uuid) + tb.set_traits(dev, 'CUSTOM_PHYSNET0') + tb.add_inventory(dev, orc.NET_BW_EGR_KILOBIT_PER_SEC, 1000) + os.environ['BR_INT_UUID'] = dev.uuid + + # CN2 + cn2 = tb.create_provider(self.context, 'cn2', aggA_uuid) + tb.add_inventory(cn2, orc.VCPU, 8) + tb.set_allocation(self.context, cn2, consumer, {orc.VCPU: 3}) + tb.add_inventory( + cn2, orc.MEMORY_MB, 2048, min_unit=1024, step_size=128) + tb.add_inventory(cn2, orc.DISK_GB, 1000) + os.environ['CN2_UUID'] = cn2.uuid + + nics = [] + for i in (1, 2, 3): + nic = tb.create_provider( + self.context, 'nic%d' % i, parent=cn2.uuid) + # TODO(efried): Use standard HW_NIC_ROOT trait + tb.set_traits(nic, 'CUSTOM_HW_NIC_ROOT') + nics.append(nic) + os.environ['NIC%d_UUID'] = nic.uuid + # PFs for NIC1 + for i in (1, 2): + suf = '1_%d' % i + pf = tb.create_provider( + self.context, 'pf%s' % suf, parent=nics[0].uuid) + tb.set_traits(pf, 'CUSTOM_PHYSNET%d' % i) + # TODO(efried): Use standard generic VF resource class? + tb.add_inventory(pf, 'CUSTOM_VF', 4) + os.environ['PF%s_UUID' % suf] = pf.uuid + # PFs for NIC2 + for i in (0, 1, 2, 3): + suf = '2_%d' % (i + 1) + pf = tb.create_provider( + self.context, 'pf%s' % suf, parent=nics[1].uuid) + tb.set_traits(pf, 'CUSTOM_PHYSNET%d' % ((i % 2) + 1)) + # TODO(efried): Use standard generic VF resource class? + tb.add_inventory(pf, 'CUSTOM_VF', 2) + os.environ['PF%s_UUID' % suf] = pf.uuid + # PF for NIC3 + suf = '3_1' + pf = tb.create_provider( + self.context, 'pf%s' % suf, parent=nics[2].uuid) + tb.set_traits(pf, 'CUSTOM_PHYSNET1') + # TODO(efried): Use standard generic VF resource class? + tb.add_inventory(pf, 'CUSTOM_VF', 8) + os.environ['PF%s_UUID' % suf] = pf.uuid + + class NonSharedStorageFixture(APIFixture): """An APIFixture that has three compute nodes with local storage that do not use shared storage.