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
This commit is contained in:
parent
15dc507f9e
commit
2bcc178be1
@ -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…
x
Reference in New Issue
Block a user