Merge "placement: Add Trait and TraitList objects"

This commit is contained in:
Jenkins 2017-03-24 19:59:58 +00:00 committed by Gerrit Code Review
commit cd2d6f592b
4 changed files with 246 additions and 0 deletions

View File

@ -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.")

View File

@ -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)

View File

@ -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))

View File

@ -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',