Change provider network segmentation ID in OVS agent

Added the ability to change the segmentation ID of a network
with ports bound to OVS agent. The rules, both in the integration
bridge and the physical bridge, to convert the internal VLAN tag
and the external segmentation ID (external VLAN tag) are deleted
and created again with the new value. The traffic from the tenant
networks will be tagged then with the new segmentation ID.

Added get network details agent RPC call to retrieve the information
of the updated network.

Partial-Bug: #1806052

Change-Id: I69f6f3ef717c3ed40218099b1f389afd3d39bd62
changes/84/632984/28
Rodolfo Alonso Hernandez 4 years ago
parent 15dc507f9e
commit 2bcc178be1
  1. 6
      neutron/agent/rpc.py
  2. 35
      neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py
  3. 6
      neutron/plugins/ml2/drivers/openvswitch/agent/vlanmanager.py
  4. 5
      neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py
  5. 14
      neutron/plugins/ml2/rpc.py
  6. 3
      neutron/tests/unit/agent/test_rpc.py
  7. 63
      neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py
  8. 7
      neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_vlanmanager.py
  9. 11
      neutron/tests/unit/plugins/ml2/test_rpc.py
  10. 8
      releasenotes/notes/change-segmentation-id-ovs-a201e0ac1c4d4fb6.yaml

@ -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,

@ -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

@ -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)

@ -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]

@ -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

@ -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')

@ -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):

@ -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)

@ -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(

@ -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.
Loading…
Cancel
Save