Merge "placement: Add Trait and TraitList objects"
This commit is contained in:
commit
cd2d6f592b
@ -2137,3 +2137,15 @@ class InvalidEmulatorThreadsPolicy(Invalid):
|
||||
class BadRequirementEmulatorThreadsPolicy(Invalid):
|
||||
msg_fmt = _("An isolated CPU emulator threads option requires a dedicated "
|
||||
"CPU policy option.")
|
||||
|
||||
|
||||
class TraitNotFound(NotFound):
|
||||
msg_fmt = _("No such trait %(name)s.")
|
||||
|
||||
|
||||
class TraitExists(NovaException):
|
||||
msg_fmt = _("The Trait %(name)s already exists")
|
||||
|
||||
|
||||
class TraitCannotDeleteStandard(Invalid):
|
||||
msg_fmt = _("Cannot delete standard trait %(name)s.")
|
||||
|
@ -1454,3 +1454,121 @@ class ResourceClassList(base.ObjectListBase, base.NovaObject):
|
||||
def __repr__(self):
|
||||
strings = [repr(x) for x in self.objects]
|
||||
return "ResourceClassList[" + ", ".join(strings) + "]"
|
||||
|
||||
|
||||
@base.NovaObjectRegistry.register
|
||||
class Trait(base.NovaObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
# All the user-defined traits must begin with this prefix.
|
||||
CUSTOM_NAMESPACE = 'CUSTOM_'
|
||||
|
||||
fields = {
|
||||
'id': fields.IntegerField(read_only=True),
|
||||
'name': fields.StringField(nullable=False)
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object(context, trait, db_trait):
|
||||
for key in trait.fields:
|
||||
setattr(trait, key, db_trait[key])
|
||||
trait.obj_reset_changes()
|
||||
trait._context = context
|
||||
return trait
|
||||
|
||||
@staticmethod
|
||||
@db_api.api_context_manager.writer
|
||||
def _create_in_db(context, updates):
|
||||
trait = models.Trait()
|
||||
trait.update(updates)
|
||||
context.session.add(trait)
|
||||
return trait
|
||||
|
||||
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 not self.name.startswith(self.CUSTOM_NAMESPACE):
|
||||
raise exception.ObjectActionError(
|
||||
action='create',
|
||||
reason='name must start with %s' % self.CUSTOM_NAMESPACE)
|
||||
|
||||
updates = self.obj_get_changes()
|
||||
|
||||
try:
|
||||
db_trait = self._create_in_db(self._context, updates)
|
||||
except db_exc.DBDuplicateEntry:
|
||||
raise exception.TraitExists(name=self.name)
|
||||
|
||||
self._from_db_object(self._context, self, db_trait)
|
||||
|
||||
@staticmethod
|
||||
@db_api.api_context_manager.reader
|
||||
def _get_by_name_from_db(context, name):
|
||||
result = context.session.query(models.Trait).filter_by(
|
||||
name=name).first()
|
||||
if not result:
|
||||
raise exception.TraitNotFound(name=name)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_by_name(cls, context, name):
|
||||
db_trait = cls._get_by_name_from_db(context, name)
|
||||
return cls._from_db_object(context, cls(), db_trait)
|
||||
|
||||
@staticmethod
|
||||
@db_api.api_context_manager.writer
|
||||
def _destroy_in_db(context, name):
|
||||
res = context.session.query(models.Trait).filter_by(
|
||||
name=name).delete()
|
||||
if not res:
|
||||
raise exception.TraitNotFound(name=name)
|
||||
|
||||
def destroy(self):
|
||||
if 'name' not in self:
|
||||
raise exception.ObjectActionError(action='destroy',
|
||||
reason='name is required')
|
||||
|
||||
if not self.name.startswith(self.CUSTOM_NAMESPACE):
|
||||
raise exception.TraitCannotDeleteStandard(name=self.name)
|
||||
|
||||
if 'id' not in self:
|
||||
raise exception.ObjectActionError(action='destroy',
|
||||
reason='ID attribute not found')
|
||||
|
||||
self._destroy_in_db(self._context, self.name)
|
||||
|
||||
|
||||
@base.NovaObjectRegistry.register
|
||||
class TraitList(base.ObjectListBase, base.NovaObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('Trait')
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@db_api.api_context_manager.reader
|
||||
def _get_all_from_db(context, filters):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
query = context.session.query(models.Trait)
|
||||
if 'name_in' in filters:
|
||||
query = query.filter(models.Trait.name.in_(filters['name_in']))
|
||||
if 'prefix' in filters:
|
||||
query = query.filter(
|
||||
models.Trait.name.like(filters['prefix'] + '%'))
|
||||
|
||||
return query.all()
|
||||
|
||||
@base.remotable_classmethod
|
||||
def get_all(cls, context, filters=None):
|
||||
db_traits = cls._get_all_from_db(context, filters)
|
||||
return base.obj_make_list(context, cls(context), Trait, db_traits)
|
||||
|
@ -1533,3 +1533,117 @@ class ResourceClassTestCase(ResourceProviderBaseCase):
|
||||
objects.ResourceClass.get_by_name,
|
||||
self.context,
|
||||
'CUSTOM_IRON_NFV')
|
||||
|
||||
|
||||
class ResourceProviderTraitsTestCase(ResourceProviderBaseCase):
|
||||
|
||||
def _assert_traits(self, expected_traits, traits_objs):
|
||||
expected_traits.sort()
|
||||
traits = []
|
||||
for obj in traits_objs:
|
||||
traits.append(obj.name)
|
||||
traits.sort()
|
||||
self.assertEqual(expected_traits, traits)
|
||||
|
||||
def test_trait_create(self):
|
||||
t = objects.Trait(self.context)
|
||||
t.name = 'CUSTOM_TRAIT_A'
|
||||
t.create()
|
||||
self.assertIn('id', t)
|
||||
self.assertEqual(t.name, 'CUSTOM_TRAIT_A')
|
||||
|
||||
def test_trait_create_with_id_set(self):
|
||||
t = objects.Trait(self.context)
|
||||
t.name = 'CUSTOM_TRAIT_A'
|
||||
t.id = 1
|
||||
self.assertRaises(exception.ObjectActionError, t.create)
|
||||
|
||||
def test_trait_create_without_name_set(self):
|
||||
t = objects.Trait(self.context)
|
||||
self.assertRaises(exception.ObjectActionError, t.create)
|
||||
|
||||
def test_trait_create_without_custom_prefix(self):
|
||||
t = objects.Trait(self.context)
|
||||
t.name = 'TRAIT_A'
|
||||
self.assertRaises(exception.ObjectActionError, t.create)
|
||||
|
||||
def test_trait_create_duplicated_trait(self):
|
||||
trait = objects.Trait(self.context)
|
||||
trait.name = 'CUSTOM_TRAIT_A'
|
||||
trait.create()
|
||||
tmp_trait = objects.Trait.get_by_name(self.context, 'CUSTOM_TRAIT_A')
|
||||
self.assertEqual('CUSTOM_TRAIT_A', tmp_trait.name)
|
||||
duplicated_trait = objects.Trait(self.context)
|
||||
duplicated_trait.name = 'CUSTOM_TRAIT_A'
|
||||
self.assertRaises(exception.TraitExists, duplicated_trait.create)
|
||||
|
||||
def test_trait_get(self):
|
||||
t = objects.Trait(self.context)
|
||||
t.name = 'CUSTOM_TRAIT_A'
|
||||
t.create()
|
||||
t = objects.Trait.get_by_name(self.context, 'CUSTOM_TRAIT_A')
|
||||
self.assertEqual(t.name, 'CUSTOM_TRAIT_A')
|
||||
|
||||
def test_trait_get_non_existed_trait(self):
|
||||
self.assertRaises(exception.TraitNotFound,
|
||||
objects.Trait.get_by_name, self.context, 'CUSTOM_TRAIT_A')
|
||||
|
||||
def test_trait_destroy(self):
|
||||
t = objects.Trait(self.context)
|
||||
t.name = 'CUSTOM_TRAIT_A'
|
||||
t.create()
|
||||
t = objects.Trait.get_by_name(self.context, 'CUSTOM_TRAIT_A')
|
||||
self.assertEqual(t.name, 'CUSTOM_TRAIT_A')
|
||||
t.destroy()
|
||||
self.assertRaises(exception.TraitNotFound, objects.Trait.get_by_name,
|
||||
self.context, 'CUSTOM_TRAIT_A')
|
||||
|
||||
def test_trait_destroy_with_standard_trait(self):
|
||||
t = objects.Trait(self.context)
|
||||
t.id = 1
|
||||
t.name = 'HW_CPU_X86_AVX'
|
||||
self.assertRaises(exception.TraitCannotDeleteStandard, t.destroy)
|
||||
|
||||
def test_traits_get_all(self):
|
||||
trait_names = ['CUSTOM_TRAIT_A', 'CUSTOM_TRAIT_B', 'CUSTOM_TRAIT_C']
|
||||
for name in trait_names:
|
||||
t = objects.Trait(self.context)
|
||||
t.name = name
|
||||
t.create()
|
||||
|
||||
self._assert_traits(trait_names,
|
||||
objects.TraitList.get_all(self.context))
|
||||
|
||||
def test_traits_get_all_with_name_in_filter(self):
|
||||
trait_names = ['CUSTOM_TRAIT_A', 'CUSTOM_TRAIT_B', 'CUSTOM_TRAIT_C']
|
||||
for name in trait_names:
|
||||
t = objects.Trait(self.context)
|
||||
t.name = name
|
||||
t.create()
|
||||
|
||||
traits = objects.TraitList.get_all(self.context,
|
||||
filters={'name_in': ['CUSTOM_TRAIT_A', 'CUSTOM_TRAIT_B']})
|
||||
self._assert_traits(['CUSTOM_TRAIT_A', 'CUSTOM_TRAIT_B'], traits)
|
||||
|
||||
def test_traits_get_all_with_non_existed_name(self):
|
||||
traits = objects.TraitList.get_all(self.context,
|
||||
filters={'name_in': ['CUSTOM_TRAIT_X', 'CUSTOM_TRAIT_Y']})
|
||||
self.assertEqual(0, len(traits))
|
||||
|
||||
def test_traits_get_all_with_prefix_filter(self):
|
||||
trait_names = ['CUSTOM_TRAIT_A', 'CUSTOM_TRAIT_B', 'CUSTOM_TRAIT_C']
|
||||
for name in trait_names:
|
||||
t = objects.Trait(self.context)
|
||||
t.name = name
|
||||
t.create()
|
||||
|
||||
traits = objects.TraitList.get_all(self.context,
|
||||
filters={'prefix': 'CUSTOM'})
|
||||
self._assert_traits(
|
||||
['CUSTOM_TRAIT_A', 'CUSTOM_TRAIT_B', 'CUSTOM_TRAIT_C'],
|
||||
traits)
|
||||
|
||||
def test_traits_get_all_with_non_existed_prefix(self):
|
||||
traits = objects.TraitList.get_all(self.context,
|
||||
filters={"prefix": "NOT_EXISTED"})
|
||||
self.assertEqual(0, len(traits))
|
||||
|
@ -1163,6 +1163,8 @@ object_data = {
|
||||
'TaskLogList': '1.0-cc8cce1af8a283b9d28b55fcd682e777',
|
||||
'Tag': '1.1-8b8d7d5b48887651a0e01241672e2963',
|
||||
'TagList': '1.1-55231bdb671ecf7641d6a2e9109b5d8e',
|
||||
'Trait': '1.0-2b58dd7c5037153cb4bfc94c0ae5dd3a',
|
||||
'TraitList': '1.0-ff48fc1575f20800796b48266114c608',
|
||||
'Usage': '1.1-b738dbebeb20e3199fc0ebca6e292a47',
|
||||
'UsageList': '1.1-15ecf022a68ddbb8c2a6739cfc9f8f5e',
|
||||
'USBDeviceBus': '1.0-e4c7dd6032e46cd74b027df5eb2d4750',
|
||||
|
Loading…
x
Reference in New Issue
Block a user