Add delete_, update_ and add_ inventory to ResourceProvider
Each of these reuses the methods used by set_inventory for deleting, updating or adding just one Inventory to a ResourceProvider. With the addition of these methods, the existing set_inventory needs a clarifying docstring. An Inventory.capacity property has been added to validate that Inventory.total and Inventory.reserved do not conflict. This will also eventually be used to make comparisons with Usages. The version of the ResourceProvider object does not require a version bump, despite the change in object hash, because ResourceProviders are not yet being used in the wild. Partially Implements: blueprint generic-resource-pools Change-Id: I62836faf829b93997a176aba4e0dc3502ae54049
This commit is contained in:
parent
8435999c78
commit
7402d134e1
|
@ -75,7 +75,8 @@ def _delete_inventory_from_provider(conn, rp, to_delete):
|
|||
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)))
|
||||
conn.execute(del_stmt)
|
||||
res = conn.execute(del_stmt)
|
||||
return res.rowcount
|
||||
|
||||
|
||||
def _add_inventory_to_provider(conn, rp, inv_list, to_add):
|
||||
|
@ -89,6 +90,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')
|
||||
ins_stmt = _INV_TBL.insert().values(
|
||||
resource_provider_id=rp.id,
|
||||
resource_class_id=res_class,
|
||||
|
@ -112,6 +116,9 @@ 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')
|
||||
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(
|
||||
|
@ -121,7 +128,11 @@ def _update_inventory_for_provider(conn, rp, inv_list, to_update):
|
|||
max_unit=inv_record.max_unit,
|
||||
step_size=inv_record.step_size,
|
||||
allocation_ratio=inv_record.allocation_ratio)
|
||||
conn.execute(upd_stmt)
|
||||
res = conn.execute(upd_stmt)
|
||||
if not res.rowcount:
|
||||
raise exception.NotFound(
|
||||
'No inventory of class %s found for update'
|
||||
% fields.ResourceClass.from_index(res_class))
|
||||
|
||||
|
||||
def _increment_provider_generation(conn, rp):
|
||||
|
@ -145,6 +156,43 @@ def _increment_provider_generation(conn, rp):
|
|||
return new_generation
|
||||
|
||||
|
||||
@db_api.api_context_manager.writer
|
||||
def _add_inventory(context, rp, inventory):
|
||||
"""Add one Inventory that wasn't already on the provider."""
|
||||
resource_class_id = fields.ResourceClass.index(inventory.resource_class)
|
||||
inv_list = InventoryList(objects=[inventory])
|
||||
conn = context.session.connection()
|
||||
with conn.begin():
|
||||
_add_inventory_to_provider(
|
||||
conn, rp, inv_list, set([resource_class_id]))
|
||||
rp.generation = _increment_provider_generation(conn, rp)
|
||||
|
||||
|
||||
@db_api.api_context_manager.writer
|
||||
def _update_inventory(context, rp, inventory):
|
||||
"""Update an inventory already on the provider."""
|
||||
resource_class_id = fields.ResourceClass.index(inventory.resource_class)
|
||||
inv_list = InventoryList(objects=[inventory])
|
||||
conn = context.session.connection()
|
||||
with conn.begin():
|
||||
_update_inventory_for_provider(
|
||||
conn, rp, inv_list, set([resource_class_id]))
|
||||
rp.generation = _increment_provider_generation(conn, rp)
|
||||
|
||||
|
||||
@db_api.api_context_manager.writer
|
||||
def _delete_inventory(context, rp, resource_class_id):
|
||||
"""Delete up to one Inventory of the given resource_class id."""
|
||||
|
||||
conn = context.session.connection()
|
||||
with conn.begin():
|
||||
if not _delete_inventory_from_provider(conn, rp, [resource_class_id]):
|
||||
raise exception.NotFound(
|
||||
'No inventory of class %s found for delete'
|
||||
% fields.ResourceClass.from_index(resource_class_id))
|
||||
rp.generation = _increment_provider_generation(conn, rp)
|
||||
|
||||
|
||||
@db_api.api_context_manager.writer
|
||||
def _set_inventory(context, rp, inv_list):
|
||||
"""Given an InventoryList object, replaces the inventory of the
|
||||
|
@ -233,11 +281,38 @@ class ResourceProvider(base.NovaObject):
|
|||
db_resource_provider = cls._get_by_uuid_from_db(context, uuid)
|
||||
return cls._from_db_object(context, cls(), db_resource_provider)
|
||||
|
||||
@base.remotable
|
||||
def add_inventory(self, inventory):
|
||||
"""Add one new Inventory to the resource provider.
|
||||
|
||||
Fails if Inventory of the provided resource class is
|
||||
already present.
|
||||
"""
|
||||
_add_inventory(self._context, self, inventory)
|
||||
self.obj_reset_changes()
|
||||
|
||||
@base.remotable
|
||||
def delete_inventory(self, resource_class):
|
||||
"""Delete Inventory of provided resource_class."""
|
||||
resource_class_id = fields.ResourceClass.index(resource_class)
|
||||
_delete_inventory(self._context, self, resource_class_id)
|
||||
self.obj_reset_changes()
|
||||
|
||||
@base.remotable
|
||||
def set_inventory(self, inv_list):
|
||||
"""Set all resource provider Inventory to be the provided list."""
|
||||
_set_inventory(self._context, self, inv_list)
|
||||
self.obj_reset_changes()
|
||||
|
||||
@base.remotable
|
||||
def update_inventory(self, inventory):
|
||||
"""Update one existing Inventory of the same resource class.
|
||||
|
||||
Fails if no Inventory of the same class is present.
|
||||
"""
|
||||
_update_inventory(self._context, self, inventory)
|
||||
self.obj_reset_changes()
|
||||
|
||||
@staticmethod
|
||||
def _create_in_db(context, updates):
|
||||
return _create_rp_in_db(context, updates)
|
||||
|
@ -340,6 +415,11 @@ class Inventory(_HasAResourceProvider):
|
|||
'allocation_ratio': fields.NonNegativeFloatField(default=1.0),
|
||||
}
|
||||
|
||||
@property
|
||||
def capacity(self):
|
||||
"""Inventory capacity, adjusted by allocation_ratio."""
|
||||
return int((self.total - self.reserved) * self.allocation_ratio)
|
||||
|
||||
@base.remotable
|
||||
def create(self):
|
||||
if 'id' in self:
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
from oslo_db import exception as db_exc
|
||||
|
||||
from nova import context
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
|
@ -137,7 +140,7 @@ class ResourceProviderTestCase(test.NoDBTestCase):
|
|||
self.context, resource_provider.uuid))
|
||||
self.assertEqual(33, reloaded_inventories[0].total)
|
||||
|
||||
def test_provider_set_inventory(self):
|
||||
def test_provider_modify_inventory(self):
|
||||
rp = objects.ResourceProvider(context=self.context,
|
||||
uuid=uuidsentinel.rp_uuid,
|
||||
name=uuidsentinel.rp_name)
|
||||
|
@ -205,8 +208,7 @@ class ResourceProviderTestCase(test.NoDBTestCase):
|
|||
max_unit=100,
|
||||
step_size=10,
|
||||
allocation_ratio=1.0)
|
||||
inv_list = objects.InventoryList(objects=[disk_inv])
|
||||
rp.set_inventory(inv_list)
|
||||
rp.update_inventory(disk_inv)
|
||||
|
||||
# generation has bumped
|
||||
self.assertEqual(saved_generation + 1, rp.generation)
|
||||
|
@ -217,7 +219,80 @@ class ResourceProviderTestCase(test.NoDBTestCase):
|
|||
self.assertEqual(1, len(new_inv_list))
|
||||
self.assertEqual(2048, new_inv_list[0].total)
|
||||
|
||||
# fail when inventory bad
|
||||
disk_inv = objects.Inventory(
|
||||
resource_provider=rp,
|
||||
resource_class=fields.ResourceClass.DISK_GB,
|
||||
total=2048,
|
||||
reserved=2048)
|
||||
disk_inv.obj_set_defaults()
|
||||
self.assertRaises(exception.ObjectActionError,
|
||||
rp.update_inventory, disk_inv)
|
||||
|
||||
# generation has not bumped
|
||||
self.assertEqual(saved_generation, rp.generation)
|
||||
|
||||
# delete inventory
|
||||
rp.delete_inventory(fields.ResourceClass.DISK_GB)
|
||||
|
||||
# generation has bumped
|
||||
self.assertEqual(saved_generation + 1, rp.generation)
|
||||
saved_generation = rp.generation
|
||||
|
||||
new_inv_list = objects.InventoryList.get_all_by_resource_provider_uuid(
|
||||
self.context, uuidsentinel.rp_uuid)
|
||||
result = new_inv_list.find(fields.ResourceClass.DISK_GB)
|
||||
self.assertIsNone(result)
|
||||
self.assertRaises(exception.NotFound, rp.delete_inventory,
|
||||
fields.ResourceClass.DISK_GB)
|
||||
|
||||
# check inventory list is empty
|
||||
inv_list = objects.InventoryList.get_all_by_resource_provider_uuid(
|
||||
self.context, uuidsentinel.rp_uuid)
|
||||
self.assertEqual(0, len(inv_list))
|
||||
|
||||
# add some inventory
|
||||
rp.add_inventory(vcpu_inv)
|
||||
inv_list = objects.InventoryList.get_all_by_resource_provider_uuid(
|
||||
self.context, uuidsentinel.rp_uuid)
|
||||
self.assertEqual(1, len(inv_list))
|
||||
|
||||
# generation has bumped
|
||||
self.assertEqual(saved_generation + 1, rp.generation)
|
||||
saved_generation = rp.generation
|
||||
|
||||
# add same inventory again
|
||||
self.assertRaises(db_exc.DBDuplicateEntry,
|
||||
rp.add_inventory, vcpu_inv)
|
||||
|
||||
# generation has not bumped
|
||||
self.assertEqual(saved_generation, rp.generation)
|
||||
|
||||
# fail when generation wrong
|
||||
rp.generation = rp.generation - 1
|
||||
self.assertRaises(exception.ConcurrentUpdateDetected,
|
||||
rp.set_inventory, inv_list)
|
||||
|
||||
def test_delete_inventory_not_found(self):
|
||||
rp = objects.ResourceProvider(context=self.context,
|
||||
uuid=uuidsentinel.rp_uuid,
|
||||
name=uuidsentinel.rp_name)
|
||||
rp.create()
|
||||
error = self.assertRaises(exception.NotFound, rp.delete_inventory,
|
||||
'DISK_GB')
|
||||
self.assertIn('No inventory of class DISK_GB found for delete',
|
||||
str(error))
|
||||
|
||||
def test_update_inventory_not_found(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='DISK_GB',
|
||||
total=2048)
|
||||
disk_inv.obj_set_defaults()
|
||||
error = self.assertRaises(exception.NotFound, rp.update_inventory,
|
||||
disk_inv)
|
||||
self.assertIn('No inventory of class DISK_GB found for update',
|
||||
str(error))
|
||||
|
|
|
@ -1180,7 +1180,7 @@ object_data = {
|
|||
'Quotas': '1.2-1fe4cd50593aaf5d36a6dc5ab3f98fb3',
|
||||
'QuotasNoOp': '1.2-e041ddeb7dc8188ca71706f78aad41c1',
|
||||
'RequestSpec': '1.6-c1cb516acdf120d367a42d343ed695b5',
|
||||
'ResourceProvider': '1.0-94e0e906feb26a24e217935c1e401467',
|
||||
'ResourceProvider': '1.0-421c968ee9b2341dd78b0c19c904d4de',
|
||||
'S3ImageMapping': '1.0-7dd7366a890d82660ed121de9092276e',
|
||||
'SchedulerLimits': '1.0-249c4bd8e62a9b327b7026b7f19cc641',
|
||||
'SchedulerRetries': '1.1-3c9c8b16143ebbb6ad7030e999d14cc0',
|
||||
|
|
|
@ -205,6 +205,22 @@ class _TestInventoryNoDB(object):
|
|||
self.assertEqual(1, inv.step_size)
|
||||
self.assertEqual(1.0, inv.allocation_ratio)
|
||||
|
||||
def test_capacity(self):
|
||||
rp = objects.ResourceProvider(id=_RESOURCE_PROVIDER_ID,
|
||||
uuid=_RESOURCE_PROVIDER_UUID)
|
||||
kwargs = dict(resource_provider=rp,
|
||||
resource_class=_RESOURCE_CLASS_NAME,
|
||||
total=16,
|
||||
reserved=16)
|
||||
inv = objects.Inventory(self.context, **kwargs)
|
||||
inv.obj_set_defaults()
|
||||
|
||||
self.assertEqual(0, inv.capacity)
|
||||
inv.reserved = 15
|
||||
self.assertEqual(1, inv.capacity)
|
||||
inv.allocation_ratio = 2.0
|
||||
self.assertEqual(2, inv.capacity)
|
||||
|
||||
|
||||
class TestInventoryNoDB(test_objects._LocalTest,
|
||||
_TestInventoryNoDB):
|
||||
|
|
Loading…
Reference in New Issue