Skip to content

Commit

Permalink
Merge "placement: Add Trait and TraitList objects"
Browse files Browse the repository at this point in the history
  • Loading branch information
Jenkins authored and openstack-gerrit committed Mar 24, 2017
2 parents aa95d71 + b5bb07c commit cd2d6f5
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 0 deletions.
12 changes: 12 additions & 0 deletions nova/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
118 changes: 118 additions & 0 deletions nova/objects/resource_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -1454,3 +1454,121 @@ def get_all(cls, context):
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)
114 changes: 114 additions & 0 deletions nova/tests/functional/db/test_resource_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -1533,3 +1533,117 @@ def test_save(self):
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))
2 changes: 2 additions & 0 deletions nova/tests/unit/objects/test_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,8 @@ def obj_name(cls):
'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',
Expand Down

0 comments on commit cd2d6f5

Please sign in to comment.