Files
neutron/neutron/tests/functional/agent/l2/base.py
LIU Yulong a5244d6d44 More accurate agent restart state transfer
Ovs-agent can be very time-consuming in handling a large number
of ports. At this point, the ovs-agent status report may have
exceeded the set timeout value. Some flows updating operations
will not be triggerred. This results in flows loss during agent
restart, especially for hosts to hosts of vxlan tunnel flow.

This fix will let the ovs-agent explicitly, in the first rpc loop,
indicate that the status is restarted. Then l2pop will be required
to update fdb entries.

Closes-Bug: #1813703
Closes-Bug: #1813714
Closes-Bug: #1813715
Closes-Bug: #1794991
Closes-Bug: #1799178

Change-Id: I8edc2deb509216add1fb21e1893f1c17dda80961
2019-03-21 15:12:31 +00:00

414 lines
18 KiB
Python

# Copyright (c) 2015 Red Hat, Inc.
# Copyright (c) 2015 SUSE Linux Products GmbH
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import random
import eventlet
import mock
from neutron_lib import constants as n_const
from neutron_lib.utils import net
from oslo_config import cfg
from oslo_utils import uuidutils
from neutron.agent.common import ovs_lib
from neutron.agent.common import polling
from neutron.agent.l2 import l2_agent_extensions_manager as ext_manager
from neutron.agent.linux import interface
from neutron.common import utils
from neutron.conf.agent import common as agent_config
from neutron.conf import common as common_config
from neutron.conf.plugins.ml2.drivers import agent
from neutron.conf.plugins.ml2.drivers import ovs_conf
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
from neutron.plugins.ml2.drivers.openvswitch.agent.extension_drivers \
import qos_driver as ovs_qos_driver
from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.ovs_ofctl \
import br_int
from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.ovs_ofctl \
import br_phys
from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.ovs_ofctl \
import br_tun
from neutron.plugins.ml2.drivers.openvswitch.agent import ovs_neutron_agent \
as ovs_agent
from neutron.tests.common import net_helpers
from neutron.tests.functional.agent.linux import base
class OVSAgentTestFramework(base.BaseOVSLinuxTestCase):
def setUp(self):
super(OVSAgentTestFramework, self).setUp()
agent_rpc = ('neutron.plugins.ml2.drivers.openvswitch.agent.'
'ovs_neutron_agent.OVSPluginApi')
mock.patch(agent_rpc).start()
mock.patch('neutron.agent.rpc.PluginReportStateAPI').start()
self.br_int = utils.get_rand_name(n_const.DEVICE_NAME_MAX_LEN,
prefix='br-int')
self.br_tun = utils.get_rand_name(n_const.DEVICE_NAME_MAX_LEN,
prefix='br-tun')
self.br_phys = utils.get_rand_name(n_const.DEVICE_NAME_MAX_LEN,
prefix='br-phys')
patch_name_len = n_const.DEVICE_NAME_MAX_LEN - len("-patch-tun")
self.patch_tun = "%s-patch-tun" % self.br_int[patch_name_len:]
self.patch_int = "%s-patch-int" % self.br_tun[patch_name_len:]
self.ovs = ovs_lib.BaseOVS()
self.config = self._configure_agent()
self.driver = interface.OVSInterfaceDriver(self.config)
self.namespace = self.useFixture(net_helpers.NamespaceFixture()).name
def _get_config_opts(self):
config = cfg.ConfigOpts()
config.register_opts(common_config.core_opts)
agent.register_agent_opts(config)
ovs_conf.register_ovs_agent_opts(config)
agent_config.register_interface_opts(config)
agent_config.register_interface_driver_opts_helper(config)
agent_config.register_agent_state_opts_helper(config)
ext_manager.register_opts(config)
return config
def _configure_agent(self):
config = self._get_config_opts()
config.set_override(
'interface_driver',
'neutron.agent.linux.interface.OVSInterfaceDriver')
config.set_override('integration_bridge', self.br_int, "OVS")
config.set_override('ovs_integration_bridge', self.br_int)
config.set_override('tunnel_bridge', self.br_tun, "OVS")
config.set_override('int_peer_patch_port', self.patch_tun, "OVS")
config.set_override('tun_peer_patch_port', self.patch_int, "OVS")
config.set_override('host', 'ovs-agent')
return config
def _bridge_classes(self):
return {
'br_int': br_int.OVSIntegrationBridge,
'br_phys': br_phys.OVSPhysicalBridge,
'br_tun': br_tun.OVSTunnelBridge
}
def create_agent(self, create_tunnels=True, ancillary_bridge=None,
local_ip='192.168.10.1'):
if create_tunnels:
tunnel_types = [n_const.TYPE_VXLAN]
else:
tunnel_types = None
bridge_mappings = ['physnet:%s' % self.br_phys]
self.config.set_override('tunnel_types', tunnel_types, "AGENT")
self.config.set_override('polling_interval', 1, "AGENT")
self.config.set_override('local_ip', local_ip, "OVS")
self.config.set_override('bridge_mappings', bridge_mappings, "OVS")
# Physical bridges should be created prior to running
self._bridge_classes()['br_phys'](self.br_phys).create()
ext_mgr = ext_manager.L2AgentExtensionsManager(self.config)
with mock.patch.object(ovs_qos_driver.QosOVSAgentDriver,
'_minimum_bandwidth_initialize'):
agent = ovs_agent.OVSNeutronAgent(self._bridge_classes(),
ext_mgr, self.config)
self.addCleanup(self.ovs.delete_bridge, self.br_int)
if tunnel_types:
self.addCleanup(self.ovs.delete_bridge, self.br_tun)
self.addCleanup(self.ovs.delete_bridge, self.br_phys)
agent.sg_agent = mock.Mock()
agent.ancillary_brs = []
if ancillary_bridge:
agent.ancillary_brs.append(ancillary_bridge)
return agent
def _mock_get_events(self, agent, polling_manager, ports):
get_events = polling_manager.get_events
p_ids = [p['id'] for p in ports]
def filter_events():
events = get_events()
filtered_ports = []
for dev in events['added']:
iface_id = agent.int_br.portid_from_external_ids(
dev.get('external_ids', []))
if iface_id in p_ids:
# if the event is not about a port that was created by
# this test, we filter the event out. Since these tests are
# not run in isolation processing all the events might make
# some test fail ( e.g. the agent might keep resycing
# because it keeps finding not ready ports that are created
# by other tests)
filtered_ports.append(dev)
return {'added': filtered_ports, 'removed': events['removed']}
polling_manager.get_events = mock.Mock(side_effect=filter_events)
def stop_agent(self, agent, rpc_loop_thread):
agent.run_daemon_loop = False
rpc_loop_thread.wait()
def start_agent(self, agent, ports=None, unplug_ports=None):
if unplug_ports is None:
unplug_ports = []
if ports is None:
ports = []
self.setup_agent_rpc_mocks(agent, unplug_ports)
polling_manager = polling.InterfacePollingMinimizer()
self._mock_get_events(agent, polling_manager, ports)
self.addCleanup(polling_manager.stop)
polling_manager.start()
utils.wait_until_true(
polling_manager._monitor.is_active)
agent.check_ovs_status = mock.Mock(
return_value=constants.OVS_NORMAL)
self.agent_thread = eventlet.spawn(agent.rpc_loop,
polling_manager)
self.addCleanup(self.stop_agent, agent, self.agent_thread)
return polling_manager
def _create_test_port_dict(self):
return {'id': uuidutils.generate_uuid(),
'mac_address': net.get_random_mac(
'fa:16:3e:00:00:00'.split(':')),
'fixed_ips': [{
'ip_address': '10.%d.%d.%d' % (
random.randint(3, 254),
random.randint(3, 254),
random.randint(3, 254))}],
'vif_name': utils.get_rand_name(
self.driver.DEV_NAME_LEN, self.driver.DEV_NAME_PREFIX)}
def _create_test_network_dict(self):
return {'id': uuidutils.generate_uuid(),
'tenant_id': uuidutils.generate_uuid()}
def _plug_ports(self, network, ports, agent,
bridge=None, namespace=None):
if namespace is None:
namespace = self.namespace
for port in ports:
bridge = bridge or agent.int_br
self.driver.plug(
network.get('id'), port.get('id'), port.get('vif_name'),
port.get('mac_address'),
bridge.br_name, namespace=namespace)
ip_cidrs = ["%s/8" % (port.get('fixed_ips')[0][
'ip_address'])]
self.driver.init_l3(port.get('vif_name'), ip_cidrs,
namespace=namespace)
def _unplug_ports(self, ports, agent):
for port in ports:
self.driver.unplug(
port.get('vif_name'), agent.int_br.br_name, self.namespace)
def _get_device_details(self, port, network):
dev = {'device': port['id'],
'port_id': port['id'],
'network_id': network['id'],
'network_type': network.get('network_type', 'vlan'),
'physical_network': network.get('physical_network', 'physnet'),
'segmentation_id': network.get('segmentation_id', 1),
'fixed_ips': port['fixed_ips'],
'device_owner': n_const.DEVICE_OWNER_COMPUTE_PREFIX,
'admin_state_up': True}
return dev
def assert_bridge(self, br, exists=True):
self.assertEqual(exists, self.ovs.bridge_exists(br))
def assert_patch_ports(self, agent):
def get_peer(port):
return agent.int_br.db_get_val(
'Interface', port, 'options', check_error=True)
utils.wait_until_true(
lambda: get_peer(self.patch_int) == {'peer': self.patch_tun})
utils.wait_until_true(
lambda: get_peer(self.patch_tun) == {'peer': self.patch_int})
def assert_bridge_ports(self):
for port in [self.patch_tun, self.patch_int]:
self.assertTrue(self.ovs.port_exists(port))
def assert_vlan_tags(self, ports, agent):
for port in ports:
res = agent.int_br.db_get_val('Port', port.get('vif_name'), 'tag')
self.assertTrue(res)
def _expected_plugin_rpc_call(self, call, expected_devices, is_up=True):
"""Helper to check expected rpc call are received
:param call: The call to check
:param expected_devices: The device for which call is expected
:param is_up: True if expected_devices are devices that are set up,
False if expected_devices are devices that are set down
"""
if is_up:
rpc_devices = [
dev for args in call.call_args_list for dev in args[0][1]]
else:
rpc_devices = [
dev for args in call.call_args_list for dev in args[0][2]]
for dev in rpc_devices:
if dev in expected_devices:
expected_devices.remove(dev)
# reset mock otherwise if the mock is called again the same call param
# will be processed again
call.reset_mock()
return not expected_devices
def create_test_ports(self, amount=3, **kwargs):
ports = []
for x in range(amount):
ports.append(self._create_test_port_dict(**kwargs))
return ports
def _mock_update_device(self, context, devices_up, devices_down, agent_id,
host=None, agent_restarted=False):
dev_up = []
dev_down = []
for port in self.ports:
if devices_up and port['id'] in devices_up:
dev_up.append(port['id'])
if devices_down and port['id'] in devices_down:
dev_down.append({'device': port['id'], 'exists': True})
return {'devices_up': dev_up,
'failed_devices_up': [],
'devices_down': dev_down,
'failed_devices_down': []}
def setup_agent_rpc_mocks(self, agent, unplug_ports):
def mock_device_details(context, devices, agent_id, host=None):
details = []
for port in self.ports:
if port['id'] in devices:
dev = self._get_device_details(
port, self.network)
details.append(dev)
ports_to_unplug = [x for x in unplug_ports if x['id'] in devices]
if ports_to_unplug:
self._unplug_ports(ports_to_unplug, self.agent)
return {'devices': details, 'failed_devices': []}
(agent.plugin_rpc.get_devices_details_list_and_failed_devices.
side_effect) = mock_device_details
agent.plugin_rpc.update_device_list.side_effect = (
self._mock_update_device)
def _prepare_resync_trigger(self, agent):
def mock_device_raise_exception(context, devices_up, devices_down,
agent_id, host=None):
agent.plugin_rpc.update_device_list.side_effect = (
self._mock_update_device)
raise Exception('Exception to trigger resync')
self.agent.plugin_rpc.update_device_list.side_effect = (
mock_device_raise_exception)
def _prepare_failed_dev_up_trigger(self, agent):
def mock_failed_devices_up(context, devices_up, devices_down,
agent_id, host=None,
agent_restarted=False):
failed_devices = []
devices = list(devices_up)
# first port fails
if self.ports[0]['id'] in devices_up:
# reassign side_effect so that next RPC call will succeed
agent.plugin_rpc.update_device_list.side_effect = (
self._mock_update_device)
devices.remove(self.ports[0]['id'])
failed_devices.append(self.ports[0]['id'])
return {'devices_up': devices,
'failed_devices_up': failed_devices,
'devices_down': [],
'failed_devices_down': []}
self.agent.plugin_rpc.update_device_list.side_effect = (
mock_failed_devices_up)
def _prepare_failed_dev_down_trigger(self, agent):
def mock_failed_devices_down(context, devices_up, devices_down,
agent_id, host=None,
agent_restarted=False):
# first port fails
failed_port_id = self.ports[0]['id']
failed_devices_down = []
dev_down = [
{'device': p['id'], 'exists': True}
for p in self.ports if p['id'] in devices_down and (
p['id'] != failed_port_id)]
# check if it's the call to set devices down and if the device
# that is supposed to fail is in the call then modify the
# side_effect so that next RPC call will succeed.
if devices_down and failed_port_id in devices_down:
agent.plugin_rpc.update_device_list.side_effect = (
self._mock_update_device)
failed_devices_down.append(failed_port_id)
return {'devices_up': devices_up,
'failed_devices_up': [],
'devices_down': dev_down,
'failed_devices_down': failed_devices_down}
self.agent.plugin_rpc.update_device_list.side_effect = (
mock_failed_devices_down)
def wait_until_ports_state(self, ports, up, timeout=60):
port_ids = [p['id'] for p in ports]
utils.wait_until_true(
lambda: self._expected_plugin_rpc_call(
self.agent.plugin_rpc.update_device_list, port_ids, up),
timeout=timeout)
def setup_agent_and_ports(self, port_dicts, create_tunnels=True,
ancillary_bridge=None,
trigger_resync=False,
failed_dev_up=False,
failed_dev_down=False,
network=None):
self.ports = port_dicts
self.agent = self.create_agent(create_tunnels=create_tunnels,
ancillary_bridge=ancillary_bridge)
self.polling_manager = self.start_agent(self.agent, ports=self.ports)
self.network = network or self._create_test_network_dict()
if trigger_resync:
self._prepare_resync_trigger(self.agent)
elif failed_dev_up:
self._prepare_failed_dev_up_trigger(self.agent)
elif failed_dev_down:
self._prepare_failed_dev_down_trigger(self.agent)
self._plug_ports(self.network, self.ports, self.agent,
bridge=ancillary_bridge)
def plug_ports_to_phys_br(self, network, ports, namespace=None):
physical_network = network.get('physical_network', 'physnet')
phys_segmentation_id = network.get('segmentation_id', None)
network_type = network.get('network_type', 'flat')
phys_br = self.agent.phys_brs[physical_network]
self._plug_ports(network, ports, self.agent, bridge=phys_br,
namespace=namespace)
if network_type == 'flat':
# NOTE(slaweq): for OVS implementations remove the DEAD VLAN tag
# on ports that belongs to flat network. DEAD VLAN tag is added
# to each newly created port. This is related to lp#1767422
for port in ports:
phys_br.clear_db_attribute("Port", port['vif_name'], "tag")
elif phys_segmentation_id and network_type == 'vlan':
for port in ports:
phys_br.set_db_attribute(
"Port", port['vif_name'], "tag", phys_segmentation_id)