From f374697760c65d3db53c50e77080a7a76819c24c Mon Sep 17 00:00:00 2001 From: Miguel Lavalle Date: Mon, 23 Apr 2018 21:53:43 -0500 Subject: [PATCH] Add binding_deactivate method to Linux bridge agent This commit adds a binding_deactivate method to the Linux bridge agent to receive messages from the ML2 plugin when a binding is de-activated for a port. After receiving that message, the agent un-plugs the corresponding tap interface from the port's network bridge. To support this, a binding_deactivate method is also added to the agents notifier. Finally, the activate method in the ML2 plugin is updated to use the binding_deactivate method in the agents notifier. Change-Id: I3f4e34766791c472a2c81842190094f697baa05c Partial-Bug: #1580880 --- neutron/agent/_topics.py | 24 +++++++++++++++ .../agent/linuxbridge_neutron_agent.py | 19 ++++++++++-- neutron/plugins/ml2/plugin.py | 4 ++- neutron/plugins/ml2/rpc.py | 10 +++++++ .../agent/test_linuxbridge_neutron_agent.py | 29 +++++++++++++++++++ 5 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 neutron/agent/_topics.py diff --git a/neutron/agent/_topics.py b/neutron/agent/_topics.py new file mode 100644 index 00000000000..6d5f8c48811 --- /dev/null +++ b/neutron/agent/_topics.py @@ -0,0 +1,24 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +TODO(mlavalle): This module should be deleted once neutron-lib containing +https://review.openstack.org/#/c/564381/ change is released. +""" + +from neutron_lib.api.definitions import portbindings_extended + + +PORT_BINDING = portbindings_extended.RESOURCE_NAME + +ACTIVATE = 'activate' +DEACTIVATE = 'deactivate' diff --git a/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py b/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py index be0ffb34eaf..cbd7986a368 100644 --- a/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py @@ -33,6 +33,7 @@ from oslo_service import service from oslo_utils import excutils from six import moves +from neutron.agent import _topics as n_topics from neutron.agent.linux import bridge_lib from neutron.agent.linux import ip_lib from neutron.api.rpc.handlers import securitygroups_rpc as sg_rpc @@ -833,7 +834,8 @@ class LinuxBridgeManager(amb.CommonAgentManagerBase): consumers = [[topics.PORT, topics.UPDATE], [topics.NETWORK, topics.DELETE], [topics.NETWORK, topics.UPDATE], - [topics.SECURITY_GROUP, topics.UPDATE]] + [topics.SECURITY_GROUP, topics.UPDATE], + [n_topics.PORT_BINDING, n_topics.DEACTIVATE]] if cfg.CONF.VXLAN.l2_population: consumers.append([topics.L2POPULATION, topics.UPDATE]) return consumers @@ -869,7 +871,8 @@ class LinuxBridgeRpcCallbacks( # 1.1 Support Security Group RPC # 1.3 Added param devices_to_update to security_groups_provider_updated # 1.4 Added support for network_update - target = oslo_messaging.Target(version='1.4') + # 1.5 Added binding_deactivate + target = oslo_messaging.Target(version='1.5') def network_delete(self, context, **kwargs): LOG.debug("network_delete received") @@ -899,6 +902,18 @@ class LinuxBridgeRpcCallbacks( self.updated_devices.add(device_name) LOG.debug("port_update RPC received for port: %s", port_id) + def binding_deactivate(self, context, **kwargs): + if kwargs.get('host') != cfg.CONF.host: + return + interface_name = self.agent.mgr.get_tap_device_name( + kwargs.get('port_id')) + bridge_name = self.agent.mgr.get_bridge_name(kwargs.get('network_id')) + LOG.debug("Removing device %(interface_name)s from bridge " + "%(bridge_name)s due to binding being de-activated", + {'interface_name': interface_name, + 'bridge_name': bridge_name}) + self.agent.mgr.remove_interface(bridge_name, interface_name) + def network_update(self, context, **kwargs): network_id = kwargs['network']['id'] LOG.debug("network_update message processed for network " diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 61899152bac..dd33eae0347 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -2199,7 +2199,9 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, original_context, bind_context, need_notify=True, try_again=True) if not try_again: - self.notifier.port_delete(context, port_id) + self.notifier.binding_deactivate(context, port_id, + active_binding.host, + network['id']) return self._make_port_binding_dict(cur_context._binding) raise n_exc.PortBindingError(port_id=port_id, host=host) diff --git a/neutron/plugins/ml2/rpc.py b/neutron/plugins/ml2/rpc.py index 0b131d49396..01bf781b89b 100644 --- a/neutron/plugins/ml2/rpc.py +++ b/neutron/plugins/ml2/rpc.py @@ -25,6 +25,7 @@ from oslo_log import log import oslo_messaging from sqlalchemy.orm import exc +from neutron.agent import _topics as n_topics from neutron.api.rpc.handlers import dvr_rpc from neutron.api.rpc.handlers import securitygroups_rpc as sg_rpc from neutron.common import rpc as n_rpc @@ -383,6 +384,7 @@ class AgentNotifierApi(dvr_rpc.DVRAgentRpcApiMixin, 1.1 - Added get_active_networks_info, create_dhcp_port, update_dhcp_port, and removed get_dhcp_port methods. 1.4 - Added network_update + 1.5 - Added binding_deactivate """ def __init__(self, topic): @@ -399,6 +401,8 @@ class AgentNotifierApi(dvr_rpc.DVRAgentRpcApiMixin, self.topic_network_update = topics.get_topic_name(topic, topics.NETWORK, topics.UPDATE) + self.topic_port_binding_deactivate = topics.get_topic_name( + topic, n_topics.PORT_BINDING, n_topics.DEACTIVATE) target = oslo_messaging.Target(topic=topic, version='1.0') self.client = n_rpc.get_client(target) @@ -425,3 +429,9 @@ class AgentNotifierApi(dvr_rpc.DVRAgentRpcApiMixin, cctxt = self.client.prepare(topic=self.topic_network_update, fanout=True, version='1.4') cctxt.cast(context, 'network_update', network=network) + + def binding_deactivate(self, context, port_id, host, network_id): + cctxt = self.client.prepare(topic=self.topic_port_binding_deactivate, + fanout=True, version='1.5') + cctxt.cast(context, 'binding_deactivate', port_id=port_id, host=host, + network_id=network_id) diff --git a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py index b1cbb0be677..a72fe0332d4 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py @@ -975,6 +975,7 @@ class TestLinuxBridgeRpcCallbacks(base.BaseTestCase): segment.network_type = 'vxlan' segment.segmentation_id = 1 self.lb_rpc.network_map['net_id'] = segment + cfg.CONF.set_default('host', 'host') def test_network_delete_mapped_net(self): mock_net = mock.Mock() @@ -1022,6 +1023,34 @@ class TestLinuxBridgeRpcCallbacks(base.BaseTestCase): self.assertEqual(0, del_fn.call_count) self.assertEqual(1, log.call_count) + def test_binding_deactivate(self): + with mock.patch.object(self.lb_rpc.agent.mgr, + "get_bridge_name") as get_br_fn,\ + mock.patch.object(self.lb_rpc.agent.mgr, + "get_tap_device_name") as get_tap_fn,\ + mock.patch.object(self.lb_rpc.agent.mgr, + "remove_interface") as rem_intf: + get_br_fn.return_value = "br0" + get_tap_fn.return_value = "tap456" + self.lb_rpc.binding_deactivate(mock.ANY, host="host", + network_id="123", port_id="456") + get_br_fn.assert_called_once_with("123") + get_tap_fn.assert_called_once_with("456") + rem_intf.assert_called_once_with("br0", "tap456") + + def test_binding_deactivate_not_for_host(self): + with mock.patch.object(self.lb_rpc.agent.mgr, + "get_bridge_name") as get_br_fn,\ + mock.patch.object(self.lb_rpc.agent.mgr, + "get_tap_device_name") as get_tap_fn,\ + mock.patch.object(self.lb_rpc.agent.mgr, + "remove_interface") as rem_intf: + self.lb_rpc.binding_deactivate(mock.ANY, host="other_host", + network_id="123", port_id="456") + get_br_fn.assert_not_called() + get_tap_fn.assert_not_called() + rem_intf.assert_not_called() + def _test_fdb_add(self, proxy_enabled=False): fdb_entries = {'net_id': {'ports':