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 get_all_by_resource_provider(context, rp): _create_incomplete_consumers_for_provider(context, rp.id) db_allocs = _get_allocations_by_provider_id(context, rp.id) # Build up a list of Allocation objects, setting the Allocation object # fields to the same-named database record field we got from # _get_allocations_by_provider_id(). We already have the # ResourceProvider object so we just pass that object to the Allocation # object constructor as-is objs = [] for rec in db_allocs: consumer = consumer_obj.Consumer( context, id=rec['consumer_id'], uuid=rec['consumer_uuid'], generation=rec['consumer_generation'], project=project_obj.Project( context, id=rec['project_id'], external_id=rec['project_external_id']), user=user_obj.User(context, id=rec['user_id'], external_id=rec['user_external_id'])) objs.append( Allocation(id=rec['id'], resource_provider=rp, resource_class=rc_cache.RC_CACHE.string_from_id( rec['resource_class_id']), consumer=consumer, used=rec['used'], created_at=rec['created_at'], updated_at=rec['updated_at'])) return objs
def test_update(self): """Tests the scenario where a user supplies a different project/user ID for an allocation's consumer and we call Consumer.update() to save that information to the consumers table. """ # First, create the consumer with the "fake-user" and "fake-project" # user/project in the base test class's setUp c = consumer_obj.Consumer(self.ctx, uuid=uuids.consumer, user=self.user_obj, project=self.project_obj) c.create() c = consumer_obj.Consumer.get_by_uuid(self.ctx, uuids.consumer) self.assertEqual(self.project_obj.id, c.project.id) self.assertEqual(self.user_obj.id, c.user.id) # Now change the consumer's project and user to a different project another_user = user_obj.User(self.ctx, external_id='another-user') another_user.create() another_proj = project_obj.Project(self.ctx, external_id='another-project') another_proj.create() c.project = another_proj c.user = another_user c.update() c = consumer_obj.Consumer.get_by_uuid(self.ctx, uuids.consumer) self.assertEqual(another_proj.id, c.project.id) self.assertEqual(another_user.id, c.user.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_create_and_get(self): p = project_obj.Project(self.ctx, external_id='another-project') p.create() p = project_obj.Project.get_by_external_id(self.ctx, 'another-project') # Project ID == 1 is fake-project created in setup self.assertEqual(2, p.id) self.assertRaises(exception.ProjectExists, p.create)
def fake_get_project(cls, ctx, external_id): if not hasattr(fake_get_project, 'called'): proj = project_obj.Project(ctx, external_id=external_id) proj.create() fake_get_project.called = True raise exception.ProjectNotFound(external_id) else: return real_get_project(ctx, external_id)
def _from_db_object(ctx, target, source): target.uuid = source['uuid'] target.generation = source['generation'] target.created_at = source['created_at'] target.updated_at = source['updated_at'] target.project = project_obj.Project(ctx, uuid=source["project_uuid"]) target.user = user_obj.User(ctx, uuid=source["user_uuid"]) target._context = ctx return target
def start_fixture(self): super(AllocationFixture, self).start_fixture() # For use creating and querying allocations/usages os.environ['ALT_USER_ID'] = uuidutils.generate_uuid() project_id = os.environ['PROJECT_ID'] user_id = os.environ['USER_ID'] alt_user_id = os.environ['ALT_USER_ID'] user = user_obj.User(self.context, uuid=user_id) user.create() alt_user = user_obj.User(self.context, uuid=alt_user_id) alt_user.create() project = project_obj.Project(self.context, uuid=project_id) project.create() # Stealing from the super rp_name = os.environ['RP_NAME'] rp_uuid = os.environ['RP_UUID'] # Create the rp with VCPU and DISK_GB inventory rp = tb.create_provider(self.context, rp_name, uuid=rp_uuid) tb.add_inventory(rp, 'DISK_GB', 2048, step_size=10, min_unit=10, max_unit=1000) tb.add_inventory(rp, 'VCPU', 10, max_unit=10) # Create a first consumer for the DISK_GB allocations consumer1 = tb.ensure_consumer(self.context, user, project) tb.set_allocation(self.context, rp, consumer1, {'DISK_GB': 1000}) os.environ['CONSUMER_0'] = consumer1.uuid # Create a second consumer for the VCPU allocations consumer2 = tb.ensure_consumer(self.context, user, project) tb.set_allocation(self.context, rp, consumer2, {'VCPU': 6}) os.environ['CONSUMER_ID'] = consumer2.uuid # Create a consumer object for a different user alt_consumer = tb.ensure_consumer(self.context, alt_user, project) os.environ['ALT_CONSUMER_ID'] = alt_consumer.uuid # Create a couple of allocations for a different user. tb.set_allocation(self.context, rp, alt_consumer, { 'DISK_GB': 20, 'VCPU': 1 }) # The ALT_RP_XXX variables are for a resource provider that has # not been created in the Allocation fixture os.environ['ALT_RP_UUID'] = uuidutils.generate_uuid() os.environ['ALT_RP_NAME'] = uuidutils.generate_uuid()
def setUp(self): super(PlacementDbBaseTestCase, self).setUp() # 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, uuid=uuids.user) self.user_obj.create() self.project_obj = project_obj.Project(self.ctx, uuid=uuids.project) self.project_obj.create() # 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 _get_or_create_project(ctx, project_id): try: proj = project_obj.Project.get_by_external_id(ctx, project_id) except exception.NotFound: # Auto-create the project if we found no record of it... try: proj = project_obj.Project(ctx, external_id=project_id) proj.create() except exception.ProjectExists: # No worries, another thread created this project already proj = project_obj.Project.get_by_external_id(ctx, project_id) return proj
def test_create_and_get(self): u = user_obj.User(self.ctx, external_id='another-user') u.create() p = project_obj.Project(self.ctx, external_id='another-project') p.create() c = consumer_obj.Consumer( self.ctx, uuid=uuids.consumer, user=u, project=p) c.create() c = consumer_obj.Consumer.get_by_uuid(self.ctx, uuids.consumer) self.assertEqual(1, c.id) # Project ID == 1 is fake-project created in setup self.assertEqual(2, c.project.id) # User ID == 1 is fake-user created in setup self.assertEqual(2, c.user.id) self.assertRaises(exception.ConsumerExists, c.create)
def get_all_by_consumer_id(context, consumer_id): _create_incomplete_consumer(context, consumer_id) db_allocs = _get_allocations_by_consumer_uuid(context, consumer_id) if not db_allocs: return [] # Build up the Consumer object (it's the same for all allocations # since we looked up by consumer ID) db_first = db_allocs[0] consumer = consumer_obj.Consumer( context, id=db_first['consumer_id'], uuid=db_first['consumer_uuid'], generation=db_first['consumer_generation'], project=project_obj.Project( context, id=db_first['project_id'], external_id=db_first['project_external_id']), user=user_obj.User(context, id=db_first['user_id'], external_id=db_first['user_external_id'])) # Build up a list of Allocation objects, setting the Allocation object # fields to the same-named database record field we got from # _get_allocations_by_consumer_id(). # # NOTE(jaypipes): Unlike with get_all_by_resource_provider(), we do # NOT already have the ResourceProvider object so we construct a new # ResourceProvider object below by looking at the resource provider # fields returned by _get_allocations_by_consumer_id(). alloc_list = [ Allocation(id=rec['id'], resource_provider=rp_obj.ResourceProvider( context, id=rec['resource_provider_id'], uuid=rec['resource_provider_uuid'], name=rec['resource_provider_name'], generation=rec['resource_provider_generation']), resource_class=rc_cache.RC_CACHE.string_from_id( rec['resource_class_id']), consumer=consumer, used=rec['used'], created_at=rec['created_at'], updated_at=rec['updated_at']) for rec in db_allocs ] return alloc_list
def _from_db_object(ctx, target, source): target.id = source['id'] target.uuid = source['uuid'] target.generation = source['generation'] target.created_at = source['created_at'] target.updated_at = source['updated_at'] target.project = project_obj.Project( ctx, id=source['project_id'], external_id=source['project_external_id']) target.user = user_obj.User(ctx, id=source['user_id'], external_id=source['user_external_id']) target._context = ctx return target
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_existing_consumer_after_gen_fail(self): """Tests that we require a consumer_generation after the appropriate microversion and that when the consumer already exists, then we raise a 400 when there is a mismatch on the existing generation. """ 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 consumer = consumer_obj.Consumer(self.ctx, project=proj, user=user, generation=42) self.mock_consumer_get.return_value = consumer consumer_gen = 2 # should NOT be ignored (and 2 is NOT expected) self.assertRaises(webob.exc.HTTPConflict, util.ensure_consumer, self.ctx, self.consumer_id, self.project_id, self.user_id, consumer_gen, self.after_version)
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 get_all_by_resource_provider(context, rp): _create_incomplete_consumers_for_provider(context, rp.uuid) db_allocs = _get_allocations_by_provider_uuid(context, rp.uuid) # Build up a list of Allocation objects, setting the Allocation object # fields to the same-named database record field we got from # _get_allocations_by_provider_uuid(). We already have the # ResourceProvider object so we just pass that object to the Allocation # object constructor as-is objs = [] for rec in db_allocs: consumer = consumer_obj.Consumer( context, uuid=rec["consumer_uuid"], generation=rec["consumer_generation"], project=project_obj.Project(context, uuid=rec["project_uuid"]), user=user_obj.User(context, uuid=rec["user_uuid"])) objs.append( Allocation(resource_provider=rp, resource_class=rec["resource_class_name"], consumer=consumer, used=rec["used"], created_at=rec["created_at"], updated_at=rec["updated_at"])) return objs
def ensure_consumer(ctx, consumer_uuid, project_id, user_id, consumer_generation, want_version): """Ensures there are records in the consumers, projects and users table for the supplied external identifiers. Returns a tuple containing the populated Consumer object containing Project and User sub-objects and a boolean indicating whether a new Consumer object was created (as opposed to an existing consumer record retrieved) :note: If the supplied project or user external identifiers do not match an existing consumer's project and user identifiers, the existing consumer's project and user IDs are updated to reflect the supplied ones. :param ctx: The request context. :param consumer_uuid: The uuid of the consumer of the resources. :param project_id: The external ID of the project consuming the resources. :param user_id: The external ID of the user consuming the resources. :param consumer_generation: The generation provided by the user for this consumer. :param want_version: the microversion matcher. :raises webob.exc.HTTPConflict if consumer generation is required and there was a mismatch """ created_new_consumer = False requires_consumer_generation = want_version.matches((1, 28)) if project_id is None: project_id = ctx.config.placement.incomplete_consumer_project_id user_id = ctx.config.placement.incomplete_consumer_user_id try: proj = project_obj.Project.get_by_external_id(ctx, project_id) except exception.NotFound: # Auto-create the project if we found no record of it... try: proj = project_obj.Project(ctx, external_id=project_id) proj.create() except exception.ProjectExists: # No worries, another thread created this project already proj = project_obj.Project.get_by_external_id(ctx, project_id) try: user = user_obj.User.get_by_external_id(ctx, user_id) except exception.NotFound: # Auto-create the user if we found no record of it... try: user = user_obj.User(ctx, external_id=user_id) user.create() except exception.UserExists: # No worries, another thread created this user already user = user_obj.User.get_by_external_id(ctx, user_id) try: consumer = consumer_obj.Consumer.get_by_uuid(ctx, consumer_uuid) if requires_consumer_generation: if consumer.generation != consumer_generation: raise webob.exc.HTTPConflict( 'consumer generation conflict - ' 'expected %(expected_gen)s but got %(got_gen)s' % { 'expected_gen': consumer.generation, 'got_gen': consumer_generation, }, comment=errors.CONCURRENT_UPDATE) # NOTE(jaypipes): The user may have specified a different project and # user external ID than the one that we had for the consumer. If this # is the case, go ahead and modify the consumer record with the # newly-supplied project/user information, but do not bump the consumer # generation (since it will be bumped in the # AllocationList.replace_all() method). # # TODO(jaypipes): This means that there may be a partial update. # Imagine a scenario where a user calls POST /allocations, and the # payload references two consumers. The first consumer is a new # consumer and is auto-created. The second consumer is an existing # consumer, but contains a different project or user ID than the # existing consumer's record. If the eventual call to # AllocationList.replace_all() fails for whatever reason (say, a # resource provider generation conflict or out of resources failure), # we will end up deleting the auto-created consumer but we MAY not undo # the changes to the second consumer's project and user ID. I say MAY # and not WILL NOT because I'm not sure that the exception that gets # raised from AllocationList.replace_all() will cause the context # manager's transaction to rollback automatically. I believe that the # same transaction context is used for both util.ensure_consumer() and # AllocationList.replace_all() within the same HTTP request, but need # to test this to be 100% certain... if (project_id != consumer.project.external_id or user_id != consumer.user.external_id): LOG.debug( "Supplied project or user ID for consumer %s was " "different than existing record. Updating consumer " "record.", consumer_uuid) consumer.project = proj consumer.user = user consumer.update() except exception.NotFound: # If we are attempting to modify or create allocations after 1.26, we # need a consumer generation specified. The user must have specified # None for the consumer generation if we get here, since there was no # existing consumer with this UUID and therefore the user should be # indicating that they expect the consumer did not exist. if requires_consumer_generation: if consumer_generation is not None: raise webob.exc.HTTPConflict('consumer generation conflict - ' 'expected null but got %s' % consumer_generation, comment=errors.CONCURRENT_UPDATE) # No such consumer. This is common for new allocations. Create the # consumer record try: consumer = consumer_obj.Consumer(ctx, uuid=consumer_uuid, project=proj, user=user) consumer.create() created_new_consumer = True except exception.ConsumerExists: # No worries, another thread created this user already consumer = consumer_obj.Consumer.get_by_uuid(ctx, consumer_uuid) return consumer, created_new_consumer
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