diff --git a/neutron/agent/rpc.py b/neutron/agent/rpc.py index 6a3effd1d97..feeac8f5d49 100644 --- a/neutron/agent/rpc.py +++ b/neutron/agent/rpc.py @@ -112,6 +112,7 @@ class PluginApi(object): 1.4 - tunnel_sync rpc signature upgrade to obtain 'host' 1.5 - Support update_device_list and get_devices_details_list_and_failed_devices + 1.6 - Support get_network_details ''' def __init__(self, topic): @@ -142,6 +143,11 @@ class PluginApi(object): 'get_devices_details_list_and_failed_devices', devices=devices, agent_id=agent_id, host=host) + def get_network_details(self, context, network, agent_id, host=None): + cctxt = self.client.prepare(version='1.6') + return cctxt.call(context, 'get_network_details', network=network, + agent_id=agent_id, host=host) + def update_device_down(self, context, device, agent_id, host=None): cctxt = self.client.prepare() return cctxt.call(context, 'update_device_down', device=device, 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 affca3d0ef8..fa8a4c0dbc2 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py @@ -25,6 +25,7 @@ import netaddr from neutron_lib.agent import constants as agent_consts from neutron_lib.agent import topics from neutron_lib.api.definitions import portbindings +from neutron_lib.api.definitions import provider_net from neutron_lib.callbacks import events as callback_events from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources as callback_resources @@ -400,6 +401,37 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, n_const.TYPE_GRE: {}, n_const.TYPE_VXLAN: {}} + def _update_network_segmentation_id(self, network): + if network[provider_net.NETWORK_TYPE] != n_const.TYPE_VLAN: + return + + try: + lvm = self.vlan_manager.get(network['id']) + except vlanmanager.MappingNotFound: + return + + segmentation_id_old = lvm.segmentation_id + if segmentation_id_old == network[provider_net.SEGMENTATION_ID]: + return + self.vlan_manager.update_segmentation_id( + network['id'], network[provider_net.SEGMENTATION_ID]) + + lvid = lvm.vlan + physical_network = network[provider_net.PHYSICAL_NETWORK] + phys_br = self.phys_brs[physical_network] + phys_port = self.phys_ofports[physical_network] + int_port = self.int_ofports[physical_network] + phys_br.reclaim_local_vlan(port=phys_port, lvid=lvid) + phys_br.provision_local_vlan( + port=phys_port, lvid=lvid, + segmentation_id=network[provider_net.SEGMENTATION_ID], + distributed=self.enable_distributed_routing) + self.int_br.reclaim_local_vlan(port=int_port, + segmentation_id=segmentation_id_old) + self.int_br.provision_local_vlan( + port=int_port, lvid=lvid, + segmentation_id=network[provider_net.SEGMENTATION_ID]) + def setup_rpc(self): self.plugin_rpc = OVSPluginApi(topics.PLUGIN) # allow us to receive port_update/delete callbacks from the cache @@ -449,6 +481,9 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, def network_update(self, context, **kwargs): network_id = kwargs['network']['id'] + network = self.plugin_rpc.get_network_details( + self.context, network_id, self.agent_id, self.conf.host) + self._update_network_segmentation_id(network) for port_id in self.network_ports[network_id]: # notifications could arrive out of order, if the port is deleted # we don't want to update it anymore diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/vlanmanager.py b/neutron/plugins/ml2/drivers/openvswitch/agent/vlanmanager.py index 5e98dd049ec..15317670755 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/vlanmanager.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/vlanmanager.py @@ -108,3 +108,9 @@ class LocalVlanManager(object): return self.mapping.pop(net_id) except KeyError: raise MappingNotFound(net_id=net_id) + + def update_segmentation_id(self, net_id, segmentation_id): + try: + self.mapping[net_id].segmentation_id = segmentation_id + except KeyError: + raise MappingNotFound(net_id=net_id) diff --git a/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py b/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py index 214259d5198..fec8ae1e246 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py +++ b/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py @@ -17,6 +17,7 @@ import os import uuid from neutron_lib.api.definitions import portbindings +from neutron_lib.api.definitions import provider_net from neutron_lib.callbacks import events from neutron_lib.callbacks import registry from neutron_lib import constants @@ -200,3 +201,7 @@ class OpenvswitchMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): a_const.VHOST_USER_SOCKET_DIR) sock_name = (constants.VHOST_USER_DEVICE_PREFIX + port_id)[:14] return os.path.join(sockdir, sock_name) + + @staticmethod + def provider_network_attribute_updates_supported(): + return [provider_net.SEGMENTATION_ID] diff --git a/neutron/plugins/ml2/rpc.py b/neutron/plugins/ml2/rpc.py index b2eaebbf98e..c48f2fdda5a 100644 --- a/neutron/plugins/ml2/rpc.py +++ b/neutron/plugins/ml2/rpc.py @@ -51,7 +51,8 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin): # 1.4 tunnel_sync rpc signature upgrade to obtain 'host' # 1.5 Support update_device_list and # get_devices_details_list_and_failed_devices - target = oslo_messaging.Target(version='1.5') + # 1.6 Support get_network_details + target = oslo_messaging.Target(version='1.6') def __init__(self, notifier, type_manager): self.setup_tunnel_callback_mixin(notifier, type_manager) @@ -69,7 +70,7 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin): def _get_request_details(kwargs): return (kwargs.get('agent_id'), kwargs.get('host'), - kwargs.get('device')) + kwargs.get('device') or kwargs.get('network')) def get_device_details(self, rpc_context, **kwargs): """Agent requests device details.""" @@ -219,6 +220,15 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin): return {'devices': devices, 'failed_devices': failed_devices} + def get_network_details(self, rpc_context, **kwargs): + """Agent requests network details.""" + agent_id, host, network = self._get_request_details(kwargs) + LOG.debug("Network %(network)s details requested by agent " + "%(agent_id)s with host %(host)s", + {'network': network, 'agent_id': agent_id, 'host': host}) + plugin = directory.get_plugin() + return plugin.get_network(rpc_context, network) + def update_device_down(self, rpc_context, **kwargs): """Device no longer exists on agent.""" # TODO(garyk) - live migration and port status diff --git a/neutron/tests/unit/agent/test_rpc.py b/neutron/tests/unit/agent/test_rpc.py index 724eed89b9b..38efd74f57f 100644 --- a/neutron/tests/unit/agent/test_rpc.py +++ b/neutron/tests/unit/agent/test_rpc.py @@ -54,6 +54,9 @@ class AgentRPCPluginApi(base.BaseTestCase): def test_get_devices_details_list(self): self._test_rpc_call('get_devices_details_list') + def test_get_network_details(self): + self._test_rpc_call('get_network_details') + def test_update_device_down(self): self._test_rpc_call('update_device_down') 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 1ae9472641a..3beb3368ca7 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 @@ -17,6 +17,7 @@ import time import mock from neutron_lib.agent import constants as agent_consts +from neutron_lib.api.definitions import provider_net from neutron_lib import constants as n_const from neutron_lib import rpc as n_rpc from oslo_config import cfg @@ -1208,7 +1209,10 @@ class TestOvsNeutronAgent(object): port = {'id': TEST_PORT_ID1, 'network_id': network['id']} self.agent._update_port_network(port['id'], port['network_id']) - self.agent.network_update(context=None, network=network) + with mock.patch.object(self.agent.plugin_rpc, 'get_network_details'), \ + mock.patch.object(self.agent, + '_update_network_segmentation_id'): + self.agent.network_update(context=None, network=network) self.assertEqual(set([port['id']]), self.agent.updated_ports) def test_network_update_outoforder(self): @@ -1222,7 +1226,10 @@ class TestOvsNeutronAgent(object): self.agent._update_port_network(port['id'], port['network_id']) self.agent.port_delete(context=None, port_id=port['id']) - self.agent.network_update(context=None, network=network) + with mock.patch.object(self.agent.plugin_rpc, 'get_network_details'), \ + mock.patch.object(self.agent, + '_update_network_segmentation_id'): + self.agent.network_update(context=None, network=network) self.assertEqual(set(), self.agent.updated_ports) def test_update_port_network(self): @@ -2362,6 +2369,58 @@ class TestOvsNeutronAgent(object): else: bridge.set_datapath_id.assert_called_once_with(dpid) + def test__update_network_segmentation_id(self): + network = {'id': 'my-net-uuid', + provider_net.SEGMENTATION_ID: 1005, + provider_net.PHYSICAL_NETWORK: 'provider_net', + provider_net.NETWORK_TYPE: n_const.TYPE_VLAN} + self.agent.vlan_manager.add('my-net-uuid', 5, n_const.TYPE_VLAN, + 'provider_net', 1004, None) + mock_phys_br = mock.Mock() + self.agent.phys_brs['provider_net'] = mock_phys_br + self.agent.phys_ofports['provider_net'] = 'phy_ofport' + self.agent.int_ofports['provider_net'] = 'int_ofport' + + with mock.patch.object(self.agent.int_br, 'reclaim_local_vlan') \ + as mock_reclaim_local_vlan, \ + mock.patch.object(self.agent.int_br, 'provision_local_vlan') \ + as mock_provision_local_vlan: + self.agent._update_network_segmentation_id(network) + mock_reclaim_local_vlan.assert_called_once_with( + port='int_ofport', segmentation_id=1004) + mock_provision_local_vlan.assert_called_once_with( + port='int_ofport', lvid=5, segmentation_id=1005) + mock_phys_br.reclaim_local_vlan.assert_called_once_with( + port='phy_ofport', lvid=5) + + def test__update_network_segmentation_id_not_vlan(self): + network = {provider_net.NETWORK_TYPE: 'not_vlan'} + with mock.patch.object(self.agent.vlan_manager, 'get') as mock_get: + self.agent._update_network_segmentation_id(network) + mock_get.assert_not_called() + + def test__update_network_segmentation_id_vlan_not_found(self): + network = {'id': 'my-net-uuid', + provider_net.SEGMENTATION_ID: 1005, + provider_net.NETWORK_TYPE: n_const.TYPE_VLAN, + provider_net.PHYSICAL_NETWORK: 'default_network'} + with mock.patch.object(self.agent.vlan_manager, + 'update_segmentation_id') as mock_update_segid: + self.agent._update_network_segmentation_id(network) + mock_update_segid.assert_not_called() + + def test__update_network_segmentation_id_segmentation_id_not_updated(self): + network = {'id': 'my-net-uuid', + provider_net.SEGMENTATION_ID: 1005, + provider_net.NETWORK_TYPE: n_const.TYPE_VLAN, + provider_net.PHYSICAL_NETWORK: 'default_network'} + self.agent.vlan_manager.add('my-net-uuid', 5, n_const.TYPE_VLAN, + 'provider_net', 1005, None) + with mock.patch.object(self.agent.vlan_manager, + 'update_segmentation_id') as mock_update_segid: + self.agent._update_network_segmentation_id(network) + mock_update_segid.assert_not_called() + class TestOvsNeutronAgentOFCtl(TestOvsNeutronAgent, ovs_test_base.OVSOFCtlTestBase): diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_vlanmanager.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_vlanmanager.py index 429327bee00..9be3f7a83a6 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_vlanmanager.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_vlanmanager.py @@ -118,3 +118,10 @@ class TestLocalVlanManager(base.BaseTestCase): def test_pop_non_existing_raises_exception(self): with testtools.ExpectedException(vlanmanager.MappingNotFound): self.vlan_manager.pop(1) + + def test_update_segmentation_id(self): + self.vlan_manager.add('net_id', 'vlan_id', 'vlan', 'phys_net', + 1001, None) + self.assertEqual(1001, self.vlan_manager.get('net_id').segmentation_id) + self.vlan_manager.update_segmentation_id('net_id', 1002) + self.assertEqual(1002, self.vlan_manager.get('net_id').segmentation_id) diff --git a/neutron/tests/unit/plugins/ml2/test_rpc.py b/neutron/tests/unit/plugins/ml2/test_rpc.py index 69390393cd0..41bd89201d0 100644 --- a/neutron/tests/unit/plugins/ml2/test_rpc.py +++ b/neutron/tests/unit/plugins/ml2/test_rpc.py @@ -214,6 +214,17 @@ class RpcCallbacksTestCase(base.BaseTestCase): self.callbacks.get_devices_details_list_and_failed_devices) self._test_get_devices_list(callback, devices, expected) + def test_get_network_details(self): + kwargs = {'agent_id': 'agent_id', + 'host': 'host_id', + 'network': 'network'} + with mock.patch.object(self.plugin, 'get_network') as mock_get_network: + mock_get_network.return_value = 'net_details' + self.assertEqual( + 'net_details', + self.callbacks.get_network_details('fake_context', **kwargs)) + mock_get_network.assert_called_once_with('fake_context', 'network') + def test_get_devices_details_list_and_failed_devices_empty_dev(self): with mock.patch.object(self.callbacks, 'get_device_details') as f: res = self.callbacks.get_devices_details_list_and_failed_devices( diff --git a/releasenotes/notes/change-segmentation-id-ovs-a201e0ac1c4d4fb6.yaml b/releasenotes/notes/change-segmentation-id-ovs-a201e0ac1c4d4fb6.yaml new file mode 100644 index 00000000000..9c0342bcdfb --- /dev/null +++ b/releasenotes/notes/change-segmentation-id-ovs-a201e0ac1c4d4fb6.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + The segmentation ID of a provider network can be now modified, even with + OVS ports bound. Note that, during this process, the traffic of the bound + ports tagged with the former segmentation ID (external VLAN) will be mapped + to the new one. This can provoke a traffic disruption while the external + network VLAN is migrated to the new tag.