def test_consumer_create_exists_different_consumer_type_supplied(self): """Tests that we update a consumer's type ID if the one supplied by a racing request is different than the one in the existing (recently created) record. """ proj = project_obj.Project(self.ctx, id=1, external_id=self.project_id) self.mock_project_get.return_value = proj user = user_obj.User(self.ctx, id=1, external_id=self.user_id) self.mock_user_get.return_value = user # Request A recently created consumer has type ID = 1 consumer = consumer_obj.Consumer( self.ctx, id=1, project=proj, user=user, generation=1, consumer_type_id=1, uuid=uuidsentinel.consumer) self.mock_consumer_get.return_value = consumer # Request B will encounter ConsumerExists as Request A just created it self.mock_consumer_create.side_effect = ( exception.ConsumerExists(uuid=uuidsentinel.consumer)) consumer_gen = 1 consumer, created_new_consumer, request_attr = util.ensure_consumer( self.ctx, self.consumer_id, self.project_id, self.user_id, consumer_gen, 'TYPE', self.cons_type_req_version) util.update_consumers([consumer], {consumer.uuid: request_attr}) # Expect 1 call to update() to update to the supplied consumer type ID self.mock_consumer_update.assert_called_once_with() # Consumer should have the new consumer type from the cache self.assertEqual( self.ctx.ct_cache.id_from_string.return_value, consumer.consumer_type_id)
def test_existing_consumer_different_consumer_type_supplied(self): """Tests that we update a consumer's type ID if the one supplied by the user is different than the one in the existing record. """ proj = project_obj.Project(self.ctx, id=1, external_id=self.project_id) self.mock_project_get.return_value = proj user = user_obj.User(self.ctx, id=1, external_id=self.user_id) self.mock_user_get.return_value = user # Consumer currently has type ID = 1 consumer = consumer_obj.Consumer( self.ctx, id=1, project=proj, user=user, generation=1, consumer_type_id=1) self.mock_consumer_get.return_value = consumer consumer_gen = 1 consumer, created_new_consumer, request_attr = util.ensure_consumer( self.ctx, self.consumer_id, self.project_id, self.user_id, consumer_gen, 'TYPE', self.cons_type_req_version) util.update_consumers([consumer], {consumer.uuid: request_attr}) # Expect 1 call to update() to update to the supplied consumer type ID self.mock_consumer_update.assert_called_once_with() # Consumer should have the new consumer type from the cache self.assertEqual( self.ctx.ct_cache.id_from_string.return_value, consumer.consumer_type_id)
def test_existing_project_no_existing_consumer_before_gen_success(self): """Check that if we find an existing project and user, that we use those found objects in creating the consumer. Do not require a consumer generation before the appropriate microversion. """ proj = project_obj.Project(self.ctx, uuid=self.project_id) self.mock_project_get.return_value = proj user = user_obj.User(self.ctx, uuid=self.user_id) self.mock_user_get.return_value = user self.mock_consumer_get.side_effect = exception.NotFound consumer_gen = None # should be ignored util.ensure_consumer(self.ctx, self.consumer_id, self.project_id, self.user_id, consumer_gen, self.before_version) self.mock_project_create.assert_not_called() self.mock_user_create.assert_not_called() self.mock_consumer_create.assert_called_once()
def test_no_existing_project_user_consumer_after_gen_success(self): """Tests that we require a consumer_generation=None after the appropriate microversion. """ self.mock_project_get.side_effect = exception.NotFound self.mock_user_get.side_effect = exception.NotFound self.mock_consumer_get.side_effect = exception.NotFound consumer_gen = None # should NOT be ignored (and None is expected) util.ensure_consumer(self.ctx, self.consumer_id, self.project_id, self.user_id, consumer_gen, self.after_version) self.mock_project_get.assert_called_once_with(self.ctx, self.project_id) self.mock_user_get.assert_called_once_with(self.ctx, self.user_id) self.mock_consumer_get.assert_called_once_with(self.ctx, self.consumer_id) self.mock_project_create.assert_called_once() self.mock_user_create.assert_called_once() self.mock_consumer_create.assert_called_once()
def test_no_existing_project_user_consumer_before_gen_success(self): """Tests that we don't require a consumer_generation=None before the appropriate microversion. """ self.mock_project_get.side_effect = exception.NotFound self.mock_user_get.side_effect = exception.NotFound self.mock_consumer_get.side_effect = exception.NotFound consumer_gen = 1 # should be ignored util.ensure_consumer(self.ctx, self.consumer_id, self.project_id, self.user_id, consumer_gen, self.before_version) self.mock_project_get.assert_called_once_with(self.ctx, self.project_id) self.mock_user_get.assert_called_once_with(self.ctx, self.user_id) self.mock_consumer_get.assert_called_once_with(self.ctx, self.consumer_id) self.mock_project_create.assert_called_once() self.mock_user_create.assert_called_once() self.mock_consumer_create.assert_called_once()
def test_no_existing_project_user_consumer_use_incomplete(self): """Verify that if the project_id arg is None, that we fall back to the CONF options for incomplete project and user UUID. """ self.mock_project_get.side_effect = exception.NotFound self.mock_user_get.side_effect = exception.NotFound self.mock_consumer_get.side_effect = exception.NotFound consumer_gen = None # should NOT be ignored (and None is expected) util.ensure_consumer(self.ctx, self.consumer_id, None, None, consumer_gen, self.before_version) self.mock_project_get.assert_called_once_with( self.ctx, self.conf.placement.incomplete_consumer_project_id) self.mock_user_get.assert_called_once_with( self.ctx, self.conf.placement.incomplete_consumer_user_id) self.mock_consumer_get.assert_called_once_with(self.ctx, self.consumer_id) self.mock_project_create.assert_called_once() self.mock_user_create.assert_called_once() self.mock_consumer_create.assert_called_once()
def test_existing_consumer_after_gen_matches_supplied_gen(self): """Tests that we require a consumer_generation after the appropriate microversion and that when the consumer already exists, then we ensure a matching generation is supplied """ proj = project_obj.Project(self.ctx, id=1, external_id=self.project_id) self.mock_project_get.return_value = proj user = user_obj.User(self.ctx, id=1, external_id=self.user_id) self.mock_user_get.return_value = user consumer = consumer_obj.Consumer( self.ctx, id=1, project=proj, user=user, generation=2) self.mock_consumer_get.return_value = consumer consumer_gen = 2 # should NOT be ignored (and 2 is expected) util.ensure_consumer( self.ctx, self.consumer_id, self.project_id, self.user_id, consumer_gen, 'TYPE', self.after_version) self.mock_project_create.assert_not_called() self.mock_user_create.assert_not_called() self.mock_consumer_create.assert_not_called()
def inspect_consumers(context, data, want_version): """Look at consumer data in allocations and create consumers as needed. Keep a record of the consumers that are created in case they need to be removed later. If an exception is raised by ensure_consumer, commonly HTTPConflict but also anything else, the newly created consumers will be deleted and the exception reraised to the caller. :param context: The placement context. :param data: A dictionary of multiple allocations by consumer uuid. :param want_version: the microversion matcher. :return: A 3-tuple of (a dict of all consumer objects (by consumer uuid), a list of those consumer objects which are new, a dict of RequestAttr objects (by consumer_uuid)) """ # First, ensure that all consumers referenced in the payload actually # exist. And if not, create them. Keep a record of auto-created consumers # so we can clean them up if the end allocation replace_all() fails. consumers = {} # dict of Consumer objects, keyed by consumer UUID new_consumers_created = [] # Save requested attributes in order to do an update later in the same # database transaction as AllocationList.replace_all() so that rollbacks # can happen properly. Consumer table updates are guarded by the # generation, so we can't necessarily save all of the original attribute # values and write them back into the table in the event of an exception. # If the generation doesn't match, Consumer.update() is a no-op. requested_attrs = {} for consumer_uuid in data: project_id = data[consumer_uuid]['project_id'] user_id = data[consumer_uuid]['user_id'] consumer_generation = data[consumer_uuid].get('consumer_generation') consumer_type = data[consumer_uuid].get('consumer_type') try: consumer, new_consumer_created, request_attr = ( data_util.ensure_consumer(context, consumer_uuid, project_id, user_id, consumer_generation, consumer_type, want_version)) if new_consumer_created: new_consumers_created.append(consumer) consumers[consumer_uuid] = consumer requested_attrs[consumer_uuid] = request_attr except Exception: # If any errors (for instance, a consumer generation conflict) # occur when ensuring consumer records above, make sure we delete # any auto-created consumers. with excutils.save_and_reraise_exception(): delete_consumers(new_consumers_created) return consumers, new_consumers_created, requested_attrs
def inspect_consumers(context, data, want_version): """Look at consumer data in allocations and create consumers as needed. Keep a record of the consumers that are created in case they need to be removed later. If an exception is raised by ensure_consumer, commonly HTTPConflict but also anything else, the newly created consumers will be deleted and the exception reraised to the caller. :param context: The placement context. :param data: A dictionary of multiple allocations by consumer uuid. :param want_version: the microversion matcher. :return: A tuple of a dict of all consumer objects (by consumer uuid) and a list of those consumer objects which are new. """ # First, ensure that all consumers referenced in the payload actually # exist. And if not, create them. Keep a record of auto-created consumers # so we can clean them up if the end allocation replace_all() fails. consumers = {} # dict of Consumer objects, keyed by consumer UUID new_consumers_created = [] for consumer_uuid in data: project_id = data[consumer_uuid]['project_id'] user_id = data[consumer_uuid]['user_id'] consumer_generation = data[consumer_uuid].get('consumer_generation') try: consumer, new_consumer_created = data_util.ensure_consumer( context, consumer_uuid, project_id, user_id, consumer_generation, want_version) if new_consumer_created: new_consumers_created.append(consumer) consumers[consumer_uuid] = consumer except Exception: # If any errors (for instance, a consumer generation conflict) # occur when ensuring consumer records above, make sure we delete # any auto-created consumers. with excutils.save_and_reraise_exception(): delete_consumers(new_consumers_created) return consumers, new_consumers_created
def _set_allocations_for_consumer(req, schema): context = req.environ['placement.context'] context.can(policies.ALLOC_UPDATE) consumer_uuid = util.wsgi_path_item(req.environ, 'consumer_uuid') if not uuidutils.is_uuid_like(consumer_uuid): raise webob.exc.HTTPBadRequest( 'Malformed consumer_uuid: %(consumer_uuid)s' % {'consumer_uuid': consumer_uuid}) consumer_uuid = str(uuid.UUID(consumer_uuid)) data = util.extract_json(req.body, schema) allocation_data = data['allocations'] # Normalize allocation data to dict. want_version = req.environ[microversion.MICROVERSION_ENVIRON] if not want_version.matches((1, 12)): allocations_dict = {} # Allocation are list-ish, transform to dict-ish for allocation in allocation_data: resource_provider_uuid = allocation['resource_provider']['uuid'] allocations_dict[resource_provider_uuid] = { 'resources': allocation['resources'] } allocation_data = allocations_dict allocation_objects = [] # Consumer object saved in case we need to delete the auto-created consumer # record consumer = None # Whether we created a new consumer record created_new_consumer = False if not allocation_data: # The allocations are empty, which means wipe them out. Internal # to the allocation object this is signalled by a used value of 0. # We still need to verify the consumer's generation, though, which # we do in _ensure_consumer() # NOTE(jaypipes): This will only occur 1.28+. The JSONSchema will # prevent an empty allocations object from being passed when there is # no consumer generation, so this is safe to do. data_util.ensure_consumer( context, consumer_uuid, data.get('project_id'), data.get('user_id'), data.get('consumer_generation'), want_version) allocations = alloc_obj.get_all_by_consumer_id(context, consumer_uuid) for allocation in allocations: allocation.used = 0 allocation_objects.append(allocation) else: # If the body includes an allocation for a resource provider # that does not exist, raise a 400. rp_objs = _resource_providers_by_uuid(context, allocation_data.keys()) consumer, created_new_consumer = data_util.ensure_consumer( context, consumer_uuid, data.get('project_id'), data.get('user_id'), data.get('consumer_generation'), want_version) for resource_provider_uuid, allocation in allocation_data.items(): resource_provider = rp_objs[resource_provider_uuid] new_allocations = _new_allocations(context, resource_provider, consumer, allocation['resources']) allocation_objects.extend(new_allocations) def _create_allocations(alloc_list): try: alloc_obj.replace_all(context, alloc_list) LOG.debug("Successfully wrote allocations %s", alloc_list) except Exception: with excutils.save_and_reraise_exception(): if created_new_consumer: delete_consumers([consumer]) try: _create_allocations(allocation_objects) # InvalidInventory is a parent for several exceptions that # indicate either that Inventory is not present, or that # capacity limits have been exceeded. except exception.NotFound as exc: raise webob.exc.HTTPBadRequest( "Unable to allocate inventory for consumer %(consumer_uuid)s: " "%(error)s" % {'consumer_uuid': consumer_uuid, 'error': exc}) except exception.InvalidInventory as exc: raise webob.exc.HTTPConflict( 'Unable to allocate inventory: %(error)s' % {'error': exc}) except exception.ConcurrentUpdateDetected as exc: raise webob.exc.HTTPConflict( 'Inventory and/or allocations changed while attempting to ' 'allocate: %(error)s' % {'error': exc}, comment=errors.CONCURRENT_UPDATE) req.response.status = 204 req.response.content_type = None return req.response