From dc4c8ab7c1ab26ae6b18f1ef26d94eaef14af900 Mon Sep 17 00:00:00 2001 From: Ryan Tidwell Date: Fri, 26 Aug 2016 11:09:59 -0700 Subject: [PATCH] Set trunk status to DOWN when parent port is unbound This patch ensures that trunk status is set to DOWN when its parent port is unbound. This typically occurs when the instance using the parent port is deleted in Nova. Change-Id: I2673d2dd2d1dfd8eaabab66c009311cf4c143734 Partially-implements: blueprint vlan-aware-vms --- neutron/services/trunk/plugin.py | 25 ++++++++++ .../tests/unit/services/trunk/test_plugin.py | 47 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/neutron/services/trunk/plugin.py b/neutron/services/trunk/plugin.py index 8a01f545479..904fe9b8bea 100644 --- a/neutron/services/trunk/plugin.py +++ b/neutron/services/trunk/plugin.py @@ -25,6 +25,7 @@ from neutron.db import api as db_api from neutron.db import common_db_mixin from neutron.db import db_base_plugin_common from neutron.db import db_base_plugin_v2 +from neutron.extensions import portbindings from neutron.objects import base as objects_base from neutron.objects import trunk as trunk_objects from neutron.services import service_base @@ -70,6 +71,11 @@ class TrunkPlugin(service_base.ServicePluginBase, drivers.register() registry.subscribe(rules.enforce_port_deletion_rules, resources.PORT, events.BEFORE_DELETE) + # NOTE(tidwellr) Consider keying off of PRECOMMIT_UPDATE if we find + # AFTER_UPDATE to be problematic for setting trunk status when a + # a parent port becomes unbound. + registry.subscribe(self._trigger_trunk_status_change, + resources.PORT, events.AFTER_UPDATE) registry.notify(constants.TRUNK_PLUGIN, events.AFTER_INIT, self) for driver in self._drivers: LOG.debug('Trunk plugin loaded with driver %s', driver.name) @@ -345,3 +351,22 @@ class TrunkPlugin(service_base.ServicePluginBase, raise trunk_exc.TrunkNotFound(trunk_id=trunk_id) return obj + + def _trigger_trunk_status_change(self, resource, event, trigger, **kwargs): + updated_port = kwargs['port'] + trunk_details = updated_port.get('trunk_details') + # If no trunk_details, the port is not the parent of a trunk. + if not trunk_details: + return + + context = kwargs['context'] + original_port = kwargs['original_port'] + orig_vif_type = original_port.get(portbindings.VIF_TYPE) + new_vif_type = updated_port.get(portbindings.VIF_TYPE) + vif_type_changed = orig_vif_type != new_vif_type + if vif_type_changed and new_vif_type == portbindings.VIF_TYPE_UNBOUND: + trunk = self._get_trunk(context, trunk_details['trunk_id']) + # NOTE(status_police) Trunk status goes to DOWN when the parent + # port is unbound. This means there are no more physical resources + # associated with the logical resource. + trunk.update(status=constants.DOWN_STATUS) diff --git a/neutron/tests/unit/services/trunk/test_plugin.py b/neutron/tests/unit/services/trunk/test_plugin.py index 31cda4103ef..91af07535f1 100644 --- a/neutron/tests/unit/services/trunk/test_plugin.py +++ b/neutron/tests/unit/services/trunk/test_plugin.py @@ -19,6 +19,8 @@ import testtools from neutron.callbacks import events from neutron.callbacks import registry +from neutron.callbacks import resources +from neutron.extensions import portbindings from neutron import manager from neutron.objects import trunk as trunk_objects from neutron.services.trunk import callbacks @@ -266,6 +268,51 @@ class TrunkPluginTestCase(test_plugin.Ml2PluginV2TestCase): {'sub_ports': [{'port_id': subport['port']['id']}]}) self.assertEqual(constants.DOWN_STATUS, trunk['status']) + def test__trigger_trunk_status_change_vif_type_changed_unbound(self): + with self.port() as parent: + parent[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_UNBOUND + original_port = {portbindings.VIF_TYPE: 'fakeviftype'} + self._test__trigger_trunk_status_change(parent, + original_port, + constants.ACTIVE_STATUS, + constants.DOWN_STATUS) + + def test__trigger_trunk_status_change_vif_type_unchanged(self): + with self.port() as parent: + parent[portbindings.VIF_TYPE] = 'fakeviftype' + original_port = {portbindings.VIF_TYPE: 'fakeviftype'} + self._test__trigger_trunk_status_change(parent, + original_port, + constants.ACTIVE_STATUS, + constants.ACTIVE_STATUS) + + def test__trigger_trunk_status_change_vif_type_changed(self): + with self.port() as parent: + parent[portbindings.VIF_TYPE] = 'realviftype' + original_port = {portbindings.VIF_TYPE: 'fakeviftype'} + self._test__trigger_trunk_status_change(parent, + original_port, + constants.ACTIVE_STATUS, + constants.ACTIVE_STATUS) + + def _test__trigger_trunk_status_change(self, new_parent, + original_parent, + initial_trunk_status, + final_trunk_status): + trunk = self._create_test_trunk(new_parent) + trunk = self._get_trunk_obj(trunk['id']) + trunk.update(status=initial_trunk_status) + trunk_details = {'trunk_id': trunk.id} + new_parent['trunk_details'] = trunk_details + original_parent['trunk_details'] = trunk_details + kwargs = {'context': self.context, 'port': new_parent, + 'original_port': original_parent} + self.trunk_plugin._trigger_trunk_status_change(resources.PORT, + events.AFTER_UPDATE, + None, **kwargs) + trunk = self._get_trunk_obj(trunk.id) + self.assertEqual(final_trunk_status, trunk.status) + class TrunkPluginDriversTestCase(test_plugin.Ml2PluginV2TestCase):