From d1472deed5bf7b4cec7363e64fd4746cb278fcfb Mon Sep 17 00:00:00 2001 From: Bob Kukura Date: Mon, 24 Feb 2014 16:33:26 -0500 Subject: [PATCH] ML2 mechanism driver access to binding details The following properties are added to the PortContext object passed to ML2 mechanism drivers for port operations: * bound_driver - name of current bound driver * original_bound_driver - name previously bound driver in an update * original_bound_segment - network segment used in previous binding Some issues with the existing ML2 port binding unit tests were also fixed. The remainder of the fix for bug 1276395, making these binding details available to mechanism drivers when a port is deleted, will be addressed as part of the fix for bug 1276391. Partial-Bug: #1276395 Change-Id: I9ecff4a4e044920ed2dde709c89aeb9bc773220d --- neutron/plugins/ml2/driver_api.py | 27 ++++++++++- neutron/plugins/ml2/driver_context.py | 21 +++++++++ neutron/tests/unit/ml2/_test_mech_agent.py | 12 +++++ .../unit/ml2/drivers/mechanism_logger.py | 10 +++- .../tests/unit/ml2/drivers/mechanism_test.py | 46 +++++++++++++++++-- neutron/tests/unit/ml2/test_port_binding.py | 14 +++--- 6 files changed, 117 insertions(+), 13 deletions(-) diff --git a/neutron/plugins/ml2/driver_api.py b/neutron/plugins/ml2/driver_api.py index f6167e761cd..78a37d98252 100644 --- a/neutron/plugins/ml2/driver_api.py +++ b/neutron/plugins/ml2/driver_api.py @@ -218,7 +218,7 @@ class PortContext(object): @abstractproperty def original(self): - """Return the original state of the port + """Return the original state of the port. Return the original state of the port, prior to a call to update_port. Method is only valid within calls to @@ -236,6 +236,31 @@ class PortContext(object): """Return the currently bound segment dictionary.""" pass + @abstractproperty + def original_bound_segment(self): + """Return the original bound segment dictionary. + + Return the original bound segment dictionary, prior to a call + to update_port. Method is only valid within calls to + update_port_precommit and update_port_postcommit. + """ + pass + + @abstractproperty + def bound_driver(self): + """Return the currently bound mechanism driver name.""" + pass + + @abstractproperty + def original_bound_driver(self): + """Return the original bound mechanism driver name. + + Return the original bound mechanism driver name, prior to a + call to update_port. Method is only valid within calls to + update_port_precommit and update_port_postcommit. + """ + pass + @abstractmethod def host_agents(self, agent_type): """Get agents of the specified type on port's host. diff --git a/neutron/plugins/ml2/driver_context.py b/neutron/plugins/ml2/driver_context.py index 89f4e61ac7f..facee4e3004 100644 --- a/neutron/plugins/ml2/driver_context.py +++ b/neutron/plugins/ml2/driver_context.py @@ -78,6 +78,12 @@ class PortContext(MechanismDriverContext, api.PortContext): network) self._binding = db.ensure_port_binding(plugin_context.session, port['id']) + if original_port: + self._original_bound_segment_id = self._binding.segment + self._original_bound_driver = self._binding.driver + else: + self._original_bound_segment_id = None + self._original_bound_driver = None @property def current(self): @@ -99,6 +105,21 @@ class PortContext(MechanismDriverContext, api.PortContext): if segment[api.ID] == id: return segment + @property + def original_bound_segment(self): + if self._original_bound_segment_id: + for segment in self._network_context.network_segments: + if segment[api.ID] == self._original_bound_segment_id: + return segment + + @property + def bound_driver(self): + return self._binding.driver + + @property + def original_bound_driver(self): + return self._original_bound_driver + def host_agents(self, agent_type): return self._plugin.get_agents(self._plugin_context, filters={'agent_type': [agent_type], diff --git a/neutron/tests/unit/ml2/_test_mech_agent.py b/neutron/tests/unit/ml2/_test_mech_agent.py index 0714b58907d..73b926f0511 100644 --- a/neutron/tests/unit/ml2/_test_mech_agent.py +++ b/neutron/tests/unit/ml2/_test_mech_agent.py @@ -67,6 +67,18 @@ class FakePortContext(api.PortContext): if segment[api.ID] == self._bound_segment_id: return segment + @property + def original_bound_segment(self): + return None + + @property + def bound_driver(self): + return None + + @property + def original_bound_driver(self): + return None + def host_agents(self, agent_type): if agent_type == self._agent_type: return self._agents diff --git a/neutron/tests/unit/ml2/drivers/mechanism_logger.py b/neutron/tests/unit/ml2/drivers/mechanism_logger.py index ca92dbc648a..c23ef81aaaf 100644 --- a/neutron/tests/unit/ml2/drivers/mechanism_logger.py +++ b/neutron/tests/unit/ml2/drivers/mechanism_logger.py @@ -83,11 +83,19 @@ class LoggerMechanismDriver(api.MechanismDriver): def _log_port_call(self, method_name, context): network_context = context.network LOG.info(_("%(method)s called with port settings %(current)s " - "(original settings %(original)s) on network %(network)s"), + "(original settings %(original)s) " + "bound to segment %(segment)s " + "(original segment %(original_segment)s) " + "using driver %(driver)s " + "(original driver %(original_driver)s) " + "on network %(network)s"), {'method': method_name, 'current': context.current, 'original': context.original, 'segment': context.bound_segment, + 'original_segment': context.original_bound_segment, + 'driver': context.bound_driver, + 'original_driver': context.original_bound_driver, 'network': network_context.current}) def create_port_precommit(self, context): diff --git a/neutron/tests/unit/ml2/drivers/mechanism_test.py b/neutron/tests/unit/ml2/drivers/mechanism_test.py index 0b3d98af71e..a0c05c962ea 100644 --- a/neutron/tests/unit/ml2/drivers/mechanism_test.py +++ b/neutron/tests/unit/ml2/drivers/mechanism_test.py @@ -84,11 +84,34 @@ class TestMechanismDriver(api.MechanismDriver): assert(isinstance(context, api.PortContext)) assert(isinstance(context.current, dict)) assert(context.current['id'] is not None) + + vif_type = context.current.get(portbindings.VIF_TYPE) + assert(vif_type is not None) + if vif_type in (portbindings.VIF_TYPE_UNBOUND, + portbindings.VIF_TYPE_BINDING_FAILED): + assert(context.bound_segment is None) + assert(context.bound_driver is None) + else: + assert(isinstance(context.bound_segment, dict)) + assert(context.bound_driver == 'test') + if original_expected: assert(isinstance(context.original, dict)) assert(context.current['id'] == context.original['id']) + vif_type = context.original.get(portbindings.VIF_TYPE) + assert(vif_type is not None) + if vif_type in (portbindings.VIF_TYPE_UNBOUND, + portbindings.VIF_TYPE_BINDING_FAILED): + assert(context.original_bound_segment is None) + assert(context.original_bound_driver is None) + else: + assert(isinstance(context.original_bound_segment, dict)) + assert(context.original_bound_driver == 'test') else: - assert(not context.original) + assert(context.original is None) + assert(context.original_bound_segment is None) + assert(context.original_bound_driver is None) + network_context = context.network assert(isinstance(network_context, api.NetworkContext)) self._check_network_context(network_context, False) @@ -112,7 +135,12 @@ class TestMechanismDriver(api.MechanismDriver): self._check_port_context(context, False) def bind_port(self, context): - self._check_port_context(context, False) + # REVISIT(rkukura): Currently, bind_port() is called as part + # of either a create or update transaction. The fix for bug + # 1276391 will change it to be called outside any transaction, + # so the context.original* will no longer be available. + self._check_port_context(context, context.original is not None) + host = context.current.get(portbindings.HOST_ID, None) segment = context.network.network_segments[0][api.ID] if host == "host-ovs-no_filter": @@ -123,8 +151,18 @@ class TestMechanismDriver(api.MechanismDriver): {portbindings.CAP_PORT_FILTER: True}) def validate_port_binding(self, context): - self._check_port_context(context, False) + # REVISIT(rkukura): Currently, validate_port_binding() is + # called as part of either a create or update transaction. The + # fix for bug 1276391 will change it to be called outside any + # transaction (or eliminate it altogether), so the + # context.original* will no longer be available. + self._check_port_context(context, context.original is not None) return True def unbind_port(self, context): - self._check_port_context(context, False) + # REVISIT(rkukura): Currently, unbind_port() is called as part + # of either an update or delete transaction. The fix for bug + # 1276391 will change it to be called outside any transaction + # (or eliminate it altogether), so the context.original* will + # no longer be available. + self._check_port_context(context, context.original is not None) diff --git a/neutron/tests/unit/ml2/test_port_binding.py b/neutron/tests/unit/ml2/test_port_binding.py index 86e066892df..6117d0667a2 100644 --- a/neutron/tests/unit/ml2/test_port_binding.py +++ b/neutron/tests/unit/ml2/test_port_binding.py @@ -108,18 +108,18 @@ class PortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase): self.assertFalse(notify_mock.called) def test_update_with_new_host_binding_notifies_agent(self): - self._test_update_port_binding('host-ovs-no-filter', - 'host-bridge-no-filter') + self._test_update_port_binding('host-ovs-no_filter', + 'host-bridge-filter') def test_update_with_same_host_binding_does_not_notify(self): - self._test_update_port_binding('host-ovs-no-filter', - 'host-ovs-no-filter') + self._test_update_port_binding('host-ovs-no_filter', + 'host-ovs-no_filter') def test_update_without_binding_does_not_notify(self): - self._test_update_port_binding('host-ovs-no-filter') + self._test_update_port_binding('host-ovs-no_filter') def testt_update_from_empty_to_host_binding_notifies_agent(self): - self._test_update_port_binding('', 'host-ovs-no-filter') + self._test_update_port_binding('', 'host-ovs-no_filter') def test_update_from_host_to_empty_binding_notifies_agent(self): - self._test_update_port_binding('host-ovs-no-filter', '') + self._test_update_port_binding('host-ovs-no_filter', '')