Improve check capacity sql

During a benchmark on allocation creation, I found checking allocations
capacity occupied a great amount of time when allocation table is fairly
big. Add a check on resource provider id to reduce allocation records to
be joined with resource providers and inventories.

Change-Id: Ic36809fa7161c2aa07a42669b44046993e5db058
This commit is contained in:
leizhang 2018-04-17 16:44:27 +08:00
parent 888cd51e42
commit 7c01dd1a65
3 changed files with 142 additions and 4 deletions

View File

@ -1823,11 +1823,12 @@ def _check_capacity_exceeded(ctx, allocs):
# SELECT resource_provider_id, resource_class_id, SUM(used) AS used
# FROM allocations
# WHERE resource_class_id IN ($RESOURCE_CLASSES)
# AND resource_provider_id IN ($RESOURCE_PROVIDERS)
# GROUP BY resource_provider_id, resource_class_id
# ) AS allocs
# ON inv.resource_provider_id = allocs.resource_provider_id
# AND inv.resource_class_id = allocs.resource_class_id
# WHERE rp.uuid IN ($RESOURCE_PROVIDERS)
# WHERE rp.id IN ($RESOURCE_PROVIDERS)
# AND inv.resource_class_id IN ($RESOURCE_CLASSES)
#
# We then take the results of the above and determine if any of the
@ -1835,11 +1836,13 @@ def _check_capacity_exceeded(ctx, allocs):
rc_ids = set([_RC_CACHE.id_from_string(a.resource_class)
for a in allocs])
provider_uuids = set([a.resource_provider.uuid for a in allocs])
provider_ids = set([a.resource_provider.id for a in allocs])
usage = sa.select([_ALLOC_TBL.c.resource_provider_id,
_ALLOC_TBL.c.resource_class_id,
sql.func.sum(_ALLOC_TBL.c.used).label('used')])
usage = usage.where(_ALLOC_TBL.c.resource_class_id.in_(rc_ids))
usage = usage.where(
sa.and_(_ALLOC_TBL.c.resource_class_id.in_(rc_ids),
_ALLOC_TBL.c.resource_provider_id.in_(provider_ids)))
usage = usage.group_by(_ALLOC_TBL.c.resource_provider_id,
_ALLOC_TBL.c.resource_class_id)
usage = sa.alias(usage, name='usage')
@ -1868,7 +1871,7 @@ def _check_capacity_exceeded(ctx, allocs):
sel = sa.select(cols_in_output).select_from(primary_join)
sel = sel.where(
sa.and_(_RP_TBL.c.uuid.in_(provider_uuids),
sa.and_(_RP_TBL.c.id.in_(provider_ids),
_INV_TBL.c.resource_class_id.in_(rc_ids)))
records = ctx.session.execute(sel)
# Create a map keyed by (rp_uuid, res_class) for the records in the DB

View File

@ -1909,6 +1909,114 @@ class TestAllocationListCreateDelete(ResourceProviderBaseCase):
self.ctx, migration_uuid)
self.assertEqual(0, len(allocations))
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 = rp_obj.ResourceProvider(
context=self.ctx,
uuid=uuidsentinel.empty_rp,
name=uuidsentinel.empty_rp,
)
empty_rp.create()
full_rp = rp_obj.ResourceProvider(
context=self.ctx,
uuid=uuidsentinel.full_rp,
name=uuidsentinel.full_rp,
)
full_rp.create()
for rp in (empty_rp, full_rp):
cpu_inv = rp_obj.Inventory(
context=self.ctx,
resource_provider=rp,
resource_class=fields.ResourceClass.VCPU,
total=24,
reserved=0,
min_unit=1,
max_unit=24,
step_size=1,
allocation_ratio=16.0)
ram_inv = rp_obj.Inventory(
context=self.ctx,
resource_provider=rp,
resource_class=fields.ResourceClass.MEMORY_MB,
total=1024,
reserved=0,
min_unit=64,
max_unit=1024,
step_size=64,
allocation_ratio=1.0)
inv_list = rp_obj.InventoryList(context=self.ctx,
objects=[cpu_inv, ram_inv])
rp.set_inventory(inv_list)
# First create a allocation to consume full_rp's resource.
alloc_list = rp_obj.AllocationList(context=self.ctx,
objects=[
rp_obj.Allocation(
context=self.ctx,
consumer_id=uuidsentinel.instance,
resource_provider=full_rp,
resource_class=fields.ResourceClass.VCPU,
used=12),
rp_obj.Allocation(
context=self.ctx,
consumer_id=uuidsentinel.instance,
resource_provider=full_rp,
resource_class=fields.ResourceClass.MEMORY_MB,
used=1024)
])
alloc_list.create_all()
# Create a allocation list consists of valid requests and a invalid
# request exceeds memory full_rp can provide.
alloc_list = rp_obj.AllocationList(context=self.ctx,
objects=[
rp_obj.Allocation(
context=self.ctx,
consumer_id=uuidsentinel.instance2,
resource_provider=empty_rp,
resource_class=fields.ResourceClass.VCPU,
used=12),
rp_obj.Allocation(
context=self.ctx,
consumer_id=uuidsentinel.instance2,
resource_provider=empty_rp,
resource_class=fields.ResourceClass.MEMORY_MB,
used=512),
rp_obj.Allocation(
context=self.ctx,
consumer_id=uuidsentinel.instance2,
resource_provider=full_rp,
resource_class=fields.ResourceClass.VCPU,
used=12),
rp_obj.Allocation(
context=self.ctx,
consumer_id=uuidsentinel.instance2,
resource_provider=full_rp,
resource_class=fields.ResourceClass.MEMORY_MB,
used=512),
])
self.assertRaises(exception.InvalidAllocationCapacityExceeded,
alloc_list.create_all)
# Make sure that allocations of both empty_rp and full_rp remains
# unchanged.
allocations = rp_obj.AllocationList.get_all_by_resource_provider(
self.ctx, full_rp)
self.assertEqual(2, len(allocations))
allocations = rp_obj.AllocationList.get_all_by_resource_provider(
self.ctx, empty_rp)
self.assertEqual(0, len(allocations))
class UsageListTestCase(ResourceProviderBaseCase):

View File

@ -412,6 +412,33 @@ class TestAllocation(test_objects._LocalTest):
objects=[obj])
self.assertRaises(exception.ObjectActionError, alloc_list.create_all)
def test_create_exceed_capacity_fails(self):
rp = resource_provider.ResourceProvider(context=self.context,
uuid=_RESOURCE_PROVIDER_UUID,
name=_RESOURCE_PROVIDER_NAME)
rp.create()
inv = resource_provider.Inventory(context=self.context,
resource_provider=rp,
resource_class=_RESOURCE_CLASS_NAME,
total=16,
reserved=2,
min_unit=1,
max_unit=16,
step_size=1,
allocation_ratio=1.0)
inv_list = resource_provider.InventoryList(context=self.context,
objects=[inv])
rp.set_inventory(inv_list)
obj = resource_provider.Allocation(context=self.context,
resource_provider=rp,
resource_class=_RESOURCE_CLASS_NAME,
consumer_id=uuids.fake_instance,
used=16)
alloc_list = resource_provider.AllocationList(self.context,
objects=[obj])
self.assertRaises(exception.InvalidAllocationCapacityExceeded,
alloc_list.create_all)
class TestAllocationListNoDB(test_objects._LocalTest):
USES_DB = False