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"),\