Introduce state management for trunk resources
A trunk is a complex resource. This patch introduces the states and documents the state machine for the trunk resource. Only user initiated state transitions are introduced in this patch. Driver-initiated state transitions will be introduced as follow up. Partially-implements: bp/vlan-aware-vms Change-Id: I0446e26f9ee5193a3270336ac64d0186fe5a6c7b
This commit is contained in:
parent
26bbc783d2
commit
dee5544446
|
@ -96,7 +96,7 @@ class Trunk(base.NeutronDbObject):
|
|||
'sub_ports': obj_fields.ListOfObjectsField(SubPort.__name__),
|
||||
}
|
||||
|
||||
fields_no_update = ['tenant_id', 'port_id', 'status']
|
||||
fields_no_update = ['tenant_id', 'port_id']
|
||||
|
||||
synthetic_fields = ['sub_ports']
|
||||
|
||||
|
|
|
@ -12,11 +12,57 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
# Valid trunk statuses
|
||||
|
||||
# The trunk is happy, yay!
|
||||
# A trunk remains in ACTIVE state when updates like name or admin_status_up
|
||||
# occur. It goes back to ACTIVE state from other states (e.g. BUILD) when
|
||||
# logical and physical resource provisioning has completed successfully. The
|
||||
# attribute ADMIN_STATE_UP is not to be confused with STATUS: the former
|
||||
# indicates whether a trunk can be managed. If a trunk has admin_state_up
|
||||
# equal to false, the trunk plugin will reject any user request to manage
|
||||
# the trunk resources (i.e. adding/removing sub-ports).
|
||||
ACTIVE_STATUS = 'ACTIVE'
|
||||
|
||||
# The server has acknowledged the user request: a user has asked to either
|
||||
# create a trunk or add/remove resources to a trunk, and the plugin has
|
||||
# created/updated the logical resource. The request has been passed along
|
||||
# to a backend, and the physical resources associated to the trunk are
|
||||
# in the process of being provisioned.
|
||||
PENDING_STATUS = 'PENDING'
|
||||
|
||||
# A driver/backend has acknowledged the server request: once the server
|
||||
# notifies the driver/backend, a trunk is in BUILD state while the
|
||||
# backend provisions the trunk resources.
|
||||
BUILD_STATUS = 'BUILD'
|
||||
|
||||
# Should any temporary system failure occur during the provisioning process,
|
||||
# a trunk is in DEGRADED state. This means that the trunk was only
|
||||
# partially provisioned, and only a subset of the subports were added
|
||||
# successfully to the trunk. The operation of removing/adding the faulty
|
||||
# subports may be attempted as a recovery measure.
|
||||
DEGRADED_STATUS = 'DEGRADED'
|
||||
|
||||
# Due to unforeseen circumstances, the user request has led to a conflict, and
|
||||
# the trunk cannot be provisioned correctly for a subset of subports. For
|
||||
# instance, a subport belonging to a network might not be compatible with
|
||||
# the current trunk configuration, or the binding process leads to a persistent
|
||||
# failure. Removing the 'offending' resource may be attempted as a recovery
|
||||
# measure, but readding it to the trunk should lead to the same error
|
||||
# condition. A trunk in ERROR status should be brought back to a sane status
|
||||
# (i.e. any state except ERROR state) before attempting to add more subports,
|
||||
# therefore requests of adding more subports must be rejected to avoid
|
||||
# cascading errors.
|
||||
ERROR_STATUS = 'ERROR'
|
||||
|
||||
|
||||
# String literals for identifying trunk resources
|
||||
PARENT_PORT = 'parent_port'
|
||||
SUBPORTS = 'subports'
|
||||
TRUNK = 'trunk'
|
||||
TRUNK_PLUGIN = 'trunk_plugin'
|
||||
|
||||
|
||||
# String literals for segmentation types
|
||||
VLAN = 'vlan'
|
||||
|
|
|
@ -57,3 +57,8 @@ class TrunkInUse(n_exc.InUse):
|
|||
|
||||
class TrunkDisabled(n_exc.Conflict):
|
||||
message = _("Trunk %(trunk_id)s is currently disabled.")
|
||||
|
||||
|
||||
class TrunkInErrorState(n_exc.Conflict):
|
||||
message = _("Trunk %(trunk_id)s is in error state. Attempt "
|
||||
"to resolve the error condition before proceeding.")
|
||||
|
|
|
@ -118,12 +118,19 @@ class TrunkPlugin(service_base.ServicePluginBase,
|
|||
segmentation_type=p['segmentation_type'])
|
||||
for p in trunk['sub_ports']]
|
||||
admin_state_up = trunk.get('admin_state_up', True)
|
||||
# NOTE(status_police): a trunk is created in PENDING status. Depending
|
||||
# on the nature of the create request, a driver may set the status
|
||||
# immediately to ACTIVE if no physical provisioning is required.
|
||||
# Otherwise a transition to BUILD (or ERROR) should be expected
|
||||
# depending on how the driver reacts. PRECOMMIT failures prevent the
|
||||
# trunk from being created altogether.
|
||||
trunk_obj = trunk_objects.Trunk(context=context,
|
||||
admin_state_up=admin_state_up,
|
||||
id=uuidutils.generate_uuid(),
|
||||
name=trunk.get('name', ""),
|
||||
tenant_id=trunk['tenant_id'],
|
||||
port_id=trunk['port_id'],
|
||||
status=constants.PENDING_STATUS,
|
||||
sub_ports=sub_ports)
|
||||
with db_api.autonested_transaction(context.session):
|
||||
trunk_obj.create()
|
||||
|
@ -143,6 +150,10 @@ class TrunkPlugin(service_base.ServicePluginBase,
|
|||
with db_api.autonested_transaction(context.session):
|
||||
trunk_obj = self._get_trunk(context, trunk_id)
|
||||
original_trunk = copy.deepcopy(trunk_obj)
|
||||
# NOTE(status_police): a trunk status should not change during an
|
||||
# update_trunk(), even in face of PRECOMMIT failures. This is
|
||||
# because only name and admin_state_up are being affected, and
|
||||
# these are DB properties only.
|
||||
trunk_obj.update_fields(trunk_data, reset_changes=True)
|
||||
trunk_obj.update()
|
||||
payload = callbacks.TrunkPayload(context, trunk_id,
|
||||
|
@ -161,6 +172,10 @@ class TrunkPlugin(service_base.ServicePluginBase,
|
|||
rules.trunk_can_be_managed(context, trunk)
|
||||
trunk_port_validator = rules.TrunkPortValidator(trunk.port_id)
|
||||
if not trunk_port_validator.is_bound(context):
|
||||
# NOTE(status_police): when a trunk is deleted, the logical
|
||||
# object disappears from the datastore, therefore there is no
|
||||
# status transition involved. If PRECOMMIT failures occur,
|
||||
# the trunk remains in the status where it was.
|
||||
trunk.delete()
|
||||
payload = callbacks.TrunkPayload(context, trunk_id,
|
||||
original_trunk=trunk)
|
||||
|
@ -184,8 +199,24 @@ class TrunkPlugin(service_base.ServicePluginBase,
|
|||
|
||||
with db_api.autonested_transaction(context.session):
|
||||
trunk = self._get_trunk(context, trunk_id)
|
||||
original_trunk = copy.deepcopy(trunk)
|
||||
rules.trunk_can_be_managed(context, trunk)
|
||||
original_trunk = copy.deepcopy(trunk)
|
||||
# NOTE(status_police): the trunk status should transition to
|
||||
# PENDING (and consequently to BUILD and finally in ACTIVE
|
||||
# or ERROR), only if it is not in ERROR status already. A user
|
||||
# should attempt to resolve the ERROR condition before adding
|
||||
# more subports to the trunk. Should a trunk be in PENDING or
|
||||
# BUILD state (e.g. when dealing with multiple concurrent
|
||||
# requests), the status is still forced to PENDING and thus
|
||||
# can potentially overwrite an interleaving state change to
|
||||
# ACTIVE. Eventually the driver should bring the status back
|
||||
# to ACTIVE or ERROR.
|
||||
if trunk.status == constants.ERROR_STATUS:
|
||||
raise trunk_exc.TrunkInErrorState(trunk_id=trunk_id)
|
||||
else:
|
||||
trunk.status = constants.PENDING_STATUS
|
||||
trunk.update()
|
||||
|
||||
for subport in subports:
|
||||
obj = trunk_objects.SubPort(
|
||||
context=context,
|
||||
|
@ -240,6 +271,15 @@ class TrunkPlugin(service_base.ServicePluginBase,
|
|||
|
||||
del trunk.sub_ports[:]
|
||||
trunk.sub_ports.extend(current_subports.values())
|
||||
# NOTE(status_police): the trunk status should transition to
|
||||
# PENDING irrespective of the status in which it is in to allow
|
||||
# the user to resolve potential conflicts due to prior add_subports
|
||||
# operations.
|
||||
# Should a trunk be in PENDING or BUILD state (e.g. when dealing
|
||||
# with multiple concurrent requests), the status is still forced
|
||||
# to PENDING. See add_subports() for more details.
|
||||
trunk.status = constants.PENDING_STATUS
|
||||
trunk.update()
|
||||
payload = callbacks.TrunkPayload(context, trunk_id,
|
||||
current_trunk=trunk,
|
||||
original_trunk=original_trunk,
|
||||
|
|
|
@ -218,3 +218,43 @@ class TrunkPluginTestCase(test_plugin.Ml2PluginV2TestCase):
|
|||
|
||||
def test_remove_subports_notify_precommit_delete(self):
|
||||
self._test_remove_subports_notify(events.PRECOMMIT_DELETE)
|
||||
|
||||
def test_create_trunk_in_pending_state(self):
|
||||
with self.port() as port:
|
||||
trunk = self._create_test_trunk(port)
|
||||
self.assertEqual(
|
||||
constants.PENDING_STATUS, trunk['status'])
|
||||
|
||||
def test_add_subports_trunk_in_error_state_raises(self):
|
||||
with self.port() as port, self.port() as subport:
|
||||
trunk = self._create_test_trunk(port)
|
||||
trunk_obj = self._get_trunk_obj(trunk['id'])
|
||||
trunk_obj.status = constants.ERROR_STATUS
|
||||
trunk_obj.update()
|
||||
s = create_subport_dict(subport['port']['id'])
|
||||
self.assertRaises(trunk_exc.TrunkInErrorState,
|
||||
self.trunk_plugin.add_subports,
|
||||
self.context, trunk['id'], {'sub_ports': [s]})
|
||||
|
||||
def test_add_subports_trunk_goes_to_pending(self):
|
||||
with self.port() as port, self.port() as subport:
|
||||
trunk = self._create_test_trunk(port)
|
||||
trunk_obj = self._get_trunk_obj(trunk['id'])
|
||||
trunk_obj.status = constants.ACTIVE_STATUS
|
||||
trunk_obj.update()
|
||||
s = create_subport_dict(subport['port']['id'])
|
||||
trunk = self.trunk_plugin.add_subports(
|
||||
self.context, trunk['id'], {'sub_ports': [s]})
|
||||
self.assertEqual(constants.PENDING_STATUS, trunk['status'])
|
||||
|
||||
def test_remove_subports_trunk_goes_to_pending(self):
|
||||
with self.port() as port, self.port() as subport:
|
||||
s = create_subport_dict(subport['port']['id'])
|
||||
trunk = self._create_test_trunk(port, [s])
|
||||
trunk_obj = self._get_trunk_obj(trunk['id'])
|
||||
trunk_obj.status = constants.ACTIVE_STATUS
|
||||
trunk_obj.update()
|
||||
trunk = self.trunk_plugin.remove_subports(
|
||||
self.context, trunk['id'],
|
||||
{'sub_ports': [{'port_id': subport['port']['id']}]})
|
||||
self.assertEqual(constants.PENDING_STATUS, trunk['status'])
|
||||
|
|
Loading…
Reference in New Issue