def test_delete_all_with_multiple_consumers(self): """Tests fix for LP #1781430 where alloc_obj.delete_all() when issued for a list of allocations returned by alloc_obj.get_by_resource_provider() where the resource provider had multiple consumers allocated against it, left the DB in an inconsistent state. """ # Create a single resource provider and allocate resources for two # instances from it. Then grab all the provider's allocations with # alloc_obj.get_all_by_resource_provider() and attempt to delete # them all with alloc_obj.delete_all(). After which, another call # to alloc_obj.get_all_by_resource_provider() should return an # empty list. cn1 = self._create_provider('cn1') tb.add_inventory(cn1, 'VCPU', 8) c1_uuid = uuidsentinel.consumer1 c2_uuid = uuidsentinel.consumer2 for c_uuid in (c1_uuid, c2_uuid): self.allocate_from_provider(cn1, 'VCPU', 1, consumer_id=c_uuid) allocs = alloc_obj.get_all_by_resource_provider(self.ctx, cn1) self.assertEqual(2, len(allocs)) alloc_obj.delete_all(self.ctx, allocs) allocs = alloc_obj.get_all_by_resource_provider(self.ctx, cn1) self.assertEqual(0, len(allocs))
def test_create_list_and_delete_allocation(self): rp, _ = self._make_allocation(tb.DISK_INVENTORY, tb.DISK_ALLOCATION) allocations = alloc_obj.get_all_by_resource_provider(self.ctx, rp) self.assertEqual(1, len(allocations)) self.assertEqual(tb.DISK_ALLOCATION['used'], allocations[0].used) alloc_obj.delete_all(self.ctx, allocations) allocations = alloc_obj.get_all_by_resource_provider(self.ctx, rp) self.assertEqual(0, len(allocations))
def test_create_incomplete_consumers(self): """Test the online data migration that creates incomplete consumer records along with the incomplete consumer project/user records. """ self._create_incomplete_allocations(self.ctx) # We do a "really online" online data migration for incomplete # consumers when calling alloc_obj.get_all_by_consumer_id() and # alloc_obj.get_all_by_resource_provider() and there are still # incomplete consumer records. So, to simulate a situation where the # operator has yet to run the nova-manage online_data_migration CLI # tool completely, we first call # consumer_obj.create_incomplete_consumers() with a batch size of 1. # This should mean there will be two allocation records still remaining # with a missing consumer record (since we create 3 total to begin # with). We then query the allocations table directly to grab that # consumer UUID in the allocations table that doesn't refer to a # consumer table record and call # alloc_obj.get_all_by_consumer_id() with that consumer UUID. This # should create the remaining missing consumer record "inline" in the # alloc_obj.get_all_by_consumer_id() method. # After that happens, there should still be a single allocation record # that is missing a relation to the consumers table. We call the # alloc_obj.get_all_by_resource_provider() method and verify that # method cleans up the remaining incomplete consumers relationship. res = consumer_obj.create_incomplete_consumers(self.ctx, 1) self.assertEqual((1, 1), res) # Grab the consumer UUID for the allocation record with a # still-incomplete consumer record. res = _get_allocs_with_no_consumer_relationship(self.ctx) self.assertEqual(2, len(res)) still_missing = res[0][0] alloc_obj.get_all_by_consumer_id(self.ctx, still_missing) # There should still be a single missing consumer relationship. Let's # grab that and call alloc_obj.get_all_by_resource_provider() # which should clean that last one up for us. res = _get_allocs_with_no_consumer_relationship(self.ctx) self.assertEqual(1, len(res)) still_missing = res[0][0] rp1 = rp_obj.ResourceProvider(self.ctx, id=1) alloc_obj.get_all_by_resource_provider(self.ctx, rp1) # get_all_by_resource_provider() should have auto-completed the still # missing consumer record and _check_incomplete_consumers() should # assert correctly that there are no more incomplete consumer records. self._check_incomplete_consumers(self.ctx) res = consumer_obj.create_incomplete_consumers(self.ctx, 10) self.assertEqual((0, 0), res)
def test_get_all_by_resource_provider(self): rp, allocation = self._make_allocation(tb.DISK_INVENTORY, tb.DISK_ALLOCATION) allocations = alloc_obj.get_all_by_resource_provider(self.ctx, rp) self.assertEqual(1, len(allocations)) self.assertEqual(rp.id, allocations[0].resource_provider.id) self.assertEqual(allocation.resource_provider.id, allocations[0].resource_provider.id)
def test_create_incomplete_consumers_multiple_allocs_per_consumer(self): """Tests that missing consumer records are created when listing allocations against a resource provider or running the online data migration routine when the consumers have multiple allocations on the same provider. """ self._create_incomplete_allocations(self.ctx, num_of_consumer_allocs=2) # Run the online data migration to migrate one consumer. The batch size # needs to be large enough to hit more than one consumer for this test # where each consumer has two allocations. res = consumer_obj.create_incomplete_consumers(self.ctx, 2) self.assertEqual((2, 2), res) # Migrate the rest by listing allocations on the resource provider. rp1 = rp_obj.ResourceProvider(self.ctx, id=1) alloc_obj.get_all_by_resource_provider(self.ctx, rp1) self._check_incomplete_consumers(self.ctx) res = consumer_obj.create_incomplete_consumers(self.ctx, 10) self.assertEqual((0, 0), res)
def test_get_all_by_resource_provider(self, mock_get_allocations_from_db): rp = rp_obj.ResourceProvider(self.context, id=_RESOURCE_PROVIDER_ID, uuid=uuids.resource_provider) allocations = alloc_obj.get_all_by_resource_provider(self.context, rp) self.assertEqual(1, len(allocations)) mock_get_allocations_from_db.assert_called_once_with( self.context, rp.id) self.assertEqual(_ALLOCATION_DB['used'], allocations[0].used) self.assertEqual(_ALLOCATION_DB['created_at'], allocations[0].created_at) self.assertEqual(_ALLOCATION_DB['updated_at'], allocations[0].updated_at)
def list_for_resource_provider(req): """List allocations associated with a resource provider.""" # TODO(cdent): On a shared resource provider (for example a # giant disk farm) this list could get very long. At the moment # we have no facility for limiting the output. Given that we are # using a dict of dicts for the output we are potentially limiting # ourselves in terms of sorting and filtering. context = req.environ['placement.context'] context.can(policies.RP_ALLOC_LIST) want_version = req.environ[microversion.MICROVERSION_ENVIRON] uuid = util.wsgi_path_item(req.environ, 'uuid') # confirm existence of resource provider so we get a reasonable # 404 instead of empty list try: rp = rp_obj.ResourceProvider.get_by_uuid(context, uuid) except exception.NotFound as exc: raise webob.exc.HTTPNotFound( "Resource provider '%(rp_uuid)s' not found: %(error)s" % { 'rp_uuid': uuid, 'error': exc }) allocs = alloc_obj.get_all_by_resource_provider(context, rp) output = _serialize_allocations_for_resource_provider( allocs, rp, want_version) last_modified = _last_modified_from_allocations(allocs, want_version) allocations_json = jsonutils.dumps(output) response = req.response response.status = 200 response.body = encodeutils.to_utf8(allocations_json) response.content_type = 'application/json' if want_version.matches((1, 15)): response.last_modified = last_modified response.cache_control = 'no-cache' return response
def test_multi_provider_allocation(self): """Tests that an allocation that includes more than one resource provider can be created, listed and deleted properly. Bug #1707669 highlighted a situation that arose when attempting to remove part of an allocation for a source host during a resize operation where the exiting allocation was not being properly deleted. """ cn_source = self._create_provider('cn_source') cn_dest = self._create_provider('cn_dest') # Add same inventory to both source and destination host for cn in (cn_source, cn_dest): tb.add_inventory(cn, orc.VCPU, 24, allocation_ratio=16.0) tb.add_inventory(cn, orc.MEMORY_MB, 1024, min_unit=64, max_unit=1024, step_size=64, allocation_ratio=1.5) # Create a consumer representing the instance inst_consumer = consumer_obj.Consumer( self.ctx, uuid=uuidsentinel.instance, user=self.user_obj, project=self.project_obj) inst_consumer.create() # Now create an allocation that represents a move operation where the # scheduler has selected cn_dest as the target host and created a # "doubled-up" allocation for the duration of the move operation alloc_list = [ alloc_obj.Allocation( consumer=inst_consumer, resource_provider=cn_source, resource_class=orc.VCPU, used=1), alloc_obj.Allocation( consumer=inst_consumer, resource_provider=cn_source, resource_class=orc.MEMORY_MB, used=256), alloc_obj.Allocation( consumer=inst_consumer, resource_provider=cn_dest, resource_class=orc.VCPU, used=1), alloc_obj.Allocation( consumer=inst_consumer, resource_provider=cn_dest, resource_class=orc.MEMORY_MB, used=256), ] alloc_obj.replace_all(self.ctx, alloc_list) src_allocs = alloc_obj.get_all_by_resource_provider( self.ctx, cn_source) self.assertEqual(2, len(src_allocs)) dest_allocs = alloc_obj.get_all_by_resource_provider(self.ctx, cn_dest) self.assertEqual(2, len(dest_allocs)) consumer_allocs = alloc_obj.get_all_by_consumer_id( self.ctx, uuidsentinel.instance) self.assertEqual(4, len(consumer_allocs)) # Validate that when we create an allocation for a consumer that we # delete any existing allocation and replace it with what the new. # Here, we're emulating the step that occurs on confirm_resize() where # the source host pulls the existing allocation for the instance and # removes any resources that refer to itself and saves the allocation # back to placement new_alloc_list = [ alloc_obj.Allocation( consumer=inst_consumer, resource_provider=cn_dest, resource_class=orc.VCPU, used=1), alloc_obj.Allocation( consumer=inst_consumer, resource_provider=cn_dest, resource_class=orc.MEMORY_MB, used=256), ] alloc_obj.replace_all(self.ctx, new_alloc_list) src_allocs = alloc_obj.get_all_by_resource_provider( self.ctx, cn_source) self.assertEqual(0, len(src_allocs)) dest_allocs = alloc_obj.get_all_by_resource_provider( self.ctx, cn_dest) self.assertEqual(2, len(dest_allocs)) consumer_allocs = alloc_obj.get_all_by_consumer_id( self.ctx, uuidsentinel.instance) self.assertEqual(2, len(consumer_allocs))
def test_create_exceeding_capacity_allocation(self): """Tests on a list of allocations which contains an invalid allocation exceeds resource provider's capacity. Expect InvalidAllocationCapacityExceeded to be raised and all allocations in the list should not be applied. """ empty_rp = self._create_provider('empty_rp') full_rp = self._create_provider('full_rp') for rp in (empty_rp, full_rp): tb.add_inventory(rp, orc.VCPU, 24, allocation_ratio=16.0) tb.add_inventory(rp, orc.MEMORY_MB, 1024, min_unit=64, max_unit=1024, step_size=64) # Create a consumer representing the instance inst_consumer = consumer_obj.Consumer( self.ctx, uuid=uuidsentinel.instance, user=self.user_obj, project=self.project_obj) inst_consumer.create() # First create a allocation to consume full_rp's resource. alloc_list = [ alloc_obj.Allocation( consumer=inst_consumer, resource_provider=full_rp, resource_class=orc.VCPU, used=12), alloc_obj.Allocation( consumer=inst_consumer, resource_provider=full_rp, resource_class=orc.MEMORY_MB, used=1024) ] alloc_obj.replace_all(self.ctx, alloc_list) # Create a consumer representing the second instance inst2_consumer = consumer_obj.Consumer( self.ctx, uuid=uuidsentinel.instance2, user=self.user_obj, project=self.project_obj) inst2_consumer.create() # Create an allocation list consisting of valid requests and an invalid # request exceeding the memory full_rp can provide. alloc_list = [ alloc_obj.Allocation( consumer=inst2_consumer, resource_provider=empty_rp, resource_class=orc.VCPU, used=12), alloc_obj.Allocation( consumer=inst2_consumer, resource_provider=empty_rp, resource_class=orc.MEMORY_MB, used=512), alloc_obj.Allocation( consumer=inst2_consumer, resource_provider=full_rp, resource_class=orc.VCPU, used=12), alloc_obj.Allocation( consumer=inst2_consumer, resource_provider=full_rp, resource_class=orc.MEMORY_MB, used=512), ] self.assertRaises(exception.InvalidAllocationCapacityExceeded, alloc_obj.replace_all, self.ctx, alloc_list) # Make sure that allocations of both empty_rp and full_rp remain # unchanged. allocations = alloc_obj.get_all_by_resource_provider(self.ctx, full_rp) self.assertEqual(2, len(allocations)) allocations = alloc_obj.get_all_by_resource_provider( self.ctx, empty_rp) self.assertEqual(0, len(allocations))