From: Jay Pipes Date: Fri, 14 Oct 2016 19:53:25 +0000 (-0400) Subject: placement: add ResourceClass and ResourceClassList X-Git-Url: http://xenbits.xensource.com/gitweb?a=commitdiff_plain;h=328c45bd59227f7a06e96a4719510d0de3156cda;p=osstest%2Fopenstack-nova.git placement: add ResourceClass and ResourceClassList In preparation for the placement REST API getting CRUD operations on resource classes, create ResourceClass and ResourceClassList objects. In this patch, we only add a ResourceClassList.get_all() method that merges the standardized resource classes with any custom resource classes that might have been added to the resource_classes table. Followup patches will add the create(), delete() and save() methods and then finally the REST API changes. Change-Id: Ia9462698fafe3c70a51bb3746a6c2a45175fadf3 blueprint: custom-resource-classes --- diff --git a/nova/db/sqlalchemy/resource_class_cache.py b/nova/db/sqlalchemy/resource_class_cache.py index e1ce13da68..9b78e66f03 100644 --- a/nova/db/sqlalchemy/resource_class_cache.py +++ b/nova/db/sqlalchemy/resource_class_cache.py @@ -65,6 +65,13 @@ class ResourceClassCache(object): self.id_cache = {} self.str_cache = {} + def get_standards(self): + """Return a list of {'id': , 'name': for all standard + resource classes. + """ + return [{'id': fields.ResourceClass.STANDARD.index(s), 'name': s} + for s in fields.ResourceClass.STANDARD] + def id_from_string(self, rc_str): """Given a string representation of a resource class -- e.g. "DISK_GB" or "IRON_SILVER" -- return the integer code for the resource class. For diff --git a/nova/objects/resource_provider.py b/nova/objects/resource_provider.py index 778f0910c7..7300c49619 100644 --- a/nova/objects/resource_provider.py +++ b/nova/objects/resource_provider.py @@ -30,6 +30,7 @@ from nova.objects import fields _ALLOC_TBL = models.Allocation.__table__ _INV_TBL = models.Inventory.__table__ _RP_TBL = models.ResourceProvider.__table__ +_RC_TBL = models.ResourceClass.__table__ _RC_CACHE = None LOG = logging.getLogger(__name__) @@ -1007,3 +1008,51 @@ class UsageList(base.ObjectListBase, base.NovaObject): def __repr__(self): strings = [repr(x) for x in self.objects] return "UsageList[" + ", ".join(strings) + "]" + + +@base.NovaObjectRegistry.register +class ResourceClass(base.NovaObject): + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'id': fields.IntegerField(read_only=True), + 'name': fields.ResourceClassField(read_only=True), + } + + @staticmethod + def _from_db_object(context, target, source): + for field in target.fields: + setattr(target, field, source[field]) + + target._context = context + target.obj_reset_changes() + return target + + +@base.NovaObjectRegistry.register +class ResourceClassList(base.ObjectListBase, base.NovaObject): + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'objects': fields.ListOfObjectsField('ResourceClass'), + } + + @staticmethod + @db_api.api_context_manager.reader + def _get_all(context): + _ensure_rc_cache(context) + standards = _RC_CACHE.get_standards() + customs = list(context.session.query(models.ResourceClass).all()) + return standards + customs + + @base.remotable_classmethod + def get_all(cls, context): + resource_classes = cls._get_all(context) + return base.obj_make_list(context, cls(context), + objects.ResourceClass, resource_classes) + + def __repr__(self): + strings = [repr(x) for x in self.objects] + return "ResourceClassList[" + ", ".join(strings) + "]" diff --git a/nova/tests/functional/db/test_resource_class_cache.py b/nova/tests/functional/db/test_resource_class_cache.py index 5c9858a266..cf6479b88c 100644 --- a/nova/tests/functional/db/test_resource_class_cache.py +++ b/nova/tests/functional/db/test_resource_class_cache.py @@ -14,6 +14,7 @@ import mock from nova.db.sqlalchemy import resource_class_cache as rc_cache from nova import exception +from nova.objects import fields from nova import test from nova.tests import fixtures @@ -43,6 +44,14 @@ class TestResourceClassCache(test.TestCase): self.assertFalse(sel_mock.called) + def test_get_standards(self): + cache = rc_cache.ResourceClassCache(self.context) + standards = cache.get_standards() + self.assertEqual(len(standards), len(fields.ResourceClass.STANDARD)) + names = (rc['name'] for rc in standards) + for name in fields.ResourceClass.STANDARD: + self.assertIn(name, names) + def test_rc_cache_custom(self): """Test that non-standard, custom resource classes hit the database and return appropriate results, caching the results after a single diff --git a/nova/tests/functional/db/test_resource_provider.py b/nova/tests/functional/db/test_resource_provider.py index 614d6f8223..b75c164440 100644 --- a/nova/tests/functional/db/test_resource_provider.py +++ b/nova/tests/functional/db/test_resource_provider.py @@ -18,6 +18,7 @@ from nova import context from nova import exception from nova import objects from nova.objects import fields +from nova.objects import resource_provider as rp_obj from nova import test from nova.tests import fixtures from nova.tests import uuidsentinel @@ -39,10 +40,14 @@ DISK_ALLOCATION = dict( ) -class ResourceProviderBaseCase(test.TestCase): +class ResourceProviderBaseCase(test.NoDBTestCase): + + USES_DB_SELF = True def setUp(self): super(ResourceProviderBaseCase, self).setUp() + self.useFixture(fixtures.Database()) + self.api_db = self.useFixture(fixtures.Database(database='api')) self.context = context.RequestContext('fake-user', 'fake-project') def _make_allocation(self, rp_uuid=None): @@ -894,3 +899,33 @@ class UsageListTestCase(ResourceProviderBaseCase): usage_list = objects.UsageList.get_all_by_resource_provider_uuid( self.context, db_rp.uuid) self.assertEqual(2, len(usage_list)) + + +class ResourceClassListTestCase(ResourceProviderBaseCase): + + def test_get_all_no_custom(self): + """Test that if we haven't yet added any custom resource classes, that + we only get a list of ResourceClass objects representing the standard + classes. + """ + rcs = objects.ResourceClassList.get_all(self.context) + self.assertEqual(len(fields.ResourceClass.STANDARD), len(rcs)) + + def test_get_all_with_custom(self): + """Test that if we add some custom resource classes, that we get a list + of ResourceClass objects representing the standard classes as well as + the custom classes. + """ + customs = [ + ('IRON_NFV', 10001), + ('IRON_ENTERPRISE', 10002), + ] + with self.api_db.get_engine().connect() as conn: + for custom in customs: + c_name, c_id = custom + ins = rp_obj._RC_TBL.insert().values(id=c_id, name=c_name) + conn.execute(ins) + + rcs = objects.ResourceClassList.get_all(self.context) + expected_count = len(fields.ResourceClass.STANDARD) + len(customs) + self.assertEqual(expected_count, len(rcs)) diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py index 38c26791f7..6783d97fa1 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py @@ -1180,6 +1180,8 @@ object_data = { 'Quotas': '1.2-1fe4cd50593aaf5d36a6dc5ab3f98fb3', 'QuotasNoOp': '1.2-e041ddeb7dc8188ca71706f78aad41c1', 'RequestSpec': '1.7-5ff3e9df208bf25f8215f1b87624970d', + 'ResourceClass': '1.0-e6b367e2cf1733c5f3526f20a3286fe9', + 'ResourceClassList': '1.0-4ee0d9efdfd681fed822da88376e04d2', 'ResourceProvider': '1.1-7bbcd5ea1c51782692f55489ab08dea6', 'ResourceProviderList': '1.0-82bd48d8d0f7913bbe7266f3835c81bf', 'S3ImageMapping': '1.0-7dd7366a890d82660ed121de9092276e',