Add test_dvr_ovn.py
Moved downstream tests with some changes - Changed setup() to work properly even on environments where routers can be scheduled on compute nodes. - Adjusted paths and references to fit the whitebox plugin - Adjusted uuids to a new unique ones - Added skip for a single-node environment - Added necessary config options and base functions Also: - Extended tcpdump capture code to support toolbox on coreos. - Added external bridge and tcpdump capture interface name discovering to support environments where different nodes have different interface names. Note: some of tests still need to be adapted for podified environment so for now they will be skipped. Change-Id: I1497862f35ac3c8182a668db87bc608193c6727f
This commit is contained in:
parent
ae81daf2a7
commit
69169975c0
@ -23,7 +23,7 @@ from tempest.lib import exceptions
|
||||
|
||||
from whitebox_neutron_tempest_plugin.common import utils
|
||||
|
||||
CONF = config.CONF
|
||||
WB_CONF = config.CONF.whitebox_neutron_plugin_options
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
@ -35,7 +35,21 @@ class TcpdumpCapture(fixtures.Fixture):
|
||||
self.client = client
|
||||
self.interfaces = [ifc.strip() for ifc in interfaces.split(',')]
|
||||
self.filter_str = filter_str
|
||||
self.timeout = CONF.whitebox_neutron_plugin_options.capture_timeout
|
||||
self.timeout = WB_CONF.capture_timeout
|
||||
result = self.client.exec_command(
|
||||
'which toolbox || echo missing')
|
||||
if 'missing' in result:
|
||||
cmd_prefix = "sudo timeout {}"
|
||||
self.path_prefix = ''
|
||||
else:
|
||||
# (rsafrono) on coreos ocp nodes tcpdump is executed via
|
||||
# toolbox. the toolbox can ask for update, therefore we need
|
||||
# the 'yes no' to skip updating since it can take some time
|
||||
cmd_prefix = "yes no | timeout {} toolbox"
|
||||
# the toolbox runs in a container.
|
||||
# host file system is mounted there to /host
|
||||
self.path_prefix = '/host'
|
||||
self.cmd_prefix = cmd_prefix.format(self.timeout)
|
||||
|
||||
def _setUp(self):
|
||||
self.start()
|
||||
@ -51,10 +65,11 @@ class TcpdumpCapture(fixtures.Fixture):
|
||||
|
||||
for interface in self.interfaces:
|
||||
process = self.client.open_session()
|
||||
capture_file = self.client.exec_command('sudo mktemp').rstrip()
|
||||
cmd = 'sudo timeout {} tcpdump -s0 -Uni {} {} -w {}'.format(
|
||||
self.timeout, interface, self.filter_str,
|
||||
capture_file)
|
||||
capture_file = self.client.exec_command(
|
||||
'mktemp -u').rstrip()
|
||||
cmd = '{} tcpdump -s0 -Uni {} {} -w {}{}'.format(
|
||||
self.cmd_prefix, interface, self.filter_str,
|
||||
self.path_prefix, capture_file)
|
||||
self.capture_files.append(capture_file)
|
||||
LOG.debug('Executing command: {}'.format(cmd))
|
||||
process.exec_command(cmd)
|
||||
@ -71,7 +86,8 @@ class TcpdumpCapture(fixtures.Fixture):
|
||||
if self.capture_files:
|
||||
if utils.host_responds_to_ping(self.client.host):
|
||||
self.client.exec_command(
|
||||
'sudo rm -f ' + ' '.join(self.capture_files))
|
||||
'{} rm -f '.format(self.cmd_prefix) + ' '.join(
|
||||
self.capture_files))
|
||||
self.capture_files = None
|
||||
|
||||
def is_empty(self):
|
||||
@ -101,10 +117,11 @@ class TcpdumpCapture(fixtures.Fixture):
|
||||
merged_cap_file = self.capture_files[0]
|
||||
else:
|
||||
cap_file_candidates = []
|
||||
print_pcap_file_cmd = 'sudo tcpdump -r {} | wc -l'
|
||||
print_pcap_file_cmd = '{} tcpdump -r {} | wc -l'
|
||||
for cap_file in self.capture_files:
|
||||
if 0 < int(self.client.exec_command(
|
||||
print_pcap_file_cmd.format(cap_file)).rstrip()):
|
||||
print_pcap_file_cmd.format(
|
||||
self.cmd_prefix, cap_file)).rstrip()):
|
||||
# cap files that are not empty
|
||||
cap_file_candidates.append(cap_file)
|
||||
|
||||
@ -115,13 +132,13 @@ class TcpdumpCapture(fixtures.Fixture):
|
||||
merged_cap_file = cap_file_candidates[0]
|
||||
else:
|
||||
merged_cap_file = self.client.exec_command(
|
||||
'sudo mktemp').rstrip()
|
||||
'mktemp -u').rstrip()
|
||||
n_retries = 5
|
||||
for i in range(n_retries):
|
||||
try:
|
||||
self.client.exec_command(
|
||||
'sudo tcpslice -w {} {}'.format(
|
||||
merged_cap_file,
|
||||
'{} tcpslice -w {} {}'.format(
|
||||
self.cmd_prefix, merged_cap_file,
|
||||
' '.join(cap_file_candidates)))
|
||||
except exceptions.SSHExecCommandFailed as exc:
|
||||
if i == (n_retries - 1):
|
||||
@ -132,6 +149,6 @@ class TcpdumpCapture(fixtures.Fixture):
|
||||
break
|
||||
|
||||
ssh_channel = self.client.open_session()
|
||||
ssh_channel.exec_command('sudo cat ' + merged_cap_file)
|
||||
ssh_channel.exec_command('cat ' + merged_cap_file)
|
||||
self.addCleanup(ssh_channel.close)
|
||||
return ssh_channel.makefile()
|
||||
|
@ -96,6 +96,9 @@ WhiteboxNeutronPluginOptions = [
|
||||
default=False,
|
||||
help='Specifies whether the OSP setup under test has been '
|
||||
'configured with BGP functionality or not'),
|
||||
cfg.StrOpt('bgp_agent_config',
|
||||
default='/etc/ovn-bgp-agent/bgp-agent.conf',
|
||||
help='Path to ovn-bgp-agent config file'),
|
||||
cfg.IntOpt('sriov_pfs_per_host',
|
||||
default=1,
|
||||
help='Number of available PF (Physical Function) ports per'
|
||||
@ -116,12 +119,19 @@ WhiteboxNeutronPluginOptions = [
|
||||
cfg.StrOpt('ml2_plugin_config',
|
||||
default='/etc/neutron/plugins/ml2/ml2_conf.ini',
|
||||
help='Path to ml2 plugin config.'),
|
||||
cfg.StrOpt('ext_bridge',
|
||||
default='br-ex',
|
||||
help="OpenvSwitch bridge dedicated for external network."),
|
||||
cfg.StrOpt('node_integration_bridge',
|
||||
default='br-int',
|
||||
help="OpenvSwitch bridge dedicated for OVN's use."),
|
||||
cfg.StrOpt('ext_bridge',
|
||||
default='{"default": "br-ex", "alt": "ospbr"}',
|
||||
help="Bridge dedicated for external network. Dict with values "
|
||||
"for default node and alternative node (if exist)."),
|
||||
cfg.StrOpt('node_ext_interface',
|
||||
default=None,
|
||||
help='Physical interface of a node that is connected to the'
|
||||
'external network. In case the value is set to None the '
|
||||
'interface name will be discovered from a list of '
|
||||
'interfaces under bridge connected to external network'),
|
||||
cfg.IntOpt('ovn_max_controller_gw_ports_per_router',
|
||||
default=1,
|
||||
help='The number of network nodes used '
|
||||
|
@ -14,6 +14,7 @@
|
||||
# under the License.
|
||||
import base64
|
||||
from functools import partial
|
||||
import json
|
||||
from multiprocessing import Process
|
||||
import os
|
||||
import random
|
||||
@ -65,6 +66,7 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
|
||||
sriov_agents = [
|
||||
agent for agent in agents if 'sriov' in agent['binary']]
|
||||
cls.has_sriov_support = True if sriov_agents else False
|
||||
cls.ext_bridge = json.loads(WB_CONF.ext_bridge)
|
||||
# deployer tool dependent variables
|
||||
if WB_CONF.openstack_type == 'devstack':
|
||||
cls.master_node_client = cls.get_node_client('localhost')
|
||||
@ -113,6 +115,16 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
|
||||
subnet['ip_version'] == constants.IP_VERSION_4):
|
||||
return subnet['gateway_ip']
|
||||
|
||||
def get_external_bridge(self, client):
|
||||
commands = [
|
||||
"sudo ovs-vsctl list-br",
|
||||
"sudo ip -o link show type bridge | cut -d ' ' -f 2 | tr -d ':'"]
|
||||
for cmd in commands:
|
||||
result = client.exec_command(cmd).strip().split()
|
||||
if self.ext_bridge['default'] in result:
|
||||
return self.ext_bridge['default']
|
||||
return self.ext_bridge['alt']
|
||||
|
||||
@staticmethod
|
||||
def get_node_client(
|
||||
host, username=WB_CONF.overcloud_ssh_user, pkey=None,
|
||||
@ -123,16 +135,6 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
|
||||
return ssh.Client(host=host, username=username,
|
||||
key_filename=key_filename)
|
||||
|
||||
def find_different_compute_host(self, exclude_hosts):
|
||||
for node in self.nodes:
|
||||
if not node['is_compute']:
|
||||
continue
|
||||
if node['is_compute'] and not node['name'] in exclude_hosts:
|
||||
return node['name']
|
||||
raise self.skipException(
|
||||
"Not able to find a different compute than: {}".format(
|
||||
exclude_hosts))
|
||||
|
||||
def get_local_ssh_client(self, network):
|
||||
return ssh.Client(
|
||||
host=self._get_local_ip_from_network(
|
||||
@ -152,6 +154,16 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
|
||||
if node['name'] == node_name:
|
||||
return node['client']
|
||||
|
||||
def find_different_compute_host(self, exclude_hosts):
|
||||
for node in self.nodes:
|
||||
if not node['is_compute']:
|
||||
continue
|
||||
if node['is_compute'] and not node['name'] in exclude_hosts:
|
||||
return node['name']
|
||||
raise self.skipException(
|
||||
"Not able to find a different compute than: {}".format(
|
||||
exclude_hosts))
|
||||
|
||||
@staticmethod
|
||||
def _get_local_ip_from_network(network):
|
||||
host_ip_addresses = [ifaddresses(iface)[AF_INET][0]['addr']
|
||||
@ -342,7 +354,7 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
|
||||
@classmethod
|
||||
def check_service_setting(
|
||||
cls, host, service='neutron', config_files=None,
|
||||
section='DEFAULT', param='', value='True',
|
||||
section='DEFAULT', param='', value='true',
|
||||
msg='Required config value is missing', skip_if_fails=True):
|
||||
"""Check if a service on a node has a setting with a value in config
|
||||
|
||||
@ -354,7 +366,7 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
|
||||
2 config files with same sections.
|
||||
:param section(str): Section in the config file.
|
||||
:param param(str): Parameter in section to check.
|
||||
:param value(str): Expected value.
|
||||
:param value(str): Expected value, case insensitive.
|
||||
:param msg(str): Message to print in case of expected value not found
|
||||
:param skip_if_fails(bool): skip if the check fails - if it fails and
|
||||
skip_if_fails is False, return False.
|
||||
@ -374,7 +386,7 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
|
||||
LOG.debug("Command = '{}'".format(cmd))
|
||||
result = host['client'].exec_command(cmd)
|
||||
LOG.debug("Result = '{}'".format(result))
|
||||
if value in result:
|
||||
if value.lower() in result.lower():
|
||||
return True
|
||||
else:
|
||||
continue
|
||||
@ -886,9 +898,31 @@ class TrafficFlowTest(BaseTempestWhiteboxTestCase):
|
||||
cls.discover_nodes()
|
||||
|
||||
def _start_captures(self, interface, filters):
|
||||
def get_interface(client):
|
||||
# (rsafrono) discover interfaces, useful when capturing on
|
||||
# nodes and different types of nodes have different interfaces,
|
||||
# e.g. on podified environment ocp and compute have different
|
||||
# names of interfaces connected to external network
|
||||
bridge = self.get_external_bridge(client)
|
||||
filters = [r"| grep 'eth\|enp\|ens' | grep -v veth ",
|
||||
"| cut -f2 -d ' ' | tr -d ':'"]
|
||||
commands = [
|
||||
"sudo ovs-vsctl list-ports " + bridge + filters[0],
|
||||
"sudo ip -o link show master " + bridge + filters[0] +
|
||||
filters[1]]
|
||||
for cmd in commands:
|
||||
interfaces = client.exec_command(
|
||||
cmd + " || true").strip().split()
|
||||
if interfaces:
|
||||
return ','.join(interfaces)
|
||||
|
||||
for node in self.nodes:
|
||||
if interface:
|
||||
node_interface = interface
|
||||
else:
|
||||
node_interface = get_interface(node['client'])
|
||||
node['capture'] = capture.TcpdumpCapture(
|
||||
node['client'], interface, filters)
|
||||
node['client'], node_interface, filters)
|
||||
self.useFixture(node['capture'])
|
||||
time.sleep(2)
|
||||
|
||||
|
1019
whitebox_neutron_tempest_plugin/tests/scenario/test_dvr_ovn.py
Normal file
1019
whitebox_neutron_tempest_plugin/tests/scenario/test_dvr_ovn.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -330,7 +330,7 @@ class ProviderRoutedNetworkOVNConfigTest(ProviderRoutedNetworkBaseTest,
|
||||
to its availability zone.
|
||||
"""
|
||||
|
||||
ext_bridge = WB_CONF.ext_bridge
|
||||
ext_bridge = self.ext_bridge['default']
|
||||
for _, details in self.resources.items():
|
||||
for host in details['hosts']:
|
||||
mapping = self.get_host_ovn_bridge_mapping(host)
|
||||
|
Loading…
Reference in New Issue
Block a user