]> xenbits.xensource.com Git - osstest/openstack-nova.git/commitdiff
placement: add ResourceClass and ResourceClassList
authorJay Pipes <jaypipes@gmail.com>
Fri, 14 Oct 2016 19:53:25 +0000 (15:53 -0400)
committerJay Pipes <jaypipes@gmail.com>
Thu, 3 Nov 2016 15:48:53 +0000 (11:48 -0400)
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

nova/db/sqlalchemy/resource_class_cache.py
nova/objects/resource_provider.py
nova/tests/functional/db/test_resource_class_cache.py
nova/tests/functional/db/test_resource_provider.py
nova/tests/unit/objects/test_objects.py

index e1ce13da68e91ec6303ae476b75ae02e097957d7..9b78e66f031750691f4dbf6aa42dac17a221f073 100644 (file)
@@ -65,6 +65,13 @@ class ResourceClassCache(object):
         self.id_cache = {}
         self.str_cache = {}
 
+    def get_standards(self):
+        """Return a list of {'id': <ID>, 'name': <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
index 778f0910c7b8cbb6788de85ee55c09f65a75c3ec..7300c496192bd7ba747db8adc5a35cd2e7834a31 100644 (file)
@@ -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) + "]"
index 5c9858a266cfffe61c478d1ff3ce4a5e625723e9..cf6479b88c6a6e6a48bc9897bca119223e642d44 100644 (file)
@@ -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
index 614d6f8223b0653d674f0d56626b1e21c2b5d528..b75c1644405faa58e7cfca0e16ad99e39d8bdee8 100644 (file)
@@ -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))
index 38c26791f778bc6bd3150a3f3a62186824afe68e..6783d97fa12dfcb3b2ef8c5ae97e134aa2589762 100644 (file)
@@ -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',