msg_fmt = _("No inventory of class %(resource_class)s found.")
+class ResourceClassExists(NovaException):
+ msg_fmt = _("Resource class %(resource_class)s already exists.")
+
+
class InvalidInventory(Invalid):
msg_fmt = _("Inventory for '%(resource_class)s' on "
"resource provider '%(resource_provider)s' invalid.")
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_db import exception as db_exc
from oslo_log import log as logging
from oslo_utils import versionutils
import six
# Version 1.0: Initial version
VERSION = '1.0'
+ CUSTOM_NAMESPACE = 'CUSTOM_'
+ """All non-standard resource classes must begin with this string."""
+
+ MIN_CUSTOM_RESOURCE_CLASS_ID = 10000
+ """Any user-defined resource classes must have an identifier greater than
+ or equal to this number.
+ """
+
fields = {
'id': fields.IntegerField(read_only=True),
- 'name': fields.ResourceClassField(read_only=True),
+ 'name': fields.ResourceClassField(nullable=False),
}
@staticmethod
target.obj_reset_changes()
return target
+ @staticmethod
+ @db_api.api_context_manager.reader
+ def _get_next_id(context):
+ """Utility method to grab the next resource class identifier to use for
+ user-defined resource classes.
+ """
+ query = context.session.query(func.max(models.ResourceClass.id))
+ max_id = query.one()[0]
+ if not max_id:
+ return ResourceClass.MIN_CUSTOM_RESOURCE_CLASS_ID
+ else:
+ return max_id + 1
+
+ def create(self):
+ if 'id' in self:
+ raise exception.ObjectActionError(action='create',
+ reason='already created')
+ if 'name' not in self:
+ raise exception.ObjectActionError(action='create',
+ reason='name is required')
+ if self.name in fields.ResourceClass.STANDARD:
+ raise exception.ResourceClassExists(resource_class=self.name)
+
+ if not self.name.startswith(self.CUSTOM_NAMESPACE):
+ raise exception.ObjectActionError(
+ action='create',
+ reason='name must start with ' + self.CUSTOM_NAMESPACE)
+
+ updates = self.obj_get_changes()
+ try:
+ rc = self._create_in_db(self._context, updates)
+ self._from_db_object(self._context, self, rc)
+ except db_exc.DBDuplicateEntry:
+ raise exception.ResourceClassExists(resource_class=self.name)
+
+ @staticmethod
+ @db_api.api_context_manager.writer
+ def _create_in_db(context, updates):
+ next_id = ResourceClass._get_next_id(context)
+ rc = models.ResourceClass()
+ rc.update(updates)
+ rc.id = next_id
+ context.session.add(rc)
+ return rc
+
@base.NovaObjectRegistry.register
class ResourceClassList(base.ObjectListBase, base.NovaObject):
the custom classes.
"""
customs = [
- ('IRON_NFV', 10001),
- ('IRON_ENTERPRISE', 10002),
+ ('CUSTOM_IRON_NFV', 10001),
+ ('CUSTOM_IRON_ENTERPRISE', 10002),
]
with self.api_db.get_engine().connect() as conn:
for custom in customs:
rcs = objects.ResourceClassList.get_all(self.context)
expected_count = len(fields.ResourceClass.STANDARD) + len(customs)
self.assertEqual(expected_count, len(rcs))
+
+ def test_create_fail_not_using_namespace(self):
+ rc = objects.ResourceClass(
+ context=self.context,
+ name='IRON_NFV',
+ )
+ exc = self.assertRaises(exception.ObjectActionError, rc.create)
+ self.assertIn('name must start with', str(exc))
+
+ def test_create_duplicate_standard(self):
+ rc = objects.ResourceClass(
+ context=self.context,
+ name=fields.ResourceClass.VCPU,
+ )
+ self.assertRaises(exception.ResourceClassExists, rc.create)
+
+ def test_create(self):
+ rc = objects.ResourceClass(
+ self.context,
+ name='CUSTOM_IRON_NFV',
+ )
+ rc.create()
+ min_id = objects.ResourceClass.MIN_CUSTOM_RESOURCE_CLASS_ID
+ self.assertEqual(min_id, rc.id)
+
+ rc = objects.ResourceClass(
+ self.context,
+ name='CUSTOM_IRON_ENTERPRISE',
+ )
+ rc.create()
+ self.assertEqual(min_id + 1, rc.id)
+
+ def test_create_duplicate_custom(self):
+ rc = objects.ResourceClass(
+ self.context,
+ name='CUSTOM_IRON_NFV',
+ )
+ rc.create()
+ self.assertEqual(objects.ResourceClass.MIN_CUSTOM_RESOURCE_CLASS_ID,
+ rc.id)
+ rc = objects.ResourceClass(
+ self.context,
+ name='CUSTOM_IRON_NFV',
+ )
+ self.assertRaises(exception.ResourceClassExists, rc.create)
import mock
+from nova import context
from nova import exception
from nova import objects
from nova.objects import fields
from nova.objects import resource_provider
+from nova import test
from nova.tests.unit.objects import test_objects
from nova.tests import uuidsentinel as uuids
self.assertRaises(ValueError,
usage.obj_to_primitive,
target_version='1.0')
+
+
+class TestResourceClass(test.NoDBTestCase):
+
+ def setUp(self):
+ super(TestResourceClass, self).setUp()
+ self.user_id = 'fake-user'
+ self.project_id = 'fake-project'
+ self.context = context.RequestContext(self.user_id, self.project_id)
+
+ def test_cannot_create_with_id(self):
+ rc = objects.ResourceClass(self.context, id=1, name='CUSTOM_IRON_NFV')
+ exc = self.assertRaises(exception.ObjectActionError, rc.create)
+ self.assertIn('already created', str(exc))
+
+ def test_cannot_create_requires_name(self):
+ rc = objects.ResourceClass(self.context)
+ exc = self.assertRaises(exception.ObjectActionError, rc.create)
+ self.assertIn('name is required', str(exc))