Merge "Change provider network segmentation ID in OVS agent"

This commit is contained in:
Zuul 2019-05-22 12:36:20 +00:00 committed by Gerrit Code Review
commit 8887343782
10 changed files with 154 additions and 4 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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