diff --git a/neutron/common/ovn/utils.py b/neutron/common/ovn/utils.py index f93bec17df2..e88a3d89149 100644 --- a/neutron/common/ovn/utils.py +++ b/neutron/common/ovn/utils.py @@ -463,7 +463,9 @@ def validate_and_get_data_from_binding_profile(port): if pbp_param_set.vnic_type: if pbp_param_set.vnic_type != vnic_type: continue - if capabilities and pbp_param_set.capability not in capabilities: + if (capabilities and + pbp_param_set.capability is not None and + pbp_param_set.capability not in capabilities): continue param_set = pbp_param_set.param_set param_keys = param_set.keys() 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 dd3fe8087cf..d8ffb7759d9 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py @@ -901,7 +901,7 @@ def tunnel_update(self, context, **kwargs): @profiler.trace("rpc") def tunnel_delete(self, context, **kwargs): - LOG.debug("tunnel_delete received") + LOG.debug("tunnel_delete received: %s", kwargs) if not self.enable_tunneling: return tunnel_ip = kwargs.get('tunnel_ip') diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 3fe63a3d50a..6ae5d229bf0 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -492,6 +492,34 @@ def _reset_mac_for_direct_physical(self, orig_port, port, binding): return True return False + @registry.receives(resources.AGENT, [events.AFTER_DELETE]) + def delete_agent_notified(self, resource, event, trigger, + payload=None): + context = payload.context + agent = payload.states[0] + if agent.binary != const.AGENT_PROCESS_OVS: + return + tunnel_id = payload.resource_id + tunnel_ip = agent.configurations.get('tunneling_ip') + tunnel_types = agent.configurations.get('tunnel_types') + if not tunnel_ip or not tunnel_types: + return + LOG.debug('Deleting tunnel id %s, and endpoints associated with ' + 'it (tunnel_ip: %s tunnel_types: %s)', + tunnel_id, tunnel_ip, tunnel_types) + for t_type in tunnel_types: + self.notifier.tunnel_delete( + context=context, + tunnel_ip=tunnel_ip, + tunnel_type=t_type) + try: + driver = self.type_manager.drivers.get(t_type) + except KeyError: + LOG.warning('Tunnel type %s is not registered, cannot ' + 'delete tunnel endpoint for it.', t_type) + else: + driver.obj.delete_endpoint(tunnel_ip) + @registry.receives(resources.AGENT, [events.AFTER_UPDATE]) def _retry_binding_revived_agents(self, resource, event, trigger, payload=None): diff --git a/neutron/tests/unit/common/ovn/test_utils.py b/neutron/tests/unit/common/ovn/test_utils.py index 6015d2c8575..bd559c1d6e2 100644 --- a/neutron/tests/unit/common/ovn/test_utils.py +++ b/neutron/tests/unit/common/ovn/test_utils.py @@ -606,6 +606,7 @@ def setUp(self): 'neutron_lib.plugins.directory.get_plugin').start() self.VNIC_FAKE_NORMAL = 'fake-vnic-normal' self.VNIC_FAKE_OTHER = 'fake-vnic-other' + self.VNIC_FAKE_THIRD = 'fake-vnic-third' # Replace constants.OVN_PORT_BINDING_PROFILE_PARAMS to allow synthesis _params = constants.OVN_PORT_BINDING_PROFILE_PARAMS.copy() @@ -613,9 +614,6 @@ def setUp(self): constants.OVNPortBindingProfileParamSet( {'key': [str, type(None)]}, self.VNIC_FAKE_NORMAL, None), - constants.OVNPortBindingProfileParamSet( - {'key': [str], 'other_key': [str]}, - self.VNIC_FAKE_OTHER, None), constants.OVNPortBindingProfileParamSet( { 'key': [str], @@ -623,6 +621,9 @@ def setUp(self): 'third_key': [str] }, self.VNIC_FAKE_OTHER, constants.PORT_CAP_SWITCHDEV), + constants.OVNPortBindingProfileParamSet( + {'key': [str], 'other_key': [str]}, + self.VNIC_FAKE_THIRD, None), ]) self.OVN_PORT_BINDING_PROFILE_PARAMS = mock.patch.object( constants, @@ -737,6 +738,27 @@ def test_valid_input(self): {portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED, constants.OVN_PORT_BINDING_PROFILE: expect})) + def test_valid_input_surplus_capabilities(self): + capabilities = ['rx', 'tx', 'sg', 'tso', 'gso', 'gro', 'rxvlan', + 'txvlan', 'rxhash', 'rdma', 'txudptnl'] + binding_profile = { + 'pci_vendor_info': 'dead:beef', + 'pci_slot': '0000:ca:fe.42', + 'physical_network': 'physnet1', + 'card_serial_number': 'AB2000X00042', + 'pf_mac_address': '00:53:00:00:00:42', + 'vf_num': 42, + constants.PORT_CAP_PARAM: capabilities + } + expect = binding_profile.copy() + del(expect[constants.PORT_CAP_PARAM]) + self.assertEqual( + utils.BPInfo(expect, portbindings.VNIC_REMOTE_MANAGED, + capabilities), + utils.validate_and_get_data_from_binding_profile( + {portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED, + constants.OVN_PORT_BINDING_PROFILE: binding_profile})) + def test_valid_input_surplus_keys(self): # Confirm that extra keys are allowed binding_profile = { @@ -810,12 +832,12 @@ def test_overlapping_param_set_different_vnic_type(self): utils.validate_and_get_data_from_binding_profile( {portbindings.VNIC_TYPE: self.VNIC_FAKE_NORMAL, constants.OVN_PORT_BINDING_PROFILE: binding_profile})) - # It is valid for VNIC_FAKE_OTHER + # It is valid for VNIC_FAKE_THIRD expected_bp = binding_profile.copy() self.assertEqual( - utils.BPInfo(expected_bp, self.VNIC_FAKE_OTHER, []), + utils.BPInfo(expected_bp, self.VNIC_FAKE_THIRD, []), utils.validate_and_get_data_from_binding_profile( - {portbindings.VNIC_TYPE: self.VNIC_FAKE_OTHER, + {portbindings.VNIC_TYPE: self.VNIC_FAKE_THIRD, constants.OVN_PORT_BINDING_PROFILE: binding_profile})) def test_overlapping_param_set_different_vnic_type_and_capability(self): @@ -825,13 +847,13 @@ def test_overlapping_param_set_different_vnic_type_and_capability(self): 'other_key': 42, 'third_key': 'value', } - # This param set is not valid for VNIC_FAKE_OTHER without capability + # This param set is not valid for VNIC_FAKE_THIRD without capability expect = binding_profile.copy() del(expect['third_key']) self.assertRaises( neutron_lib.exceptions.InvalidInput, utils.validate_and_get_data_from_binding_profile, - {portbindings.VNIC_TYPE: self.VNIC_FAKE_OTHER, + {portbindings.VNIC_TYPE: self.VNIC_FAKE_THIRD, constants.OVN_PORT_BINDING_PROFILE: binding_profile}) # This param set is also not valid as the capabilities do not match binding_profile = { diff --git a/neutron/tests/unit/plugins/ml2/test_plugin.py b/neutron/tests/unit/plugins/ml2/test_plugin.py index db81fdf1e3a..3155584f583 100644 --- a/neutron/tests/unit/plugins/ml2/test_plugin.py +++ b/neutron/tests/unit/plugins/ml2/test_plugin.py @@ -43,6 +43,7 @@ from oslo_config import cfg from oslo_db import exception as db_exc from oslo_utils import netutils +from oslo_utils import timeutils from oslo_utils import uuidutils import testtools import webob @@ -65,6 +66,7 @@ from neutron.plugins.ml2.common import exceptions as ml2_exc from neutron.plugins.ml2 import db as ml2_db from neutron.plugins.ml2 import driver_context +from neutron.plugins.ml2.drivers import type_tunnel from neutron.plugins.ml2.drivers import type_vlan from neutron.plugins.ml2 import managers from neutron.plugins.ml2 import models @@ -596,6 +598,64 @@ def test_update_network_with_incorrect_resource_body(self): self.assertIn("network", res.json['NeutronError']['message']) +class TestMl2AgentNotifications(Ml2PluginV2TestCase): + + class Agent: + def __init__(self, agent_dict): + for field in agent_dict: + setattr(self, field, agent_dict[field]) + + def test_delete_agent_notified(self): + agent_status = {'agent_type': constants.AGENT_TYPE_OVS, + 'binary': constants.AGENT_PROCESS_OVS, + 'host': 'AHOST', + 'topic': 'N/A', + 'configurations': {'tunnel_types': ['vxlan'], + 'tunneling_ip': '100.101.2.3'}} + agent = self.plugin.create_or_update_agent(self.context, + dict(agent_status), + timeutils.utcnow()) + agnt = self.Agent(agent[1]) + with mock.patch.object( + self.plugin.notifier, 'tunnel_delete') as m_t_del: + with mock.patch.object( + type_tunnel.EndpointTunnelTypeDriver, + 'delete_endpoint') as m_del_ep: + self.plugin.delete_agent_notified( + resource='agent', event='after_delete', trigger=None, + payload=events.DBEventPayload( + self.context, states=(agnt,), + resource_id=agent[1]['id'])) + m_t_del.assert_called_once_with( + context=mock.ANY, + tunnel_ip='100.101.2.3', tunnel_type='vxlan') + m_del_ep.assert_called_once_with('100.101.2.3') + + def test_delete_agent_notified_non_ovs(self): + agent_status = {'agent_type': constants.AGENT_TYPE_NIC_SWITCH, + 'binary': constants.AGENT_PROCESS_NIC_SWITCH, + 'host': 'AHOST', + 'topic': 'N/A', + 'configurations': {'tunnel_types': ['vxlan'], + 'tunneling_ip': '100.101.2.3'}} + agent = self.plugin.create_or_update_agent(self.context, + dict(agent_status), + timeutils.utcnow()) + agnt = self.Agent(agent[1]) + with mock.patch.object( + self.plugin.notifier, 'tunnel_delete') as m_t_del: + with mock.patch.object( + type_tunnel.EndpointTunnelTypeDriver, + 'delete_endpoint') as m_del_ep: + self.plugin.delete_agent_notified( + resource='agent', event='after_delete', trigger=None, + payload=events.DBEventPayload( + self.context, states=(agnt,), + resource_id=agent[1]['id'])) + m_t_del.assert_not_called() + m_del_ep.assert_not_called() + + class TestMl2NetworksV2AgentMechDrivers(Ml2PluginV2TestCase): _mechanism_drivers = ['logger', 'test', 'test_with_agent']