Merge "Added a state machine field"

This commit is contained in:
Jenkins 2016-01-24 22:03:08 +00:00 committed by Gerrit Code Review
commit 39e36aed68
2 changed files with 156 additions and 0 deletions
oslo_versionedobjects

@ -691,6 +691,75 @@ class EnumField(BaseEnumField):
super(EnumField, self).__init__(**kwargs)
class StateMachine(EnumField):
"""A mixin that can be applied to an EnumField to enforce a state machine
e.g: Setting the code below on a field will ensure an object cannot
transition from ERROR to ACTIVE
class FakeStateMachineField(fields.EnumField, fields.StateMachine):
ACTIVE = 'ACTIVE'
PENDING = 'PENDING'
ERROR = 'ERROR'
DELETED = 'DELETED'
ALLOWED_TRANSITIONS = {
ACTIVE: {
PENDING,
ERROR,
DELETED,
},
PENDING: {
ACTIVE,
ERROR
},
ERROR: {
PENDING,
},
DELETED: {} # This is a terminal state
}
_TYPES = (ACTIVE, PENDING, ERROR, DELETED)
def __init__(self, **kwargs):
super(FakeStateMachineField, self).__init__(self._TYPES, **kwargs)
"""
# This is dict of states, that have dicts of states an object is
# allowed to transition to
ALLOWED_TRANSITIONS = {}
def coerce(self, obj, attr, value):
super(StateMachine, self).coerce(obj, attr, value)
msg = _("%(object)s's are not allowed transition out of %(value)s "
"state")
if attr in obj:
current_value = getattr(obj, attr)
else:
return value
if current_value in self.ALLOWED_TRANSITIONS:
if value in self.ALLOWED_TRANSITIONS[current_value]:
return value
else:
msg = _(
"%(object)s's are not allowed to transition out of "
"'%(current_value)s' state to '%(value)s' state, choose "
"from %(options)r")
msg = msg % {
'object': obj.obj_name(),
'current_value': current_value,
'value': value,
'options': [x for x in self.ALLOWED_TRANSITIONS[current_value]]
}
raise ValueError(msg)
class UUIDField(AutoTypedField):
AUTO_TYPE = UUID()

@ -18,6 +18,7 @@ import iso8601
import mock
import netaddr
import six
import testtools
from oslo_versionedobjects import _utils
from oslo_versionedobjects import base as obj_base
@ -65,6 +66,32 @@ class FakeEnumField(fields.BaseEnumField):
AUTO_TYPE = FakeEnum()
class FakeStateMachineField(fields.StateMachine):
ACTIVE = 'ACTIVE'
PENDING = 'PENDING'
ERROR = 'ERROR'
ALLOWED_TRANSITIONS = {
ACTIVE: {
PENDING,
ERROR
},
PENDING: {
ACTIVE,
ERROR
},
ERROR: {
PENDING,
},
}
_TYPES = (ACTIVE, PENDING, ERROR)
def __init__(self, **kwargs):
super(FakeStateMachineField, self).__init__(self._TYPES, **kwargs)
class FakeEnumAltField(fields.BaseEnumField):
AUTO_TYPE = FakeEnumAlt()
@ -225,6 +252,66 @@ class TestEnum(TestField):
fields.EnumField, True)
class TestStateMachine(TestField):
def test_good_transitions(self):
@obj_base.VersionedObjectRegistry.register
class AnObject(obj_base.VersionedObject):
fields = {
'status': FakeStateMachineField(),
}
obj = AnObject()
obj.status = FakeStateMachineField.ACTIVE
obj.status = FakeStateMachineField.PENDING
obj.status = FakeStateMachineField.ERROR
obj.status = FakeStateMachineField.PENDING
obj.status = FakeStateMachineField.ACTIVE
def test_bad_transitions(self):
@obj_base.VersionedObjectRegistry.register
class AnObject(obj_base.VersionedObject):
fields = {
'status': FakeStateMachineField(),
}
obj = AnObject()
with testtools.ExpectedException(
ValueError,
msg="AnObject's are not allowed transition out "
"of 'ERROR' state to 'PENDING' state, choose from "
"['PENDING']"):
obj.status = FakeStateMachineField.ERROR
obj.status = FakeStateMachineField.ACTIVE
def test_bad_initial_value(self):
@obj_base.VersionedObjectRegistry.register
class AnObject(obj_base.VersionedObject):
fields = {
'status': FakeStateMachineField(),
}
obj = AnObject()
with testtools.ExpectedException(ValueError):
obj.status = "FOO"
def test_bad_updated_value(self):
@obj_base.VersionedObjectRegistry.register
class AnObject(obj_base.VersionedObject):
fields = {
'status': FakeStateMachineField(),
}
obj = AnObject()
with testtools.ExpectedException(ValueError):
obj.status = FakeStateMachineField.ACTIVE
obj.status = "FOO"
class TestInteger(TestField):
def setUp(self):
super(TestField, self).setUp()