
In the tests which are using tcpdump to check if traffic is going through the right node(s) there was only check if something was captured or not. But in case of failure we didn't know what was captured what caused issue. Now this patch adds logging of the packets captured on all of the nodes in case if the assertion in test failed. Hopefully that will help debugging issues like in the related bug. Related-bug: #OSPRH-11312 Change-Id: I1025ae0c9dbb50d187b2827a8a7c4de864e35875
145 lines
5.4 KiB
Python
145 lines
5.4 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 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='', extra_prefix=''):
|
|
self.client = client
|
|
self.interfaces = [ifc.strip() for ifc in interfaces.split(',')]
|
|
self.filter_str = filter_str
|
|
self.timeout = WB_CONF.capture_timeout
|
|
self.cmd_prefix = "{} sudo timeout {}".format(
|
|
extra_prefix, 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('sudo mktemp').rstrip()
|
|
cmd = '{} tcpdump -s0 -Uni {} {} -w {}'.format(
|
|
self.cmd_prefix, interface,
|
|
self.filter_str, capture_file)
|
|
self.capture_files.append(capture_file)
|
|
LOG.debug('Executing command: %s', 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
|
|
|
|
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 get_captured_records(self):
|
|
return [str(r) for r in rdpcap(self._open_capture_file())]
|
|
|
|
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(
|
|
self.cmd_prefix + ' mktemp').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(self.cmd_prefix + ' cat ' + merged_cap_file)
|
|
self.addCleanup(ssh_channel.close)
|
|
return ssh_channel.makefile()
|