Add a Usage and UsageList object

These encapsulate the Usage information associated with the
inventories of a resource provider. A single UsageList holds
all the Usage for all resource classes provided by that provider.
Just resource class and usage.

Putting this in place to make further headway on the placement
API, which needs to represent Usage info in this form.

This change intentionally tries to keep the new objects
relatively lightweight.

Partially-Implements: blueprint generic-resource-pools
Change-Id: Ie5eddae4d0cb51bccc815a932a3eefcd5d9ea687
This commit is contained in:
Chris Dent 2016-06-14 14:49:07 +00:00
parent 83d2eeff77
commit 51afe54246
3 changed files with 131 additions and 8 deletions

View File

@ -14,6 +14,7 @@ import six
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy import func from sqlalchemy import func
from sqlalchemy.orm import contains_eager from sqlalchemy.orm import contains_eager
from sqlalchemy import sql
from nova.db.sqlalchemy import api as db_api from nova.db.sqlalchemy import api as db_api
from nova.db.sqlalchemy import api_models as models from nova.db.sqlalchemy import api_models as models
@ -626,3 +627,63 @@ class AllocationList(base.ObjectListBase, base.NovaObject):
context, rp_uuid) context, rp_uuid)
return base.obj_make_list( return base.obj_make_list(
context, cls(context), objects.Allocation, db_allocation_list) context, cls(context), objects.Allocation, db_allocation_list)
@base.NovaObjectRegistry.register
class Usage(base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'resource_class': fields.ResourceClassField(read_only=True),
'usage': fields.NonNegativeIntegerField(),
}
@staticmethod
def _from_db_object(context, target, source):
for field in target.fields:
if field not in ('resource_class'):
setattr(target, field, source[field])
if 'resource_class' not in target:
target.resource_class = (
target.fields['resource_class'].from_index(
source['resource_class_id']))
target._context = context
target.obj_reset_changes()
return target
@base.NovaObjectRegistry.register
class UsageList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('Usage'),
}
@staticmethod
@db_api.placement_context_manager.reader
def _get_all_by_resource_provider_uuid(context, rp_uuid):
query = (context.session.query(models.Inventory.resource_class_id,
func.coalesce(func.sum(models.Allocation.used), 0))
.join(models.ResourceProvider,
models.Inventory.resource_provider_id ==
models.ResourceProvider.id)
.outerjoin(models.Allocation,
sql.and_(models.Inventory.resource_provider_id ==
models.Allocation.resource_provider_id,
models.Inventory.resource_class_id ==
models.Allocation.resource_class_id))
.filter(models.ResourceProvider.uuid == rp_uuid)
.group_by(models.Inventory.resource_class_id))
result = [dict(resource_class_id=item[0], usage=item[1])
for item in query.all()]
return result
@base.remotable_classmethod
def get_all_by_resource_provider_uuid(cls, context, rp_uuid):
usage_list = cls._get_all_by_resource_provider_uuid(context, rp_uuid)
return base.obj_make_list(context, cls(context), Usage, usage_list)

View File

@ -419,14 +419,7 @@ class ResourceProviderTestCase(ResourceProviderBaseCase):
% rp.uuid, str(error)) % rp.uuid, str(error))
class ResourceProviderListTestCase(test.NoDBTestCase): class ResourceProviderListTestCase(ResourceProviderBaseCase):
USES_DB_SELF = True
def setUp(self):
super(ResourceProviderListTestCase, self).setUp()
self.useFixture(fixtures.Database(database='placement'))
self.context = context.RequestContext('fake-user', 'fake-project')
def test_get_all_by_filters(self): def test_get_all_by_filters(self):
for rp_i in ['1', '2']: for rp_i in ['1', '2']:
@ -567,3 +560,70 @@ class TestAllocation(ResourceProviderBaseCase):
self.assertNotIn(fields.ResourceClass.IPV4_ADDRESS, self.assertNotIn(fields.ResourceClass.IPV4_ADDRESS,
[allocation.resource_class [allocation.resource_class
for allocation in allocations]) for allocation in allocations])
class UsageListTestCase(ResourceProviderBaseCase):
def test_get_all_null(self):
for uuid in [uuidsentinel.rp_uuid_1, uuidsentinel.rp_uuid_2]:
rp = objects.ResourceProvider(self.context, name=uuid, uuid=uuid)
rp.create()
usage_list = objects.UsageList.get_all_by_resource_provider_uuid(
self.context, uuidsentinel.rp_uuid_1)
self.assertEqual(0, len(usage_list))
def test_get_all_one_allocation(self):
db_rp, _ = self._make_allocation(rp_uuid=uuidsentinel.rp_uuid)
inv = objects.Inventory(resource_provider=db_rp,
resource_class=fields.ResourceClass.DISK_GB,
total=1024)
inv.obj_set_defaults()
inv_list = objects.InventoryList(objects=[inv])
db_rp.set_inventory(inv_list)
usage_list = objects.UsageList.get_all_by_resource_provider_uuid(
self.context, db_rp.uuid)
self.assertEqual(1, len(usage_list))
self.assertEqual(2, usage_list[0].usage)
self.assertEqual(fields.ResourceClass.DISK_GB,
usage_list[0].resource_class)
def test_get_inventory_no_allocation(self):
db_rp = objects.ResourceProvider(self.context,
name=uuidsentinel.rp_no_inv,
uuid=uuidsentinel.rp_no_inv)
db_rp.create()
inv = objects.Inventory(resource_provider=db_rp,
resource_class=fields.ResourceClass.DISK_GB,
total=1024)
inv.obj_set_defaults()
inv_list = objects.InventoryList(objects=[inv])
db_rp.set_inventory(inv_list)
usage_list = objects.UsageList.get_all_by_resource_provider_uuid(
self.context, db_rp.uuid)
self.assertEqual(1, len(usage_list))
self.assertEqual(0, usage_list[0].usage)
self.assertEqual(fields.ResourceClass.DISK_GB,
usage_list[0].resource_class)
def test_get_all_multiple_inv(self):
db_rp = objects.ResourceProvider(self.context,
name=uuidsentinel.rp_no_inv,
uuid=uuidsentinel.rp_no_inv)
db_rp.create()
disk_inv = objects.Inventory(
resource_provider=db_rp,
resource_class=fields.ResourceClass.DISK_GB, total=1024)
disk_inv.obj_set_defaults()
vcpu_inv = objects.Inventory(
resource_provider=db_rp,
resource_class=fields.ResourceClass.VCPU, total=24)
vcpu_inv.obj_set_defaults()
inv_list = objects.InventoryList(objects=[disk_inv, vcpu_inv])
db_rp.set_inventory(inv_list)
usage_list = objects.UsageList.get_all_by_resource_provider_uuid(
self.context, db_rp.uuid)
self.assertEqual(2, len(usage_list))

View File

@ -1195,6 +1195,8 @@ object_data = {
'TaskLogList': '1.0-cc8cce1af8a283b9d28b55fcd682e777', 'TaskLogList': '1.0-cc8cce1af8a283b9d28b55fcd682e777',
'Tag': '1.1-8b8d7d5b48887651a0e01241672e2963', 'Tag': '1.1-8b8d7d5b48887651a0e01241672e2963',
'TagList': '1.1-55231bdb671ecf7641d6a2e9109b5d8e', 'TagList': '1.1-55231bdb671ecf7641d6a2e9109b5d8e',
'Usage': '1.0-b78f18c3577a38e7a033e46a9725b09b',
'UsageList': '1.0-de53f0fd078c27cc1d43400f4e8bcef8',
'USBDeviceBus': '1.0-e4c7dd6032e46cd74b027df5eb2d4750', 'USBDeviceBus': '1.0-e4c7dd6032e46cd74b027df5eb2d4750',
'VirtCPUFeature': '1.0-3310718d8c72309259a6e39bdefe83ee', 'VirtCPUFeature': '1.0-3310718d8c72309259a6e39bdefe83ee',
'VirtCPUModel': '1.0-6a5cc9f322729fc70ddc6733bacd57d3', 'VirtCPUModel': '1.0-6a5cc9f322729fc70ddc6733bacd57d3',