
This patch adds necessary adjustments to DVR tests that recreate floating ip and the ones that test OVN DVR after VM live migration in order to allow them to run properly on a podified environment. Fixed tests list: test_validate_floatingip_compute_ingress_delete_fip_restart_instance test_dvr_create_delete_fip_restart_instance test_validate_dvr_connectivity_live_migration_basic test_validate_dvr_connectivity_live_migration_different_networks Change-Id: I5f75784ca879161f486d778b4a39c4d0410aad44
163 lines
6.2 KiB
Python
163 lines
6.2 KiB
Python
# Copyright 2024 Red Hat, Inc.
|
|
# 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 re
|
|
import time
|
|
|
|
import fixtures
|
|
from oslo_log import log
|
|
from scapy.all import ICMP
|
|
from scapy.all import rdpcap
|
|
from tempest import config
|
|
from tempest.lib import exceptions
|
|
|
|
from whitebox_neutron_tempest_plugin.common import utils
|
|
|
|
WB_CONF = config.CONF.whitebox_neutron_plugin_options
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
class TcpdumpCapture(fixtures.Fixture):
|
|
capture_files = None
|
|
processes = None
|
|
|
|
def __init__(self, client, interfaces, filter_str=''):
|
|
self.client = client
|
|
self.interfaces = [ifc.strip() for ifc in interfaces.split(',')]
|
|
self.filter_str = filter_str
|
|
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'
|
|
# when running tcpdump via the toolbox it is necessary
|
|
# to escape symbols like () in tcpdump filters
|
|
self.filter_str = re.escape(filter_str)
|
|
self.cmd_prefix = cmd_prefix.format(self.timeout)
|
|
|
|
def _setUp(self):
|
|
self.start()
|
|
|
|
def start(self):
|
|
if not self.capture_files:
|
|
# mktemp needs to be executed with sudo - otherwise the later
|
|
# tcpdump command (run with sudo) fails because the created temp
|
|
# file cannot be written
|
|
# This happens in RHEL9 because fs.protected_regular is enabled
|
|
self.capture_files = []
|
|
self.processes = []
|
|
|
|
for interface in self.interfaces:
|
|
process = self.client.open_session()
|
|
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)
|
|
self.processes.append(process)
|
|
self.addCleanup(self.cleanup)
|
|
|
|
def stop(self):
|
|
for process in (self.processes or []):
|
|
process.close()
|
|
self.processes = None
|
|
if 'toolbox' in self.client.exec_command(
|
|
'which toolbox || true'):
|
|
self.client.exec_command(
|
|
"sudo podman stop toolbox-$(whoami) || true")
|
|
|
|
def cleanup(self):
|
|
self.stop()
|
|
if self.capture_files:
|
|
if utils.host_responds_to_ping(self.client.host):
|
|
self.client.exec_command(
|
|
'{} rm -f '.format(self.cmd_prefix) + ' '.join(
|
|
self.capture_files))
|
|
self.capture_files = None
|
|
|
|
def is_empty(self):
|
|
try:
|
|
pcap = rdpcap(self._open_capture_file())
|
|
except Exception as e:
|
|
LOG.debug('Error reading pcap file: ', str(e))
|
|
return True
|
|
for record in pcap:
|
|
return False
|
|
return True
|
|
|
|
def get_next_hop_mtu(self):
|
|
pcap = rdpcap(self._open_capture_file())
|
|
for record in pcap:
|
|
if 'IP' in record and 'ICMP' in record:
|
|
icmp = record[ICMP]
|
|
# ICMP type 3 = Destionation Unreachable
|
|
if icmp.type == 3:
|
|
return repr(icmp.nexthopmtu)
|
|
return None
|
|
|
|
def _open_capture_file(self):
|
|
if not self.capture_files:
|
|
raise ValueError('No capture files available')
|
|
elif len(self.capture_files) == 1:
|
|
merged_cap_file = self.capture_files[0]
|
|
else:
|
|
cap_file_candidates = []
|
|
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(
|
|
self.cmd_prefix, cap_file)).rstrip()):
|
|
# cap files that are not empty
|
|
cap_file_candidates.append(cap_file)
|
|
|
|
if not cap_file_candidates:
|
|
# they are all empty
|
|
merged_cap_file = self.capture_files[0]
|
|
elif 1 == len(cap_file_candidates):
|
|
merged_cap_file = cap_file_candidates[0]
|
|
else:
|
|
merged_cap_file = self.client.exec_command(
|
|
'mktemp -u').rstrip()
|
|
n_retries = 5
|
|
for i in range(n_retries):
|
|
try:
|
|
self.client.exec_command(
|
|
'{} tcpslice -w {} {}'.format(
|
|
self.cmd_prefix, merged_cap_file,
|
|
' '.join(cap_file_candidates)))
|
|
except exceptions.SSHExecCommandFailed as exc:
|
|
if i == (n_retries - 1):
|
|
raise exc
|
|
LOG.warn('tcpslice command failed - retrying...')
|
|
time.sleep(5)
|
|
else:
|
|
break
|
|
|
|
ssh_channel = self.client.open_session()
|
|
ssh_channel.exec_command('cat ' + merged_cap_file)
|
|
self.addCleanup(ssh_channel.close)
|
|
return ssh_channel.makefile()
|