From 01bee2e32c245416616cc632ecf39cfbb565b809 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Mon, 6 Nov 2017 15:41:04 -0600 Subject: [PATCH] Refactor test_allocation_candidates For the sake of code deduplication, LOC reduction, and readability (improved signal:noise ratio), this change set implements some convenience methods in AllocationCandidatesTestCase for creation of placement artifacts such as resource providers, inventory, and allocations, as well as for extracting and comparing results of get_allocation_candidates. This only changes test code; and is just a refactor - no tests are added, removed, or substantively changed. Change-Id: I34527a3b2ac33ec3b1c6cce2c4a2163eea1ada4d --- .../db/test_allocation_candidates.py | 776 ++++++------------ 1 file changed, 232 insertions(+), 544 deletions(-) diff --git a/nova/tests/functional/db/test_allocation_candidates.py b/nova/tests/functional/db/test_allocation_candidates.py index 9f45e551e933..e0fae24bab66 100644 --- a/nova/tests/functional/db/test_allocation_candidates.py +++ b/nova/tests/functional/db/test_allocation_candidates.py @@ -10,11 +10,54 @@ # License for the specific language governing permissions and limitations # under the License. from nova import context +from nova import exception from nova.objects import fields from nova.objects import resource_provider as rp_obj from nova import test from nova.tests import fixtures -from nova.tests import uuidsentinel +from nova.tests import uuidsentinel as uuids + + +def _add_inventory(rp, rc, total, **kwargs): + kwargs.setdefault('max_unit', total) + inv = rp_obj.Inventory(rp._context, resource_provider=rp, + resource_class=rc, total=total, **kwargs) + inv.obj_set_defaults() + rp.add_inventory(inv) + + +def _set_traits(rp, *traits): + tlist = [] + for tname in traits: + try: + trait = rp_obj.Trait.get_by_name(rp._context, tname) + except exception.TraitNotFound: + trait = rp_obj.Trait(rp._context, name=tname) + trait.create() + tlist.append(trait) + rp.set_traits(rp_obj.TraitList(objects=tlist)) + + +def _provider_uuids_from_iterable(objs): + """Return the set of resource_provider.uuid from an iterable. + + :param objs: Iterable of any object with a resource_provider attribute + (e.g. an AllocationRequest.resource_requests or an + AllocationCandidates.provider_summaries). + """ + return set(obj.resource_provider.uuid for obj in objs) + + +def _find_summary_for_provider(p_sums, rp_uuid): + for summary in p_sums: + if summary.resource_provider.uuid == rp_uuid: + return summary + + +def _find_summary_for_resource(p_sum, rc_name): + for resource in p_sum.resources: + if resource.resource_class == rc_name: + return resource class AllocationCandidatesTestCase(test.NoDBTestCase): @@ -32,38 +75,70 @@ class AllocationCandidatesTestCase(test.NoDBTestCase): self.useFixture(fixtures.Database()) self.api_db = self.useFixture(fixtures.Database(database='api')) self.ctx = context.RequestContext('fake-user', 'fake-project') - - def _requested_resources(self): - # The resources we will request - resources = { + self.requested_resources = { fields.ResourceClass.VCPU: 1, fields.ResourceClass.MEMORY_MB: 64, fields.ResourceClass.DISK_GB: 1500, } - return resources + # For debugging purposes, populated by _create_provider and used by + # _validate_allocation_requests to make failure results more readable. + self.rp_uuid_to_name = {} - def _find_summary_for_provider(self, p_sums, rp_uuid): - for summary in p_sums: - if summary.resource_provider.uuid == rp_uuid: - return summary + def _get_allocation_candidates(self, resources=None): + # The resources we will request + if resources is None: + resources = self.requested_resources + return rp_obj.AllocationCandidates.get_by_filters( + self.ctx, + filters={ + 'resources': resources, + }, + ) - def _find_summary_for_resource(self, p_sum, rc_name): - for resource in p_sum.resources: - if resource.resource_class == rc_name: - return resource + def _create_provider(self, name, *aggs): + rp = rp_obj.ResourceProvider(self.ctx, name=name, + uuid=getattr(uuids, name)) + rp.create() + if aggs: + rp.set_aggregates(aggs) + self.rp_uuid_to_name[rp.uuid] = name + return rp - def _find_requests_for_provider(self, reqs, rp_uuid): - res = [] - for ar in reqs: + def _validate_allocation_requests(self, expected, candidates): + """Assert correctness of allocation requests in allocation candidates. + + This is set up to make it easy for the caller to specify the expected + result, to make that expected structure readable for someone looking at + the test case, and to make test failures readable for debugging. + + :param expected: A list of lists of tuples representing the expected + allocation requests, of the form: + [ + [(resource_provider_name, resource_class_name, resource_count), + ..., + ], + ... + ] + :param candidates: The result from AllocationCandidates.get_by_filters. + """ + # Extract/convert allocation requests from candidates + observed = [] + for ar in candidates.allocation_requests: + rrs = [] for rr in ar.resource_requests: - if rr.resource_provider.uuid == rp_uuid: - res.append(rr) - return res + rrs.append((self.rp_uuid_to_name[rr.resource_provider.uuid], + rr.resource_class, rr.amount)) + rrs.sort() + observed.append(rrs) + observed.sort() - def _find_request_for_resource(self, res_reqs, rc_name): - for rr in res_reqs: - if rr.resource_class == rc_name: - return rr + # Sort the guts of the expected structure + for rr in expected: + rr.sort() + expected.sort() + + # Now we ought to be able to compare them + self.assertEqual(expected, observed) def test_all_local(self): """Create some resource providers that can satisfy the request for @@ -71,107 +146,37 @@ class AllocationCandidatesTestCase(test.NoDBTestCase): allocation requests returned by AllocationCandidates correspond with each of these resource providers. """ - # Create two compute node providers with VCPU, RAM and local disk - cn1_uuid = uuidsentinel.cn1 - cn1 = rp_obj.ResourceProvider( - self.ctx, - name='cn1', - uuid=cn1_uuid, - ) - cn1.create() - - cn2_uuid = uuidsentinel.cn2 - cn2 = rp_obj.ResourceProvider( - self.ctx, - name='cn2', - uuid=cn2_uuid, - ) - cn2.create() - - cn3_uuid = uuidsentinel.cn3 - cn3 = rp_obj.ResourceProvider( - self.ctx, - name='cn3', - uuid=cn3_uuid - ) - cn3.create() - - for cn in (cn1, cn2, cn3): - vcpu = rp_obj.Inventory( - resource_provider=cn, - resource_class=fields.ResourceClass.VCPU, - total=24, - reserved=0, - min_unit=1, - max_unit=24, - step_size=1, - allocation_ratio=16.0, - ) - memory_mb = rp_obj.Inventory( - resource_provider=cn, - resource_class=fields.ResourceClass.MEMORY_MB, - total=32768, - reserved=0, - min_unit=64, - max_unit=32768, - step_size=64, - allocation_ratio=1.5, - ) - if cn.uuid == cn3_uuid: - disk_gb = rp_obj.Inventory( - resource_provider=cn, - resource_class=fields.ResourceClass.DISK_GB, - total=1000, - reserved=100, - min_unit=10, - max_unit=1000, - step_size=10, - allocation_ratio=1.0, - ) - else: - disk_gb = rp_obj.Inventory( - resource_provider=cn, - resource_class=fields.ResourceClass.DISK_GB, - total=2000, - reserved=100, - min_unit=10, - max_unit=2000, - step_size=10, - allocation_ratio=1.0, - ) - disk_gb.obj_set_defaults() - inv_list = rp_obj.InventoryList(objects=[ - vcpu, - memory_mb, - disk_gb, - ]) - cn.set_inventory(inv_list) + # Create three compute node providers with VCPU, RAM and local disk + for name in ('cn1', 'cn2', 'cn3'): + cn = self._create_provider(name) + _add_inventory(cn, fields.ResourceClass.VCPU, 24, + allocation_ratio=16.0) + _add_inventory(cn, fields.ResourceClass.MEMORY_MB, 32768, + min_unit=64, step_size=64, allocation_ratio=1.5) + total_gb = 1000 if name == 'cn3' else 2000 + _add_inventory(cn, fields.ResourceClass.DISK_GB, total_gb, + reserved=100, min_unit=10, step_size=10, + allocation_ratio=1.0) # Ask for the alternative placement possibilities and verify each # provider is returned - requested_resources = self._requested_resources() - p_alts = rp_obj.AllocationCandidates.get_by_filters( - self.ctx, - filters={ - 'resources': requested_resources, - }, - ) + alloc_cands = self._get_allocation_candidates() # Verify the provider summary information indicates 0 usage and # capacity calculated from above inventory numbers for both compute # nodes - p_sums = p_alts.provider_summaries - self.assertEqual(2, len(p_sums)) + # TODO(efried): Come up with a terse & readable way to validate + # provider summaries + p_sums = alloc_cands.provider_summaries - p_sum_rps = set([ps.resource_provider.uuid for ps in p_sums]) + self.assertEqual(set([uuids.cn1, uuids.cn2]), + _provider_uuids_from_iterable(p_sums)) - self.assertEqual(set([cn1_uuid, cn2_uuid]), p_sum_rps) - - cn1_p_sum = self._find_summary_for_provider(p_sums, cn1_uuid) + cn1_p_sum = _find_summary_for_provider(p_sums, uuids.cn1) self.assertIsNotNone(cn1_p_sum) self.assertEqual(3, len(cn1_p_sum.resources)) - cn1_p_sum_vcpu = self._find_summary_for_resource(cn1_p_sum, 'VCPU') + cn1_p_sum_vcpu = _find_summary_for_resource(cn1_p_sum, 'VCPU') self.assertIsNotNone(cn1_p_sum_vcpu) expected_capacity = (24 * 16.0) @@ -179,11 +184,11 @@ class AllocationCandidatesTestCase(test.NoDBTestCase): self.assertEqual(0, cn1_p_sum_vcpu.used) # Let's verify the disk for the second compute node - cn2_p_sum = self._find_summary_for_provider(p_sums, cn2_uuid) + cn2_p_sum = _find_summary_for_provider(p_sums, uuids.cn2) self.assertIsNotNone(cn2_p_sum) self.assertEqual(3, len(cn2_p_sum.resources)) - cn2_p_sum_disk = self._find_summary_for_resource(cn2_p_sum, 'DISK_GB') + cn2_p_sum_disk = _find_summary_for_resource(cn2_p_sum, 'DISK_GB') self.assertIsNotNone(cn2_p_sum_disk) expected_capacity = ((2000 - 100) * 1.0) @@ -196,27 +201,15 @@ class AllocationCandidatesTestCase(test.NoDBTestCase): # disk. The amounts of the requests should correspond to the requested # resource amounts in the filter:resources dict passed to # AllocationCandidates.get_by_filters(). - a_reqs = p_alts.allocation_requests - self.assertEqual(2, len(a_reqs)) - - a_req_rps = set() - for ar in a_reqs: - for rr in ar.resource_requests: - a_req_rps.add(rr.resource_provider.uuid) - - self.assertEqual(set([cn1_uuid, cn2_uuid]), a_req_rps) - - cn1_reqs = self._find_requests_for_provider(a_reqs, cn1_uuid) - # There should be a req object for each resource we have requested - self.assertEqual(3, len(cn1_reqs)) - - cn1_req_vcpu = self._find_request_for_resource(cn1_reqs, 'VCPU') - self.assertIsNotNone(cn1_req_vcpu) - self.assertEqual(requested_resources['VCPU'], cn1_req_vcpu.amount) - - cn2_req_disk = self._find_request_for_resource(cn1_reqs, 'DISK_GB') - self.assertIsNotNone(cn2_req_disk) - self.assertEqual(requested_resources['DISK_GB'], cn2_req_disk.amount) + expected = [ + [('cn1', fields.ResourceClass.VCPU, 1), + ('cn1', fields.ResourceClass.MEMORY_MB, 64), + ('cn1', fields.ResourceClass.DISK_GB, 1500)], + [('cn2', fields.ResourceClass.VCPU, 1), + ('cn2', fields.ResourceClass.MEMORY_MB, 64), + ('cn2', fields.ResourceClass.DISK_GB, 1500)], + ] + self._validate_allocation_requests(expected, alloc_cands) def test_local_with_shared_disk(self): """Create some resource providers that can satisfy the request for @@ -227,110 +220,48 @@ class AllocationCandidatesTestCase(test.NoDBTestCase): providers """ # The aggregate that will be associated to everything... - agg_uuid = uuidsentinel.agg + agg_uuid = uuids.agg - # Create two compute node providers with VCPU, RAM and NO local disk - cn1_uuid = uuidsentinel.cn1 - cn1 = rp_obj.ResourceProvider( - self.ctx, - name='cn1', - uuid=cn1_uuid, - ) - cn1.create() + # Create two compute node providers with VCPU, RAM and NO local disk, + # associated with the aggregate. + for name in ('cn1', 'cn2'): + cn = self._create_provider(name, agg_uuid) + _add_inventory(cn, fields.ResourceClass.VCPU, 24, + allocation_ratio=16.0) + _add_inventory(cn, fields.ResourceClass.MEMORY_MB, 1024, + min_unit=64, allocation_ratio=1.5) - cn2_uuid = uuidsentinel.cn2 - cn2 = rp_obj.ResourceProvider( - self.ctx, - name='cn2', - uuid=cn2_uuid, - ) - cn2.create() - - # Populate the two compute node providers with inventory, sans DISK_GB - for cn in (cn1, cn2): - vcpu = rp_obj.Inventory( - resource_provider=cn, - resource_class=fields.ResourceClass.VCPU, - total=24, - reserved=0, - min_unit=1, - max_unit=24, - step_size=1, - allocation_ratio=16.0, - ) - memory_mb = rp_obj.Inventory( - resource_provider=cn, - resource_class=fields.ResourceClass.MEMORY_MB, - total=1024, - reserved=0, - min_unit=64, - max_unit=1024, - step_size=1, - allocation_ratio=1.5, - ) - inv_list = rp_obj.InventoryList(objects=[vcpu, memory_mb]) - cn.set_inventory(inv_list) - - # Create the shared storage pool - ss_uuid = uuidsentinel.ss - ss = rp_obj.ResourceProvider( - self.ctx, - name='shared storage', - uuid=ss_uuid, - ) - ss.create() + # Create the shared storage pool, asociated with the same aggregate + ss = self._create_provider('shared storage', agg_uuid) # Give the shared storage pool some inventory of DISK_GB - disk_gb = rp_obj.Inventory( - resource_provider=ss, - resource_class=fields.ResourceClass.DISK_GB, - total=2000, - reserved=100, - min_unit=10, - max_unit=2000, - step_size=1, - allocation_ratio=1.0, - ) - inv_list = rp_obj.InventoryList(objects=[disk_gb]) - ss.set_inventory(inv_list) + _add_inventory(ss, fields.ResourceClass.DISK_GB, 2000, reserved=100, + min_unit=10) # Mark the shared storage pool as having inventory shared among any # provider associated via aggregate - t = rp_obj.Trait.get_by_name(self.ctx, "MISC_SHARES_VIA_AGGREGATE") - ss.set_traits(rp_obj.TraitList(objects=[t])) - - # Now associate the shared storage pool and both compute nodes with the - # same aggregate - cn1.set_aggregates([agg_uuid]) - cn2.set_aggregates([agg_uuid]) - ss.set_aggregates([agg_uuid]) + _set_traits(ss, "MISC_SHARES_VIA_AGGREGATE") # Ask for the alternative placement possibilities and verify each # compute node provider is listed in the allocation requests as well as # the shared storage pool provider - requested_resources = self._requested_resources() - p_alts = rp_obj.AllocationCandidates.get_by_filters( - self.ctx, - filters={ - 'resources': requested_resources, - }, - ) + alloc_cands = self._get_allocation_candidates() # Verify the provider summary information indicates 0 usage and # capacity calculated from above inventory numbers for both compute # nodes - p_sums = p_alts.provider_summaries - self.assertEqual(3, len(p_sums)) + # TODO(efried): Come up with a terse & readable way to validate + # provider summaries + p_sums = alloc_cands.provider_summaries - p_sum_rps = set([ps.resource_provider.uuid for ps in p_sums]) + self.assertEqual(set([uuids.cn1, uuids.cn2, ss.uuid]), + _provider_uuids_from_iterable(p_sums)) - self.assertEqual(set([cn1_uuid, cn2_uuid, ss_uuid]), p_sum_rps) - - cn1_p_sum = self._find_summary_for_provider(p_sums, cn1_uuid) + cn1_p_sum = _find_summary_for_provider(p_sums, uuids.cn1) self.assertIsNotNone(cn1_p_sum) self.assertEqual(2, len(cn1_p_sum.resources)) - cn1_p_sum_vcpu = self._find_summary_for_resource(cn1_p_sum, 'VCPU') + cn1_p_sum_vcpu = _find_summary_for_resource(cn1_p_sum, 'VCPU') self.assertIsNotNone(cn1_p_sum_vcpu) expected_capacity = (24 * 16.0) @@ -338,11 +269,11 @@ class AllocationCandidatesTestCase(test.NoDBTestCase): self.assertEqual(0, cn1_p_sum_vcpu.used) # Let's verify memory for the second compute node - cn2_p_sum = self._find_summary_for_provider(p_sums, cn2_uuid) + cn2_p_sum = _find_summary_for_provider(p_sums, uuids.cn2) self.assertIsNotNone(cn2_p_sum) self.assertEqual(2, len(cn2_p_sum.resources)) - cn2_p_sum_ram = self._find_summary_for_resource(cn2_p_sum, 'MEMORY_MB') + cn2_p_sum_ram = _find_summary_for_resource(cn2_p_sum, 'MEMORY_MB') self.assertIsNotNone(cn2_p_sum_ram) expected_capacity = (1024 * 1.5) @@ -350,11 +281,11 @@ class AllocationCandidatesTestCase(test.NoDBTestCase): self.assertEqual(0, cn2_p_sum_ram.used) # Let's verify only diks for the shared storage pool - ss_p_sum = self._find_summary_for_provider(p_sums, ss_uuid) + ss_p_sum = _find_summary_for_provider(p_sums, ss.uuid) self.assertIsNotNone(ss_p_sum) self.assertEqual(1, len(ss_p_sum.resources)) - ss_p_sum_disk = self._find_summary_for_resource(ss_p_sum, 'DISK_GB') + ss_p_sum_disk = _find_summary_for_resource(ss_p_sum, 'DISK_GB') self.assertIsNotNone(ss_p_sum_disk) expected_capacity = ((2000 - 100) * 1.0) @@ -369,64 +300,25 @@ class AllocationCandidatesTestCase(test.NoDBTestCase): # AllocationCandidates.get_by_filters(). The providers for VCPU and # MEMORY_MB should be the compute nodes while the provider for the # DISK_GB should be the shared storage pool - a_reqs = p_alts.allocation_requests - self.assertEqual(2, len(a_reqs)) - - a_req_rps = set() - for ar in a_reqs: - for rr in ar.resource_requests: - a_req_rps.add(rr.resource_provider.uuid) - - self.assertEqual(set([cn1_uuid, cn2_uuid, ss_uuid]), a_req_rps) - - cn1_reqs = self._find_requests_for_provider(a_reqs, cn1_uuid) - # There should be a req object for only VCPU and MEMORY_MB - self.assertEqual(2, len(cn1_reqs)) - - cn1_req_vcpu = self._find_request_for_resource(cn1_reqs, 'VCPU') - self.assertIsNotNone(cn1_req_vcpu) - self.assertEqual(requested_resources['VCPU'], cn1_req_vcpu.amount) - - cn2_reqs = self._find_requests_for_provider(a_reqs, cn2_uuid) - - # There should NOT be an allocation resource request that lists a - # compute node provider UUID for DISK_GB, since the shared storage pool - # is the thing that is providing the disk - cn1_req_disk = self._find_request_for_resource(cn1_reqs, 'DISK_GB') - self.assertIsNone(cn1_req_disk) - cn2_req_disk = self._find_request_for_resource(cn2_reqs, 'DISK_GB') - self.assertIsNone(cn2_req_disk) - - # Let's check the second compute node for MEMORY_MB - cn2_req_ram = self._find_request_for_resource(cn2_reqs, 'MEMORY_MB') - self.assertIsNotNone(cn2_req_ram) - self.assertEqual(requested_resources['MEMORY_MB'], cn2_req_ram.amount) - - # We should find the shared storage pool providing the DISK_GB for each - # of the allocation requests - ss_reqs = self._find_requests_for_provider(a_reqs, ss_uuid) - self.assertEqual(2, len(ss_reqs)) - - # Shared storage shouldn't be listed as providing anything but disk... - ss_req_ram = self._find_request_for_resource(ss_reqs, 'MEMORY_MB') - self.assertIsNone(ss_req_ram) - - ss_req_disk = self._find_request_for_resource(ss_reqs, 'DISK_GB') - self.assertIsNotNone(ss_req_disk) - self.assertEqual(requested_resources['DISK_GB'], ss_req_disk.amount) + expected = [ + [('cn1', fields.ResourceClass.VCPU, 1), + ('cn1', fields.ResourceClass.MEMORY_MB, 64), + ('shared storage', fields.ResourceClass.DISK_GB, 1500)], + [('cn2', fields.ResourceClass.VCPU, 1), + ('cn2', fields.ResourceClass.MEMORY_MB, 64), + ('shared storage', fields.ResourceClass.DISK_GB, 1500)], + ] + self._validate_allocation_requests(expected, alloc_cands) # Test for bug #1705071. We query for allocation candidates with a # request for ONLY the DISK_GB (the resource that is shared with # compute nodes) and no VCPU/MEMORY_MB. Before the fix for bug # #1705071, this resulted in a KeyError - p_alts = rp_obj.AllocationCandidates.get_by_filters( - self.ctx, - filters={ - 'resources': { - 'DISK_GB': 10, - } - }, + alloc_cands = self._get_allocation_candidates( + resources={ + 'DISK_GB': 10, + } ) # We should only have provider summary information for the sharing @@ -434,26 +326,16 @@ class AllocationCandidatesTestCase(test.NoDBTestCase): # allocated against for this request. In the future, we may look into # returning the shared-with providers in the provider summaries, but # that's a distant possibility. - p_sums = p_alts.provider_summaries - self.assertEqual(1, len(p_sums)) - - p_sum_rps = set([ps.resource_provider.uuid for ps in p_sums]) - - self.assertEqual(set([ss_uuid]), p_sum_rps) + self.assertEqual( + set([ss.uuid]), + _provider_uuids_from_iterable(alloc_cands.provider_summaries)) # The allocation_requests will only include the shared storage # provider because the only thing we're requesting to allocate is # against the provider of DISK_GB, which happens to be the shared # storage provider. - a_reqs = p_alts.allocation_requests - self.assertEqual(1, len(a_reqs)) - - a_req_rps = set() - for ar in a_reqs: - for rr in ar.resource_requests: - a_req_rps.add(rr.resource_provider.uuid) - - self.assertEqual(set([ss_uuid]), a_req_rps) + expected = [[('shared storage', fields.ResourceClass.DISK_GB, 10)]] + self._validate_allocation_requests(expected, alloc_cands) def test_local_with_shared_custom_resource(self): """Create some resource providers that can satisfy the request for @@ -464,50 +346,16 @@ class AllocationCandidatesTestCase(test.NoDBTestCase): VCPU/MEMORY_MB by the compute node providers """ # The aggregate that will be associated to everything... - agg_uuid = uuidsentinel.agg + agg_uuid = uuids.agg # Create two compute node providers with VCPU, RAM and NO local - # CUSTOM_MAGIC resources - cn1_uuid = uuidsentinel.cn1 - cn1 = rp_obj.ResourceProvider( - self.ctx, - name='cn1', - uuid=cn1_uuid, - ) - cn1.create() - - cn2_uuid = uuidsentinel.cn2 - cn2 = rp_obj.ResourceProvider( - self.ctx, - name='cn2', - uuid=cn2_uuid, - ) - cn2.create() - - # Populate the two compute node providers with inventory - for cn in (cn1, cn2): - vcpu = rp_obj.Inventory( - resource_provider=cn, - resource_class=fields.ResourceClass.VCPU, - total=24, - reserved=0, - min_unit=1, - max_unit=24, - step_size=1, - allocation_ratio=16.0, - ) - memory_mb = rp_obj.Inventory( - resource_provider=cn, - resource_class=fields.ResourceClass.MEMORY_MB, - total=1024, - reserved=0, - min_unit=64, - max_unit=1024, - step_size=1, - allocation_ratio=1.5, - ) - inv_list = rp_obj.InventoryList(objects=[vcpu, memory_mb]) - cn.set_inventory(inv_list) + # CUSTOM_MAGIC resources, associated with the aggregate. + for name in ('cn1', 'cn2'): + cn = self._create_provider(name, agg_uuid) + _add_inventory(cn, fields.ResourceClass.VCPU, 24, + allocation_ratio=16.0) + _add_inventory(cn, fields.ResourceClass.MEMORY_MB, 1024, + min_unit=64, allocation_ratio=1.5) # Create a custom resource called MAGIC magic_rc = rp_obj.ResourceClass( @@ -516,50 +364,16 @@ class AllocationCandidatesTestCase(test.NoDBTestCase): ) magic_rc.create() - # Create the shared provider that servers MAGIC - magic_p_uuid = uuidsentinel.magic_p - magic_p = rp_obj.ResourceProvider( - self.ctx, - name='shared custom resource provider', - uuid=magic_p_uuid, - ) - magic_p.create() - - # Give the provider some MAGIC - magic = rp_obj.Inventory( - resource_provider=magic_p, - resource_class=magic_rc.name, - total=2048, - reserved=1024, - min_unit=10, - max_unit=2048, - step_size=1, - allocation_ratio=1.0, - ) - inv_list = rp_obj.InventoryList(objects=[magic]) - magic_p.set_inventory(inv_list) + # Create the shared provider that serves CUSTOM_MAGIC, associated with + # the same aggregate + magic_p = self._create_provider('shared custom resource provider', + agg_uuid) + _add_inventory(magic_p, magic_rc.name, 2048, reserved=1024, + min_unit=10) # Mark the magic provider as having inventory shared among any provider # associated via aggregate - t = rp_obj.Trait( - self.ctx, - name="MISC_SHARES_VIA_AGGREGATE", - ) - # TODO(jaypipes): Once MISC_SHARES_VIA_AGGREGATE is a standard - # os-traits trait, we won't need to create() here. Instead, we will - # just do: - # t = rp_obj.Trait.get_by_name( - # self.context, - # "MISC_SHARES_VIA_AGGREGATE", - # ) - t.create() - magic_p.set_traits(rp_obj.TraitList(objects=[t])) - - # Now associate the shared custom resource provider and both compute - # nodes with the same aggregate - cn1.set_aggregates([agg_uuid]) - cn2.set_aggregates([agg_uuid]) - magic_p.set_aggregates([agg_uuid]) + _set_traits(magic_p, "MISC_SHARES_VIA_AGGREGATE") # The resources we will request requested_resources = { @@ -568,12 +382,8 @@ class AllocationCandidatesTestCase(test.NoDBTestCase): magic_rc.name: 512, } - p_alts = rp_obj.AllocationCandidates.get_by_filters( - self.ctx, - filters={ - 'resources': requested_resources, - }, - ) + alloc_cands = self._get_allocation_candidates( + resources=requested_resources) # Verify the allocation requests that are returned. There should be 2 # allocation requests, one for each compute node, containing 3 @@ -583,186 +393,64 @@ class AllocationCandidatesTestCase(test.NoDBTestCase): # AllocationCandidates.get_by_filters(). The providers for VCPU and # MEMORY_MB should be the compute nodes while the provider for the # MAGIC should be the shared custom resource provider. - a_reqs = p_alts.allocation_requests - self.assertEqual(2, len(a_reqs)) - - a_req_rps = set() - for ar in a_reqs: - for rr in ar.resource_requests: - a_req_rps.add(rr.resource_provider.uuid) - - self.assertEqual(set([cn1_uuid, cn2_uuid, magic_p_uuid]), a_req_rps) - - cn1_reqs = self._find_requests_for_provider(a_reqs, cn1_uuid) - # There should be a req object for only VCPU and MEMORY_MB - self.assertEqual(2, len(cn1_reqs)) - - cn1_req_vcpu = self._find_request_for_resource(cn1_reqs, 'VCPU') - self.assertIsNotNone(cn1_req_vcpu) - self.assertEqual(requested_resources['VCPU'], cn1_req_vcpu.amount) - - cn2_reqs = self._find_requests_for_provider(a_reqs, cn2_uuid) - - # There should NOT be an allocation resource request that lists a - # compute node provider UUID for MAGIC, since the shared - # custom provider is the thing that is providing the disk - cn1_req_disk = self._find_request_for_resource(cn1_reqs, magic_rc.name) - self.assertIsNone(cn1_req_disk) - cn2_req_disk = self._find_request_for_resource(cn2_reqs, magic_rc.name) - self.assertIsNone(cn2_req_disk) - - # Let's check the second compute node for MEMORY_MB - cn2_req_ram = self._find_request_for_resource(cn2_reqs, 'MEMORY_MB') - self.assertIsNotNone(cn2_req_ram) - self.assertEqual(requested_resources['MEMORY_MB'], cn2_req_ram.amount) - - # We should find the shared custom resource provider providing the - # MAGIC for each of the allocation requests - magic_p_reqs = self._find_requests_for_provider(a_reqs, magic_p_uuid) - self.assertEqual(2, len(magic_p_reqs)) - - # Shared custom resource provider shouldn't be listed as providing - # anything but MAGIC... - magic_p_req_ram = self._find_request_for_resource( - magic_p_reqs, 'MEMORY_MB') - self.assertIsNone(magic_p_req_ram) - - magic_p_req_magic = self._find_request_for_resource( - magic_p_reqs, magic_rc.name) - self.assertIsNotNone(magic_p_req_magic) - self.assertEqual( - requested_resources[magic_rc.name], magic_p_req_magic.amount) + expected = [ + [('cn1', fields.ResourceClass.VCPU, 1), + ('cn1', fields.ResourceClass.MEMORY_MB, 64), + ('shared custom resource provider', magic_rc.name, 512)], + [('cn2', fields.ResourceClass.VCPU, 1), + ('cn2', fields.ResourceClass.MEMORY_MB, 64), + ('shared custom resource provider', magic_rc.name, 512)], + ] + self._validate_allocation_requests(expected, alloc_cands) def test_mix_local_and_shared(self): # The aggregate that will be associated to shared storage pool - agg_uuid = uuidsentinel.agg + agg_uuid = uuids.agg # Create three compute node providers with VCPU and RAM, but only # the third compute node has DISK. The first two computes will - # share the storage from the shared storage pool - cn1_uuid = uuidsentinel.cn1 - cn1 = rp_obj.ResourceProvider( - self.ctx, - name='cn1', - uuid=cn1_uuid, - ) - cn1.create() - - cn2_uuid = uuidsentinel.cn2 - cn2 = rp_obj.ResourceProvider( - self.ctx, - name='cn2', - uuid=cn2_uuid, - ) - cn2.create() - - cn3_uuid = uuidsentinel.cn3 - cn3 = rp_obj.ResourceProvider( - self.ctx, - name='cn3', - uuid=cn3_uuid - ) - cn3.create() - - # Populate the two compute node providers with inventory - for cn in (cn1, cn2, cn3): - vcpu = rp_obj.Inventory( - resource_provider=cn, - resource_class=fields.ResourceClass.VCPU, - total=24, - reserved=0, - min_unit=1, - max_unit=24, - step_size=1, - allocation_ratio=16.0, - ) - memory_mb = rp_obj.Inventory( - resource_provider=cn, - resource_class=fields.ResourceClass.MEMORY_MB, - total=1024, - reserved=0, - min_unit=64, - max_unit=1024, - step_size=1, - allocation_ratio=1.5, - ) - disk_gb = rp_obj.Inventory( - resource_provider=cn3, - resource_class=fields.ResourceClass.DISK_GB, - total=2000, - reserved=100, - min_unit=10, - max_unit=2000, - step_size=1, - allocation_ratio=1.0, - ) - if cn == cn3: - inv_list = rp_obj.InventoryList( - objects=[vcpu, memory_mb, disk_gb]) + # share the storage from the shared storage pool. + for name in ('cn1', 'cn2', 'cn3'): + cn = self._create_provider(name) + _add_inventory(cn, fields.ResourceClass.VCPU, 24, + allocation_ratio=16.0) + _add_inventory(cn, fields.ResourceClass.MEMORY_MB, 1024, + min_unit=64, allocation_ratio=1.5) + if name == 'cn3': + _add_inventory(cn, fields.ResourceClass.DISK_GB, 2000, + reserved=100, min_unit=10) else: - inv_list = rp_obj.InventoryList(objects=[vcpu, memory_mb]) - cn.set_inventory(inv_list) + cn.set_aggregates([agg_uuid]) # Create the shared storage pool - ss_uuid = uuidsentinel.ss - ss = rp_obj.ResourceProvider( - self.ctx, - name='shared storage', - uuid=ss_uuid, - ) - ss.create() + ss = self._create_provider('shared storage') # Give the shared storage pool some inventory of DISK_GB - disk_gb = rp_obj.Inventory( - resource_provider=ss, - resource_class=fields.ResourceClass.DISK_GB, - total=2000, - reserved=100, - min_unit=10, - max_unit=2000, - step_size=1, - allocation_ratio=1.0, - ) - inv_list = rp_obj.InventoryList(objects=[disk_gb]) - ss.set_inventory(inv_list) + _add_inventory(ss, fields.ResourceClass.DISK_GB, 2000, reserved=100, + min_unit=10) - t = rp_obj.Trait.get_by_name(self.ctx, "MISC_SHARES_VIA_AGGREGATE") - ss.set_traits(rp_obj.TraitList(objects=[t])) + _set_traits(ss, "MISC_SHARES_VIA_AGGREGATE") - # Put the cn1, cn2 and ss in the same aggregate - cn1.set_aggregates([agg_uuid]) - cn2.set_aggregates([agg_uuid]) + # Put the ss RP in the same aggregate as the first two compute nodes ss.set_aggregates([agg_uuid]) - requested_resources = self._requested_resources() - p_alts = rp_obj.AllocationCandidates.get_by_filters( - self.ctx, - filters={ - 'resources': requested_resources, - }, - ) + alloc_cands = self._get_allocation_candidates() # Expect cn1, cn2, cn3 and ss in the summaries - p_sums = p_alts.provider_summaries - self.assertEqual(4, len(p_sums)) - - p_sum_rps = set([ps.resource_provider.uuid for ps in p_sums]) - - self.assertEqual(set([cn1_uuid, cn2_uuid, - ss_uuid, cn3_uuid]), - p_sum_rps) + self.assertEqual( + set([uuids.cn1, uuids.cn2, ss.uuid, uuids.cn3]), + _provider_uuids_from_iterable(alloc_cands.provider_summaries)) # Expect three allocation requests: (cn1, ss), (cn2, ss), (cn3) - a_reqs = p_alts.allocation_requests - self.assertEqual(3, len(a_reqs)) - - expected_ar = [] - for ar in a_reqs: - rr_set = set() - for rr in ar.resource_requests: - rr_set.add(rr.resource_provider.uuid) - expected_ar.append(rr_set) - - self.assertEqual(sorted(expected_ar), - sorted([set([cn1.uuid, ss.uuid]), - set([cn2.uuid, ss.uuid]), set([cn3.uuid])])) + expected = [ + [('cn1', fields.ResourceClass.VCPU, 1), + ('cn1', fields.ResourceClass.MEMORY_MB, 64), + ('shared storage', fields.ResourceClass.DISK_GB, 1500)], + [('cn2', fields.ResourceClass.VCPU, 1), + ('cn2', fields.ResourceClass.MEMORY_MB, 64), + ('shared storage', fields.ResourceClass.DISK_GB, 1500)], + [('cn3', fields.ResourceClass.VCPU, 1), + ('cn3', fields.ResourceClass.MEMORY_MB, 64), + ('cn3', fields.ResourceClass.DISK_GB, 1500)], + ] + self._validate_allocation_requests(expected, alloc_cands)