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, 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 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_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, 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 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 ID. """ 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, CONF.placement.incomplete_consumer_project_id) self.mock_user_get.assert_called_once_with( self.ctx, 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 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 = 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. util.ensure_consumer(context, consumer_uuid, data.get('project_id'), data.get('user_id'), data.get('consumer_generation'), want_version) allocations = rp_obj.AllocationList.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 = 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) allocations = rp_obj.AllocationList( context, objects=allocation_objects) def _create_allocations(alloc_list): try: alloc_list.replace_all() LOG.debug("Successfully wrote allocations %s", alloc_list) except Exception: if created_new_consumer: delete_consumers([consumer]) raise try: _create_allocations(allocations) # 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
def set_allocations(req): context = req.environ['placement.context'] context.can(policies.ALLOC_MANAGE) want_version = req.environ[microversion.MICROVERSION_ENVIRON] want_schema = schema.POST_ALLOCATIONS_V1_13 if want_version.matches((1, 28)): want_schema = schema.POST_ALLOCATIONS_V1_28 data = util.extract_json(req.body, want_schema) # 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 = 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. _delete_consumers(new_consumers_created) raise # Create a sequence of allocation objects to be used in one # AllocationList.replace_all() call, which will mean all the changes # happen within a single transaction and with resource provider # and consumer generations (if applicable) check all in one go. allocations = create_allocation_list(context, data, consumers) def _create_allocations(alloc_list): try: alloc_list.replace_all() LOG.debug("Successfully wrote allocations %s", alloc_list) except Exception: _delete_consumers(new_consumers_created) raise try: _create_allocations(allocations) except exception.NotFound as exc: raise webob.exc.HTTPBadRequest( _("Unable to allocate inventory %(error)s") % {'error': exc}) except exception.InvalidInventory as exc: # InvalidInventory is a parent for several exceptions that # indicate either that Inventory is not present, or that # capacity limits have been exceeded. 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