diff --git a/nova/exception.py b/nova/exception.py index 09bcfa59fdb9..41d75c236f1b 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -1875,3 +1875,11 @@ class NUMATopologyUnsupported(Invalid): class MemoryPagesUnsupported(Invalid): msg_fmt = _("Host does not support guests with custom memory page sizes") + + +class EnumFieldInvalid(Invalid): + msg_fmt = _('%(typename)s in %(fieldname)s is not an instance of Enum') + + +class EnumFieldUnset(Invalid): + msg_fmt = _('%(fieldname)s missing field type') diff --git a/nova/objects/fields.py b/nova/objects/fields.py index cd99a31f09e4..e2af176847ec 100644 --- a/nova/objects/fields.py +++ b/nova/objects/fields.py @@ -23,6 +23,7 @@ from oslo_utils import strutils from oslo_utils import timeutils import six +from nova import exception from nova.i18n import _ from nova.network import model as network_model @@ -617,10 +618,22 @@ class StringField(AutoTypedField): AUTO_TYPE = String() -class EnumField(AutoTypedField): - def __init__(self, valid_values, **kwargs): - self.AUTO_TYPE = Enum(valid_values=valid_values) - super(EnumField, self).__init__(**kwargs) +class BaseEnumField(AutoTypedField): + '''This class should not be directly instantiated. Instead + subclass it and set AUTO_TYPE to be a SomeEnum() + where SomeEnum is a subclass of Enum. + ''' + def __init__(self, **kwargs): + if self.AUTO_TYPE is None: + raise exception.EnumFieldUnset( + fieldname=self.__class__.__name__) + + if not isinstance(self.AUTO_TYPE, Enum): + raise exception.EnumFieldInvalid( + typename=self.AUTO_TYPE.__class__.__name, + fieldname=self.__class__.__name__) + + super(BaseEnumField, self).__init__(**kwargs) def __repr__(self): valid_values = self._type._valid_values @@ -636,6 +649,19 @@ class EnumField(AutoTypedField): for k, v in args.items()])) +class EnumField(BaseEnumField): + '''This class allows for anonymous enum types to be + declared, simply by passing in a list of valid values + to its constructor. It is generally preferrable though, + to create an explicit named enum type by sub-classing + the BaeEnumField type directly. See ArchitectureField + for an example. + ''' + def __init__(self, valid_values, **kwargs): + self.AUTO_TYPE = Enum(valid_values=valid_values) + super(EnumField, self).__init__(**kwargs) + + class UUIDField(AutoTypedField): AUTO_TYPE = UUID() diff --git a/nova/tests/unit/objects/test_fields.py b/nova/tests/unit/objects/test_fields.py index 56dc7c180109..e2c03035339f 100644 --- a/nova/tests/unit/objects/test_fields.py +++ b/nova/tests/unit/objects/test_fields.py @@ -35,6 +35,38 @@ class FakeFieldType(fields.FieldType): return value[1:-1] +class FakeEnum(fields.Enum): + FROG = "frog" + PLATYPUS = "platypus" + ALLIGATOR = "alligator" + + ALL = (FROG, PLATYPUS, ALLIGATOR) + + def __init__(self, **kwargs): + super(FakeEnum, self).__init__(valid_values=FakeEnum.ALL, + **kwargs) + + +class FakeEnumAlt(fields.Enum): + FROG = "frog" + PLATYPUS = "platypus" + AARDVARK = "aardvark" + + ALL = (FROG, PLATYPUS, AARDVARK) + + def __init__(self, **kwargs): + super(FakeEnumAlt, self).__init__(valid_values=FakeEnumAlt.ALL, + **kwargs) + + +class FakeEnumField(fields.BaseEnumField): + AUTO_TYPE = FakeEnum() + + +class FakeEnumAltField(fields.BaseEnumField): + AUTO_TYPE = FakeEnumAlt() + + class TestField(test.NoDBTestCase): def setUp(self): super(TestField, self).setUp() @@ -84,6 +116,31 @@ class TestString(TestField): self.assertEqual("'123'", self.field.stringify(123)) +class TestBaseEnum(TestField): + def setUp(self): + super(TestBaseEnum, self).setUp() + self.field = FakeEnumField() + self.coerce_good_values = [('frog', 'frog'), + ('platypus', 'platypus'), + ('alligator', 'alligator')] + self.coerce_bad_values = ['aardvark', 'wookie'] + self.to_primitive_values = self.coerce_good_values[0:1] + self.from_primitive_values = self.coerce_good_values[0:1] + + def test_stringify(self): + self.assertEqual("'platypus'", self.field.stringify('platypus')) + + def test_stringify_invalid(self): + self.assertRaises(ValueError, self.field.stringify, 'aardvark') + + def test_fingerprint(self): + # Notes(yjiang5): make sure changing valid_value will be detected + # in test_objects.test_versions + field1 = FakeEnumField() + field2 = FakeEnumAltField() + self.assertNotEqual(str(field1), str(field2)) + + class TestEnum(TestField): def setUp(self): super(TestEnum, self).setUp()