diff --git a/nova/objects/resource_provider.py b/nova/objects/resource_provider.py index d4af5406812f..bca43812788a 100644 --- a/nova/objects/resource_provider.py +++ b/nova/objects/resource_provider.py @@ -14,6 +14,7 @@ import six import sqlalchemy as sa from sqlalchemy import func 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_models as models @@ -626,3 +627,63 @@ class AllocationList(base.ObjectListBase, base.NovaObject): context, rp_uuid) return base.obj_make_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) diff --git a/nova/tests/functional/db/test_resource_provider.py b/nova/tests/functional/db/test_resource_provider.py index 3c9b7aa93c8a..4acaf66993a5 100644 --- a/nova/tests/functional/db/test_resource_provider.py +++ b/nova/tests/functional/db/test_resource_provider.py @@ -419,14 +419,7 @@ class ResourceProviderTestCase(ResourceProviderBaseCase): % rp.uuid, str(error)) -class ResourceProviderListTestCase(test.NoDBTestCase): - - 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') +class ResourceProviderListTestCase(ResourceProviderBaseCase): def test_get_all_by_filters(self): for rp_i in ['1', '2']: @@ -567,3 +560,70 @@ class TestAllocation(ResourceProviderBaseCase): self.assertNotIn(fields.ResourceClass.IPV4_ADDRESS, [allocation.resource_class 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)) diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py index db0dccb2f844..7e5fbacba277 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py @@ -1195,6 +1195,8 @@ object_data = { 'TaskLogList': '1.0-cc8cce1af8a283b9d28b55fcd682e777', 'Tag': '1.1-8b8d7d5b48887651a0e01241672e2963', 'TagList': '1.1-55231bdb671ecf7641d6a2e9109b5d8e', + 'Usage': '1.0-b78f18c3577a38e7a033e46a9725b09b', + 'UsageList': '1.0-de53f0fd078c27cc1d43400f4e8bcef8', 'USBDeviceBus': '1.0-e4c7dd6032e46cd74b027df5eb2d4750', 'VirtCPUFeature': '1.0-3310718d8c72309259a6e39bdefe83ee', 'VirtCPUModel': '1.0-6a5cc9f322729fc70ddc6733bacd57d3',