From 0694bebd6de3934528492aadb78e3048dd5a07ea Mon Sep 17 00:00:00 2001 From: Miguel Lavalle Date: Wed, 9 May 2018 10:39:05 -0500 Subject: [PATCH] Add binding de-activation to OVS agent To support multiple port bindings, the L2 agents have to have the capability to handle binding deactivation notifications from the Neutron server. This patch adds the necessary code to the OVS agent. After receiving the notification, the agent un-plugs the corresponding VIF from the integration bridge. Change-Id: I78178de2039ccabc649558de4f6549a38de90418 Partial-Bug: #1580880 --- .../openvswitch/agent/ovs_neutron_agent.py | 27 ++++++++++++++++- .../agent/test_ovs_neutron_agent.py | 29 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py index 988bf802266..aa306a0d1ba 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py @@ -124,7 +124,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, # 1.2 Support DVR (Distributed Virtual Router) 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 __init__(self, bridge_classes, ext_manager, conf=None): '''Constructor. @@ -174,6 +175,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, self.updated_ports = set() # Stores port delete notifications self.deleted_ports = set() + # Stores the port IDs whose binding has been deactivated + self.deactivated_bindings = set() self.network_ports = collections.defaultdict(set) # keeps association between ports and ofports to detect ofport change @@ -414,6 +417,12 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, {'network_id': network_id, 'ports': self.network_ports[network_id]}) + def binding_deactivate(self, context, **kwargs): + if kwargs.get('host') != self.conf.host: + return + port_id = kwargs.get('port_id') + self.deactivated_bindings.add(port_id) + def _clean_network_ports(self, port_id): for port_set in self.network_ports.values(): if port_id in port_set: @@ -444,6 +453,20 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, # more secure self.sg_agent.remove_devices_filter(deleted_ports) + def process_deactivated_bindings(self, port_info): + # don't try to deactivate bindings for removed ports since they are + # already gone + if 'removed' in port_info: + self.deactivated_bindings -= port_info['removed'] + while self.deactivated_bindings: + port_id = self.deactivated_bindings.pop() + port = self.int_br.get_vif_port_by_id(port_id) + if not port: + continue + self.int_br.delete_port(port.port_name) + LOG.debug(("Port id %s unplugged from integration bridge because " + "its binding was de-activated"), port_id) + def tunnel_update(self, context, **kwargs): LOG.debug("tunnel_update received") if not self.enable_tunneling: @@ -1786,6 +1809,7 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, return (polling_manager.is_polling_required or self.updated_ports or self.deleted_ports or + self.deactivated_bindings or self.sg_agent.firewall_refresh_needed()) def _port_info_has_changes(self, port_info): @@ -2075,6 +2099,7 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, failed_devices, failed_ancillary_devices)) sync = False self.process_deleted_ports(port_info) + self.process_deactivated_bindings(port_info) ofport_changed_ports = self.update_stale_ofport_rules() if ofport_changed_ports: port_info.setdefault('updated', set()).update( diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py index f9f915b2cdd..2be8c15cca4 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py @@ -114,6 +114,7 @@ class TestOvsNeutronAgent(object): group='SECURITYGROUP') cfg.CONF.set_default('quitting_rpc_timeout', 10, 'AGENT') cfg.CONF.set_default('local_ip', '127.0.0.1', 'OVS') + cfg.CONF.set_default('host', 'host') mock.patch( 'neutron.agent.ovsdb.native.helpers.enable_connection_uri').start() mock.patch( @@ -1218,6 +1219,34 @@ class TestOvsNeutronAgent(object): self.assertFalse(int_br.set_db_attribute.called) self.assertFalse(int_br.drop_port.called) + def test_binding_deactivate_not_for_host(self): + self.agent.binding_deactivate('unused_context', port_id='id', + host='other_host') + self.assertEqual(set(), self.agent.deactivated_bindings) + + def test_binding_deactivate(self): + vif = FakeVif() + with mock.patch.object(self.agent, 'int_br') as int_br: + int_br.get_vif_port_by_id.return_value = vif + self.agent.binding_deactivate('unused_context', port_id='id', + host='host') + self.assertEqual(set(['id']), self.agent.deactivated_bindings) + self.agent.process_deactivated_bindings(port_info={}) + int_br.get_vif_port_by_id.assert_called_once_with('id') + int_br.delete_port.assert_called_once_with(vif.port_name) + self.assertEqual(set(), self.agent.deactivated_bindings) + + def test_binding_deactivate_removed_port(self): + with mock.patch.object(self.agent, 'int_br') as int_br: + self.agent.binding_deactivate('unused_context', port_id='id', + host='host') + self.assertEqual(set(['id']), self.agent.deactivated_bindings) + self.agent.process_deactivated_bindings( + port_info={'removed': {'id', }}) + int_br.get_vif_port_by_id.assert_not_called() + int_br.delete_port.assert_not_called() + self.assertEqual(set(), self.agent.deactivated_bindings) + def _test_setup_physical_bridges(self, port_exists=False): with mock.patch.object(ip_lib.IPDevice, "exists") as devex_fn,\ mock.patch.object(sys, "exit"),\