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:
Chris Dent 2016-06-10 12:43:23 +00:00
parent 8435999c78
commit 7402d134e1
4 changed files with 177 additions and 6 deletions

View File

@ -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:

View File

@ -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))

View File

@ -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',

View File

@ -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):