Check capacity and allocations when changing Inventory
Four new exceptions are added, three as a subclass of InvalidInventory which is itself a subclass of Invalid: * InventoryInUse: raised when there are allocations for an inventory being deleted. * InvalidInventoryCapacity: raised when reserved is greater or equal to capacity. * InvalidInventoryNewCapacityExceeded: raised when an inventory is changed in a way that makes usage exceed capacity. Change-Id: I41bd4184484226ce04e28848ff7919c8616f268d Partially-Implements: blueprint generic-resource-pools
This commit is contained in:
parent
33a50eace0
commit
cee4348bd5
@ -2108,6 +2108,28 @@ class ResourceProviderInUse(NovaException):
|
|||||||
msg_fmt = _("Resource provider has allocations.")
|
msg_fmt = _("Resource provider has allocations.")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidInventory(Invalid):
|
||||||
|
msg_fmt = _("Inventory for '%(resource_class)s' on "
|
||||||
|
"resource provider '%(resource_provider)s' invalid.")
|
||||||
|
|
||||||
|
|
||||||
|
class InventoryInUse(InvalidInventory):
|
||||||
|
msg_fmt = _("Inventory for '%(resource_classes)s' on "
|
||||||
|
"resource provider '%(resource_provider)s' in use.")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidInventoryCapacity(InvalidInventory):
|
||||||
|
msg_fmt = _("Invalid inventory for '%(resource_class)s' on "
|
||||||
|
"resource provider '%(resource_provider)s'. "
|
||||||
|
"The reserved value is greater than or equal to total.")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidInventoryNewCapacityExceeded(InvalidInventory):
|
||||||
|
msg_fmt = _("Invalid inventory for '%(resource_class)s' on "
|
||||||
|
"resource provider '%(resource_provider)s'. The new total "
|
||||||
|
"minus reserved amount is less than the existing used amount.")
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedPointerModelRequested(Invalid):
|
class UnsupportedPointerModelRequested(Invalid):
|
||||||
msg_fmt = _("Pointer model '%(model)s' requested is not supported by "
|
msg_fmt = _("Pointer model '%(model)s' requested is not supported by "
|
||||||
"host.")
|
"host.")
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
import six
|
import six
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import func
|
||||||
from sqlalchemy.orm import contains_eager
|
from sqlalchemy.orm import contains_eager
|
||||||
|
|
||||||
from nova.db.sqlalchemy import api as db_api
|
from nova.db.sqlalchemy import api as db_api
|
||||||
@ -21,6 +22,7 @@ from nova import objects
|
|||||||
from nova.objects import base
|
from nova.objects import base
|
||||||
from nova.objects import fields
|
from nova.objects import fields
|
||||||
|
|
||||||
|
_ALLOC_TBL = models.Allocation.__table__
|
||||||
_INV_TBL = models.Inventory.__table__
|
_INV_TBL = models.Inventory.__table__
|
||||||
_RP_TBL = models.ResourceProvider.__table__
|
_RP_TBL = models.ResourceProvider.__table__
|
||||||
|
|
||||||
@ -42,11 +44,26 @@ def _delete_inventory_from_provider(conn, rp, to_delete):
|
|||||||
"""Deletes any inventory records from the supplied provider and set() of
|
"""Deletes any inventory records from the supplied provider and set() of
|
||||||
resource class identifiers.
|
resource class identifiers.
|
||||||
|
|
||||||
|
If there are allocations for any of the inventories to be deleted raise
|
||||||
|
InventoryInUse exception.
|
||||||
|
|
||||||
:param conn: DB connection to use.
|
:param conn: DB connection to use.
|
||||||
:param rp: Resource provider from which to delete inventory.
|
:param rp: Resource provider from which to delete inventory.
|
||||||
:param to_delete: set() containing resource class IDs for records to
|
:param to_delete: set() containing resource class IDs for records to
|
||||||
delete.
|
delete.
|
||||||
"""
|
"""
|
||||||
|
allocation_query = sa.select(
|
||||||
|
[_ALLOC_TBL.c.resource_class_id.label('resource_class')]).where(
|
||||||
|
sa.and_(_ALLOC_TBL.c.resource_provider_id == rp.id,
|
||||||
|
_ALLOC_TBL.c.resource_class_id.in_(to_delete))
|
||||||
|
).group_by(_ALLOC_TBL.c.resource_class_id)
|
||||||
|
allocations = conn.execute(allocation_query).fetchall()
|
||||||
|
if allocations:
|
||||||
|
resource_classes = ', '.join([fields.ResourceClass.from_index(
|
||||||
|
allocation.resource_class) for allocation in allocations])
|
||||||
|
raise exception.InventoryInUse(resource_classes=resource_classes,
|
||||||
|
resource_provider=rp.uuid)
|
||||||
|
|
||||||
del_stmt = _INV_TBL.delete().where(sa.and_(
|
del_stmt = _INV_TBL.delete().where(sa.and_(
|
||||||
_INV_TBL.c.resource_provider_id == rp.id,
|
_INV_TBL.c.resource_provider_id == rp.id,
|
||||||
_INV_TBL.c.resource_class_id.in_(to_delete)))
|
_INV_TBL.c.resource_class_id.in_(to_delete)))
|
||||||
@ -66,8 +83,9 @@ def _add_inventory_to_provider(conn, rp, inv_list, to_add):
|
|||||||
for res_class in to_add:
|
for res_class in to_add:
|
||||||
inv_record = inv_list.find(res_class)
|
inv_record = inv_list.find(res_class)
|
||||||
if inv_record.capacity <= 0:
|
if inv_record.capacity <= 0:
|
||||||
raise exception.ObjectActionError(
|
raise exception.InvalidInventoryCapacity(
|
||||||
action='add inventory', reason='invalid resource capacity')
|
resource_class=fields.ResourceClass.from_index(res_class),
|
||||||
|
resource_provider=rp.uuid)
|
||||||
ins_stmt = _INV_TBL.insert().values(
|
ins_stmt = _INV_TBL.insert().values(
|
||||||
resource_provider_id=rp.id,
|
resource_provider_id=rp.id,
|
||||||
resource_class_id=res_class,
|
resource_class_id=res_class,
|
||||||
@ -92,8 +110,19 @@ def _update_inventory_for_provider(conn, rp, inv_list, to_update):
|
|||||||
for res_class in to_update:
|
for res_class in to_update:
|
||||||
inv_record = inv_list.find(res_class)
|
inv_record = inv_list.find(res_class)
|
||||||
if inv_record.capacity <= 0:
|
if inv_record.capacity <= 0:
|
||||||
raise exception.ObjectActionError(
|
raise exception.InvalidInventoryCapacity(
|
||||||
action='update inventory', reason='invalid resource capacity')
|
resource_class=fields.ResourceClass.from_index(res_class),
|
||||||
|
resource_provider=rp.uuid)
|
||||||
|
allocation_query = sa.select(
|
||||||
|
[func.sum(_ALLOC_TBL.c.used).label('usage')]).\
|
||||||
|
where(sa.and_(
|
||||||
|
_ALLOC_TBL.c.resource_provider_id == rp.id,
|
||||||
|
_ALLOC_TBL.c.resource_class_id == res_class))
|
||||||
|
allocations = conn.execute(allocation_query).first()
|
||||||
|
if allocations and allocations['usage'] > inv_record.capacity:
|
||||||
|
raise exception.InvalidInventoryNewCapacityExceeded(
|
||||||
|
resource_class=fields.ResourceClass.from_index(res_class),
|
||||||
|
resource_provider=rp.uuid)
|
||||||
upd_stmt = _INV_TBL.update().where(sa.and_(
|
upd_stmt = _INV_TBL.update().where(sa.and_(
|
||||||
_INV_TBL.c.resource_provider_id == rp.id,
|
_INV_TBL.c.resource_provider_id == rp.id,
|
||||||
_INV_TBL.c.resource_class_id == res_class)).values(
|
_INV_TBL.c.resource_class_id == res_class)).values(
|
||||||
|
@ -290,8 +290,11 @@ class ResourceProviderTestCase(ResourceProviderBaseCase):
|
|||||||
total=2048,
|
total=2048,
|
||||||
reserved=2048)
|
reserved=2048)
|
||||||
disk_inv.obj_set_defaults()
|
disk_inv.obj_set_defaults()
|
||||||
self.assertRaises(exception.ObjectActionError,
|
error = self.assertRaises(exception.InvalidInventoryCapacity,
|
||||||
rp.update_inventory, disk_inv)
|
rp.update_inventory, disk_inv)
|
||||||
|
self.assertIn("Invalid inventory for '%s'"
|
||||||
|
% fields.ResourceClass.DISK_GB, str(error))
|
||||||
|
self.assertIn("on resource provider '%s'." % rp.uuid, str(error))
|
||||||
|
|
||||||
# generation has not bumped
|
# generation has not bumped
|
||||||
self.assertEqual(saved_generation, rp.generation)
|
self.assertEqual(saved_generation, rp.generation)
|
||||||
@ -347,6 +350,21 @@ class ResourceProviderTestCase(ResourceProviderBaseCase):
|
|||||||
self.assertIn('No inventory of class DISK_GB found for delete',
|
self.assertIn('No inventory of class DISK_GB found for delete',
|
||||||
str(error))
|
str(error))
|
||||||
|
|
||||||
|
def test_delete_inventory_with_allocation(self):
|
||||||
|
rp, allocation = self._make_allocation()
|
||||||
|
disk_inv = objects.Inventory(resource_provider=rp,
|
||||||
|
resource_class='DISK_GB',
|
||||||
|
total=2048)
|
||||||
|
disk_inv.obj_set_defaults()
|
||||||
|
inv_list = objects.InventoryList(objects=[disk_inv])
|
||||||
|
rp.set_inventory(inv_list)
|
||||||
|
error = self.assertRaises(exception.InventoryInUse,
|
||||||
|
rp.delete_inventory,
|
||||||
|
'DISK_GB')
|
||||||
|
self.assertIn(
|
||||||
|
"Inventory for 'DISK_GB' on resource provider '%s' in use"
|
||||||
|
% rp.uuid, str(error))
|
||||||
|
|
||||||
def test_update_inventory_not_found(self):
|
def test_update_inventory_not_found(self):
|
||||||
rp = objects.ResourceProvider(context=self.context,
|
rp = objects.ResourceProvider(context=self.context,
|
||||||
uuid=uuidsentinel.rp_uuid,
|
uuid=uuidsentinel.rp_uuid,
|
||||||
@ -361,6 +379,45 @@ class ResourceProviderTestCase(ResourceProviderBaseCase):
|
|||||||
self.assertIn('No inventory of class DISK_GB found for update',
|
self.assertIn('No inventory of class DISK_GB found for update',
|
||||||
str(error))
|
str(error))
|
||||||
|
|
||||||
|
def test_update_inventory_violates_allocation(self):
|
||||||
|
rp, allocation = self._make_allocation()
|
||||||
|
disk_inv = objects.Inventory(resource_provider=rp,
|
||||||
|
resource_class='DISK_GB',
|
||||||
|
total=2048)
|
||||||
|
disk_inv.obj_set_defaults()
|
||||||
|
inv_list = objects.InventoryList(objects=[disk_inv])
|
||||||
|
rp.set_inventory(inv_list)
|
||||||
|
# attempt to set inventory to less than currently allocated
|
||||||
|
# amounts
|
||||||
|
disk_inv = objects.Inventory(
|
||||||
|
resource_provider=rp,
|
||||||
|
resource_class=fields.ResourceClass.DISK_GB, total=1)
|
||||||
|
disk_inv.obj_set_defaults()
|
||||||
|
error = self.assertRaises(
|
||||||
|
exception.InvalidInventoryNewCapacityExceeded,
|
||||||
|
rp.update_inventory, disk_inv)
|
||||||
|
self.assertIn("Invalid inventory for '%s'"
|
||||||
|
% fields.ResourceClass.DISK_GB, str(error))
|
||||||
|
self.assertIn("on resource provider '%s'." % rp.uuid, str(error))
|
||||||
|
|
||||||
|
def test_add_invalid_inventory(self):
|
||||||
|
rp = objects.ResourceProvider(context=self.context,
|
||||||
|
uuid=uuidsentinel.rp_uuid,
|
||||||
|
name=uuidsentinel.rp_name)
|
||||||
|
rp.create()
|
||||||
|
disk_inv = objects.Inventory(
|
||||||
|
resource_provider=rp,
|
||||||
|
resource_class=fields.ResourceClass.DISK_GB,
|
||||||
|
total=1024, reserved=2048)
|
||||||
|
disk_inv.obj_set_defaults()
|
||||||
|
error = self.assertRaises(exception.InvalidInventoryCapacity,
|
||||||
|
rp.add_inventory,
|
||||||
|
disk_inv)
|
||||||
|
self.assertIn("Invalid inventory for '%s'"
|
||||||
|
% fields.ResourceClass.DISK_GB, str(error))
|
||||||
|
self.assertIn("on resource provider '%s'."
|
||||||
|
% rp.uuid, str(error))
|
||||||
|
|
||||||
|
|
||||||
class ResourceProviderListTestCase(test.NoDBTestCase):
|
class ResourceProviderListTestCase(test.NoDBTestCase):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user