Slawek Kaplonski 81d9dc6831 Log packets captured by tcpdump on the nodes in case of test failure
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
2024-12-09 13:11:52 +00:00

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