Move project_id and user_id to Allocation object
It had been on the AllocationList object but this makes it impossible to have an AllocationList from multiple project ids. When it was decided to allow setting allocations from multiple different consumers, it was decided that since we were already in the code changing things, we should also make adjustments to allow each allocation to have its own project and user id. In the process, ensure that if a persisted Allocation has a project_id and user_id associated with it, load those values when the Allocation is loaded (either list allocations by consumer or by resource provider). We cannot enforce (at the object level) that Allocations must have a project_id and user_id because there is a microversion where they are not required that we continue to support. In newer microversions the JSON schema enforces the their requirement. Inspecting the placement fixtures related to this change revealed a bug with a missing user_id in one Allocation being created. Change-Id: I3cf887bd541c187ec3baca2ae3f8c16f1754e96e
This commit is contained in:
parent
df9fbbfec9
commit
e000a8f290
|
@ -236,15 +236,13 @@ def _set_allocations(req, schema):
|
|||
resource_provider=resource_provider,
|
||||
consumer_id=consumer_uuid,
|
||||
resource_class=resource_class,
|
||||
project_id=data.get('project_id'),
|
||||
user_id=data.get('user_id'),
|
||||
used=resources[resource_class])
|
||||
allocation_objects.append(allocation)
|
||||
|
||||
allocations = rp_obj.AllocationList(
|
||||
context,
|
||||
objects=allocation_objects,
|
||||
project_id=data.get('project_id'),
|
||||
user_id=data.get('user_id'),
|
||||
)
|
||||
context, objects=allocation_objects)
|
||||
|
||||
try:
|
||||
allocations.create_all()
|
||||
|
|
|
@ -1450,8 +1450,49 @@ class Allocation(_HasAResourceProvider):
|
|||
'consumer_id': fields.UUIDField(),
|
||||
'resource_class': fields.ResourceClassField(),
|
||||
'used': fields.IntegerField(),
|
||||
# The following two fields are allowed to be set to None to
|
||||
# support Allocations that were created before the fields were
|
||||
# required.
|
||||
'project_id': fields.StringField(nullable=True),
|
||||
'user_id': fields.StringField(nullable=True),
|
||||
}
|
||||
|
||||
def ensure_consumer_project_user(self, conn):
|
||||
"""Examines the project_id, user_id of the object along with the
|
||||
supplied consumer_id and ensures that if project_id and user_id
|
||||
are set that there are records in the consumers, projects, and
|
||||
users table for these entities.
|
||||
"""
|
||||
# If project_id and user_id are not set then silently
|
||||
# move on. This allows microversion <1.8 to continue to work. Since
|
||||
# then the fields are required and the enforcement is at the HTTP
|
||||
# API layer.
|
||||
if not ('project_id' in self and
|
||||
self.project_id is not None and
|
||||
'user_id' in self and
|
||||
self.user_id is not None):
|
||||
return
|
||||
# Grab the project internal ID if it exists in the projects table
|
||||
pid = _ensure_project(conn, self.project_id)
|
||||
# Grab the user internal ID if it exists in the users table
|
||||
uid = _ensure_user(conn, self.user_id)
|
||||
|
||||
# Add the consumer if it doesn't already exist
|
||||
sel_stmt = sa.select([_CONSUMER_TBL.c.uuid]).where(
|
||||
_CONSUMER_TBL.c.uuid == self.consumer_id)
|
||||
result = conn.execute(sel_stmt).fetchall()
|
||||
if not result:
|
||||
try:
|
||||
conn.execute(_CONSUMER_TBL.insert().values(
|
||||
uuid=self.consumer_id,
|
||||
project_id=pid,
|
||||
user_id=uid))
|
||||
except db_exc.DBDuplicateEntry:
|
||||
# We assume at this time that a consumer project/user can't
|
||||
# change, so if we get here, we raced and should just pass
|
||||
# if the consumer already exists.
|
||||
pass
|
||||
|
||||
|
||||
@db_api.api_context_manager.writer
|
||||
def _delete_allocations_for_consumer(ctx, consumer_id):
|
||||
|
@ -1707,42 +1748,8 @@ class AllocationList(base.ObjectListBase, base.NovaObject):
|
|||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('Allocation'),
|
||||
'project_id': fields.StringField(nullable=True),
|
||||
'user_id': fields.StringField(nullable=True),
|
||||
}
|
||||
|
||||
def _ensure_consumer_project_user(self, conn, consumer_id):
|
||||
"""Examines the project_id, user_id of the object along with the
|
||||
supplied consumer_id and ensures that there are records in the
|
||||
consumers, projects, and users table for these entities.
|
||||
|
||||
:param consumer_id: Comes from the Allocation object being processed
|
||||
"""
|
||||
if (self.obj_attr_is_set('project_id') and
|
||||
self.project_id is not None and
|
||||
self.obj_attr_is_set('user_id') and
|
||||
self.user_id is not None):
|
||||
# Grab the project internal ID if it exists in the projects table
|
||||
pid = _ensure_project(conn, self.project_id)
|
||||
# Grab the user internal ID if it exists in the users table
|
||||
uid = _ensure_user(conn, self.user_id)
|
||||
|
||||
# Add the consumer if it doesn't already exist
|
||||
sel_stmt = sa.select([_CONSUMER_TBL.c.uuid]).where(
|
||||
_CONSUMER_TBL.c.uuid == consumer_id)
|
||||
result = conn.execute(sel_stmt).fetchall()
|
||||
if not result:
|
||||
try:
|
||||
conn.execute(_CONSUMER_TBL.insert().values(
|
||||
uuid=consumer_id,
|
||||
project_id=pid,
|
||||
user_id=uid))
|
||||
except db_exc.DBDuplicateEntry:
|
||||
# We assume at this time that a consumer project/user can't
|
||||
# change, so if we get here, we raced and should just pass
|
||||
# if the consumer already exists.
|
||||
pass
|
||||
|
||||
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
|
||||
@db_api.api_context_manager.writer
|
||||
def _set_allocations(self, context, allocs):
|
||||
|
@ -1779,22 +1786,29 @@ class AllocationList(base.ObjectListBase, base.NovaObject):
|
|||
# objects are used at the end of the allocation transaction as a guard
|
||||
# against concurrent updates.
|
||||
with conn.begin():
|
||||
# First delete any existing allocations for that rp/consumer combo.
|
||||
consumer_id = allocs[0].consumer_id
|
||||
_delete_allocations_for_consumer(context, consumer_id)
|
||||
# First delete any existing allocations for this consumer. This
|
||||
# must be done before checking capacity.
|
||||
for alloc in allocs:
|
||||
consumer_id = alloc.consumer_id
|
||||
_delete_allocations_for_consumer(context, consumer_id)
|
||||
# If there are any allocations with string resource class names
|
||||
# that don't exist this will raise a ResourceClassNotFound
|
||||
# exception.
|
||||
before_gens = _check_capacity_exceeded(conn, allocs)
|
||||
self._ensure_consumer_project_user(conn, consumer_id)
|
||||
# Now add the allocations that were passed in.
|
||||
seen_consumers = set()
|
||||
for alloc in allocs:
|
||||
consumer_id = alloc.consumer_id
|
||||
# Only set consumer <-> project/user association if we
|
||||
# haven't set it already.
|
||||
if consumer_id not in seen_consumers:
|
||||
alloc.ensure_consumer_project_user(conn)
|
||||
seen_consumers.add(consumer_id)
|
||||
rp = alloc.resource_provider
|
||||
rc_id = _RC_CACHE.id_from_string(alloc.resource_class)
|
||||
ins_stmt = _ALLOC_TBL.insert().values(
|
||||
resource_provider_id=rp.id,
|
||||
resource_class_id=rc_id,
|
||||
consumer_id=alloc.consumer_id,
|
||||
consumer_id=consumer_id,
|
||||
used=alloc.used)
|
||||
result = conn.execute(ins_stmt)
|
||||
alloc.id = result.lastrowid
|
||||
|
|
|
@ -119,8 +119,6 @@ class AllocationFixture(APIFixture):
|
|||
rp.create()
|
||||
|
||||
# Create some DISK_GB inventory and allocations.
|
||||
# Each set of allocations must have the same consumer_id because only
|
||||
# the first allocation is used for the project/user association.
|
||||
consumer_id = uuidutils.generate_uuid()
|
||||
inventory = rp_obj.Inventory(
|
||||
self.context, resource_provider=rp,
|
||||
|
@ -132,23 +130,23 @@ class AllocationFixture(APIFixture):
|
|||
self.context, resource_provider=rp,
|
||||
resource_class='DISK_GB',
|
||||
consumer_id=consumer_id,
|
||||
project_id=project_id,
|
||||
user_id=user_id,
|
||||
used=500)
|
||||
alloc2 = rp_obj.Allocation(
|
||||
self.context, resource_provider=rp,
|
||||
resource_class='DISK_GB',
|
||||
consumer_id=consumer_id,
|
||||
project_id=project_id,
|
||||
user_id=user_id,
|
||||
used=500)
|
||||
alloc_list = rp_obj.AllocationList(
|
||||
self.context,
|
||||
objects=[alloc1, alloc2],
|
||||
project_id=project_id,
|
||||
user_id=user_id,
|
||||
objects=[alloc1, alloc2]
|
||||
)
|
||||
alloc_list.create_all()
|
||||
|
||||
# Create some VCPU inventory and allocations.
|
||||
# Each set of allocations must have the same consumer_id because only
|
||||
# the first allocation is used for the project/user association.
|
||||
consumer_id = uuidutils.generate_uuid()
|
||||
inventory = rp_obj.Inventory(
|
||||
self.context, resource_provider=rp,
|
||||
|
@ -160,38 +158,40 @@ class AllocationFixture(APIFixture):
|
|||
self.context, resource_provider=rp,
|
||||
resource_class='VCPU',
|
||||
consumer_id=consumer_id,
|
||||
project_id=project_id,
|
||||
user_id=user_id,
|
||||
used=2)
|
||||
alloc2 = rp_obj.Allocation(
|
||||
self.context, resource_provider=rp,
|
||||
resource_class='VCPU',
|
||||
consumer_id=consumer_id,
|
||||
project_id=project_id,
|
||||
user_id=user_id,
|
||||
used=4)
|
||||
alloc_list = rp_obj.AllocationList(
|
||||
self.context,
|
||||
objects=[alloc1, alloc2],
|
||||
project_id=project_id,
|
||||
user_id=user_id)
|
||||
objects=[alloc1, alloc2])
|
||||
alloc_list.create_all()
|
||||
|
||||
# Create a couple of allocations for a different user.
|
||||
# Each set of allocations must have the same consumer_id because only
|
||||
# the first allocation is used for the project/user association.
|
||||
consumer_id = uuidutils.generate_uuid()
|
||||
alloc1 = rp_obj.Allocation(
|
||||
self.context, resource_provider=rp,
|
||||
resource_class='DISK_GB',
|
||||
consumer_id=consumer_id,
|
||||
project_id=project_id,
|
||||
user_id=alt_user_id,
|
||||
used=20)
|
||||
alloc2 = rp_obj.Allocation(
|
||||
self.context, resource_provider=rp,
|
||||
resource_class='VCPU',
|
||||
consumer_id=consumer_id,
|
||||
project_id=project_id,
|
||||
user_id=alt_user_id,
|
||||
used=1)
|
||||
alloc_list = rp_obj.AllocationList(
|
||||
self.context,
|
||||
objects=[alloc1, alloc2],
|
||||
project_id=project_id,
|
||||
user_id=alt_user_id)
|
||||
objects=[alloc1, alloc2])
|
||||
alloc_list.create_all()
|
||||
|
||||
# The ALT_RP_XXX variables are for a resource provider that has
|
||||
|
|
|
@ -1248,16 +1248,18 @@ class TestAllocationListCreateDelete(ResourceProviderBaseCase):
|
|||
allocation1 = rp_obj.Allocation(resource_provider=rp,
|
||||
consumer_id=consumer_uuid,
|
||||
resource_class=rp_class,
|
||||
project_id=self.ctx.project_id,
|
||||
user_id=self.ctx.user_id,
|
||||
used=100)
|
||||
allocation2 = rp_obj.Allocation(resource_provider=rp,
|
||||
consumer_id=consumer_uuid,
|
||||
resource_class=rp_class,
|
||||
project_id=self.ctx.project_id,
|
||||
user_id=self.ctx.user_id,
|
||||
used=200)
|
||||
allocation_list = rp_obj.AllocationList(
|
||||
self.ctx,
|
||||
objects=[allocation1, allocation2],
|
||||
project_id=self.ctx.project_id,
|
||||
user_id=self.ctx.user_id,
|
||||
)
|
||||
allocation_list.create_all()
|
||||
|
||||
|
@ -1290,12 +1292,12 @@ class TestAllocationListCreateDelete(ResourceProviderBaseCase):
|
|||
allocation3 = rp_obj.Allocation(resource_provider=rp,
|
||||
consumer_id=other_consumer_uuid,
|
||||
resource_class=rp_class,
|
||||
project_id=self.ctx.project_id,
|
||||
user_id=uuidsentinel.other_user,
|
||||
used=200)
|
||||
allocation_list = rp_obj.AllocationList(
|
||||
self.ctx,
|
||||
objects=[allocation3],
|
||||
project_id=self.ctx.project_id,
|
||||
user_id=uuidsentinel.other_user,
|
||||
)
|
||||
allocation_list.create_all()
|
||||
|
||||
|
|
|
@ -59,6 +59,8 @@ _ALLOCATION_DB = {
|
|||
'resource_class_id': _RESOURCE_CLASS_ID,
|
||||
'consumer_id': uuids.fake_instance,
|
||||
'used': 8,
|
||||
'user_id': None,
|
||||
'project_id': None,
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue