Merge "Check capacity and allocations when changing Inventory"
This commit is contained in:
commit
ee809ecd0b
@ -2108,6 +2108,28 @@ class ResourceProviderInUse(NovaException):
|
||||
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):
|
||||
msg_fmt = _("Pointer model '%(model)s' requested is not supported by "
|
||||
"host.")
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
import six
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import contains_eager
|
||||
|
||||
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 fields
|
||||
|
||||
_ALLOC_TBL = models.Allocation.__table__
|
||||
_INV_TBL = models.Inventory.__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
|
||||
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 rp: Resource provider from which to delete inventory.
|
||||
:param to_delete: set() containing resource class IDs for records to
|
||||
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_(
|
||||
_INV_TBL.c.resource_provider_id == rp.id,
|
||||
_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:
|
||||
inv_record = inv_list.find(res_class)
|
||||
if inv_record.capacity <= 0:
|
||||
raise exception.ObjectActionError(
|
||||
action='add inventory', reason='invalid resource capacity')
|
||||
raise exception.InvalidInventoryCapacity(
|
||||
resource_class=fields.ResourceClass.from_index(res_class),
|
||||
resource_provider=rp.uuid)
|
||||
ins_stmt = _INV_TBL.insert().values(
|
||||
resource_provider_id=rp.id,
|
||||
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:
|
||||
inv_record = inv_list.find(res_class)
|
||||
if inv_record.capacity <= 0:
|
||||
raise exception.ObjectActionError(
|
||||
action='update inventory', reason='invalid resource capacity')
|
||||
raise exception.InvalidInventoryCapacity(
|
||||
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_(
|
||||
_INV_TBL.c.resource_provider_id == rp.id,
|
||||
_INV_TBL.c.resource_class_id == res_class)).values(
|
||||
|
@ -290,8 +290,11 @@ class ResourceProviderTestCase(ResourceProviderBaseCase):
|
||||
total=2048,
|
||||
reserved=2048)
|
||||
disk_inv.obj_set_defaults()
|
||||
self.assertRaises(exception.ObjectActionError,
|
||||
rp.update_inventory, disk_inv)
|
||||
error = self.assertRaises(exception.InvalidInventoryCapacity,
|
||||
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
|
||||
self.assertEqual(saved_generation, rp.generation)
|
||||
@ -347,6 +350,21 @@ class ResourceProviderTestCase(ResourceProviderBaseCase):
|
||||
self.assertIn('No inventory of class DISK_GB found for delete',
|
||||
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):
|
||||
rp = objects.ResourceProvider(context=self.context,
|
||||
uuid=uuidsentinel.rp_uuid,
|
||||
@ -361,6 +379,45 @@ class ResourceProviderTestCase(ResourceProviderBaseCase):
|
||||
self.assertIn('No inventory of class DISK_GB found for update',
|
||||
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):
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user