commit 209be7924743300d33d780a0dc842490d627a049
Author: Radek Pazdera <rpazdera(a)redhat.com>
Date: Thu Jun 21 11:24:27 2012 +0200
nettestctl: New CLI option --packet_capture (-p)
This commit introduces new CLI `-p' option to nettestctl.py.
If enabled (it is off by default), network communication from all
test interfaces (excluding controller ports) are logged to hard-drive.
These logs are transfered to controller machine when the recipe
exection is over. In case of an error at the slave side, the dump
files are still transfered, but they will not be transfered when
the controller is killed during its execution.
Log files are in binary *.pcap format and can be found in the `Logs'
directory with the slave log files. For instance:
Logs/2012-06-20_18:56:42/mcast/10.34.1.236/1.pcap
The path contains:
1. date of execution - 2012-06-20_18:56:42
2. recipe name - mcast
3. slave hostname - 10.34.1.236
4. capture file name - 1.pcap
The naming convention of the capture file is <id>.pcap, where <id>
is a netdevice id from machine's netconfig.
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
Common/Logs.py | 5 +++
Common/PacketCapture.py | 64 ++++++++++++++++++++++++++++++++++++++++
NetTest/NetTestController.py | 66 ++++++++++++++++++++++++++++++++++++++++--
NetTest/NetTestSlave.py | 31 +++++++++++++++++++
nettestctl.py | 37 ++++++++++++++++-------
5 files changed, 188 insertions(+), 15 deletions(-)
---
diff --git a/Common/Logs.py b/Common/Logs.py
index 961e158..ac0d5d3 100644
--- a/Common/Logs.py
+++ b/Common/Logs.py
@@ -262,6 +262,11 @@ class Logs:
@classmethod
+ def get_logging_root_path(cls):
+ return cls.root_path
+
+
+ @classmethod
def prepare_logging(cls, debug=0, waitForNet=False,
recipe_path=None, to_display=True):
"""
diff --git a/Common/PacketCapture.py b/Common/PacketCapture.py
new file mode 100644
index 0000000..8cfad17
--- /dev/null
+++ b/Common/PacketCapture.py
@@ -0,0 +1,64 @@
+"""
+This module contains tools for capturing packets within LNST.
+
+Copyright 2012 Red Hat, Inc.
+Licensed under the GNU General Public License, version 2 as
+published by the Free Software Foundation; see COPYING for details.
+"""
+
+__author__ = """
+rpazdera(a)redhat.com (Radek Pazdera)
+"""
+
+import logging
+import subprocess
+
+class PacketCapture:
+ """ Capture/handle traffic that goes through a specific
+ network interface. Capturing backend of this class
+ is provided by tcpdump(8).
+ """
+
+ _cmd = ""
+ _tcpdump = None
+
+ _devname = None
+ _file = None
+ _filter = None
+
+ def set_interface(self, devname):
+ self._devname = devname
+
+ def set_output_file(self, file_path):
+ self._file = file_path
+
+ def set_filter(self, filt):
+ self._filter = filt
+
+ def start(self):
+ self._run()
+
+ def stop(self):
+ """ Send SIGTERM to the background instance of
+ tcpdump.
+ """
+ self._tcpdump.terminate()
+
+ def _compose_cmd(self):
+ """ Create a command from the options """
+ interface = self._devname
+ output_file = self._file
+ pcap_filter = self._filter
+
+ self._cmd = "tcpdump -p -i %s -w %s \"%s\"" % (interface, output_file,
+ pcap_filter)
+
+ def _execute_tcpdump(self):
+ """ Start tcpdump in the background """
+ cmd = self._cmd
+ self._tcpdump = subprocess.Popen(cmd, shell=True, stdout=None,
+ stderr=None)
+
+ def _run(self):
+ self._compose_cmd()
+ self._execute_tcpdump()
diff --git a/NetTest/NetTestController.py b/NetTest/NetTestController.py
index 5455921..a33cacb 100644
--- a/NetTest/NetTestController.py
+++ b/NetTest/NetTestController.py
@@ -13,6 +13,9 @@ jpirko(a)redhat.com (Jiri Pirko)
import logging
import socket
+import os
+from Common.Logs import Logs
+from Common.SshUtils import scp_from_remote
from pprint import pprint, pformat
from Common.XmlRpc import ServerProxy
from NetTestParse import NetTestParse
@@ -32,6 +35,7 @@ class NetTestController:
self._remoteexec = remoteexec
self._docleanup = cleanup
self._res_serializer = res_serializer
+ self._remote_capture_files = {}
def _get_machineinfo(self, machine_id):
return self._recipe["machines"][machine_id]["info"]
@@ -210,8 +214,31 @@ class NetTestController:
self._cleanup()
return True
- def run_recipe(self):
+ def run_recipe(self, packet_capture=False):
self._prepare()
+
+ if packet_capture:
+ self._start_packet_capture()
+
+ err = None
+ try:
+ res = self._run_recipe()
+ except e:
+ err = e
+
+ if packet_capture:
+ self._stop_packet_capture()
+ self._gather_capture_files()
+
+ if self._docleanup:
+ self._cleanup()
+
+ if not err:
+ return res
+ else:
+ raise err
+
+ def _run_recipe(self):
for sequence in self._recipe["sequences"]:
res = self._run_command_sequence(sequence)
@@ -222,10 +249,43 @@ class NetTestController:
if not res:
break
- if self._docleanup:
- self._cleanup()
return res
+ def _start_packet_capture(self):
+ logging.info("Starting packet capture")
+ for machine_id in self._recipe["machines"]:
+ rpc = self._get_machinerpc(machine_id)
+ capture_files = rpc.start_packet_capture("")
+ self._remote_capture_files[machine_id] = capture_files
+
+ def _stop_packet_capture(self):
+ logging.info("Stopping packet capture")
+ for machine_id in self._recipe["machines"]:
+ rpc = self._get_machinerpc(machine_id)
+ rpc.stop_packet_capture()
+
+ def _gather_capture_files(self):
+ logging_root = Logs.get_logging_root_path()
+ logging_root = os.path.abspath(logging_root)
+ logging.info("Retrieving capture files from slaves")
+ for machine_id in self._recipe["machines"]:
+ hostname = self._recipe["machines"][machine_id]['info']['hostname']
+ rootpass = self._recipe["machines"][machine_id]['info']['rootpass']
+
+ slave_logging_dir = os.path.join(logging_root, hostname)
+ try:
+ os.mkdir(slave_logging_dir)
+ except OSError, e:
+ if e.errno != 17:
+ raise
+
+ capture_files = self._remote_capture_files[machine_id]
+ for remote_path in capture_files:
+ filename = os.path.basename(remote_path)
+ local_path = os.path.join(slave_logging_dir, filename)
+ scp_from_remote(hostname, "22", "root", rootpass,
+ remote_path, local_path)
+
def eval_expression_recipe(self, expr):
self._prepare()
value = eval("self._recipe%s" % expr)
diff --git a/NetTest/NetTestSlave.py b/NetTest/NetTestSlave.py
index 06654be..955c28e 100644
--- a/NetTest/NetTestSlave.py
+++ b/NetTest/NetTestSlave.py
@@ -14,6 +14,8 @@ jpirko(a)redhat.com (Jiri Pirko)
from Common.Logs import Logs
import signal
import select, logging
+import os
+from Common.PacketCapture import PacketCapture
from Common.XmlRpc import Server
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
from NetConfig.NetConfig import NetConfig
@@ -29,6 +31,7 @@ class NetTestSlaveXMLRPC:
'''
def __init__(self):
self._netconfig = None
+ self._packet_captures = {}
def hello(self):
return "hello"
@@ -55,6 +58,34 @@ class NetTestSlaveXMLRPC:
self.__init__()
return True
+ def start_packet_capture(self, filt):
+ logging_dir = Logs.get_logging_root_path()
+ logging_dir = os.path.abspath(logging_dir)
+ netconfig = self._netconfig.dump_config()
+
+ files = []
+ for dev_id, dev_spec in netconfig.iteritems():
+ dump_file = os.path.join(logging_dir, "%s.pcap" % dev_id)
+ files.append(dump_file)
+
+ pcap = PacketCapture()
+ pcap.set_interface(dev_spec["name"])
+ pcap.set_output_file(dump_file)
+ pcap.set_filter(filt)
+ pcap.start()
+
+ self._packet_captures[dev_id] = pcap
+
+ return files
+
+ def stop_packet_capture(self):
+ netconfig = self._netconfig.dump_config()
+ for dev_id in netconfig.keys():
+ pcap = self._packet_captures[dev_id]
+ pcap.stop()
+
+ return True
+
def run_command(self, command):
try:
return NetTestCommand(command).run()
diff --git a/nettestctl.py b/nettestctl.py
index ec99d73..f1511df 100755
--- a/nettestctl.py
+++ b/nettestctl.py
@@ -32,6 +32,9 @@ def usage():
print "ACTION = [run | dump | all_dump | config_only | eval EXPR]"
print ""
print " -d, --debug emit debugging messages"
+ print " -p, --packet_capture capture and log all ongoing\n" \
+ " network communication during\n" \
+ " the test"
print " -h, --help print this message"
print " -r, --recipe=FILE use this net test recipe"
print " -e, --remoteexec transfer and execute\n" \
@@ -41,13 +44,14 @@ def usage():
print " -x, --result=FILE file to write xml_result"
sys.exit()
-def process_recipe(args, file_path, remoteexec, cleanup, res_serializer):
+def process_recipe(args, file_path, remoteexec, cleanup,
+ res_serializer, packet_capture):
nettestctl = NetTestController(os.path.realpath(file_path),
remoteexec=remoteexec, cleanup=cleanup,
res_serializer=res_serializer)
action = args[0]
if action == "run":
- return nettestctl.run_recipe()
+ return nettestctl.run_recipe(packet_capture)
elif action == "dump":
return nettestctl.dump_recipe()
elif action == "all_dump":
@@ -73,12 +77,15 @@ def print_summary(summary):
logging.info("*%s* %s" % (res, recipe_file))
logging.info("=====================================================")
-def get_recipe_result(args, file_path, remoteexec, cleanup, res_serializer):
+def get_recipe_result(args, file_path, remoteexec, cleanup,
+ res_serializer, packet_capture):
res_serializer.add_recipe(file_path)
Logs.set_logging_root_path(file_path)
- loggingServer = LoggingServer(LoggingServer.DEFAULT_PORT, Logs.root_path, Logs.debug)
+ loggingServer = LoggingServer(LoggingServer.DEFAULT_PORT,
+ Logs.root_path, Logs.debug)
loggingServer.start()
- res = process_recipe(args, file_path, remoteexec, cleanup, res_serializer)
+ res = process_recipe(args, file_path, remoteexec, cleanup,
+ res_serializer, packet_capture)
loggingServer.stop()
return ((file_path, res))
@@ -89,8 +96,9 @@ def main():
try:
opts, args = getopt.getopt(
sys.argv[1:],
- "dhr:ecx:",
- ["debug", "help", "recipe=", "remoteexec", "cleanup", "result"]
+ "dhr:ecx:p",
+ ["debug", "help", "recipe=", "remoteexec", "cleanup", "result=",
+ "packet_capture"]
)
except getopt.GetoptError, err:
print str(err)
@@ -102,6 +110,7 @@ def main():
remoteexec = False
cleanup = False
result_path = None
+ packet_capture = False
for opt, arg in opts:
if opt in ("-d", "--debug"):
debug += 1
@@ -115,6 +124,9 @@ def main():
cleanup = True
elif opt in ("-x", "--result"):
result_path = arg
+ elif opt in ("-p", "--packet_capture"):
+ packet_capture = True
+
Logs(debug)
@@ -140,13 +152,14 @@ def main():
recipe_file = os.path.join(recipe_path, f)
if re.match(r'^.*\.xml$', recipe_file):
logging.info("Processing recipe file \"%s\"" % recipe_file)
- summary.append(get_recipe_result(args, recipe_file,
- remoteexec, cleanup,
- res_serializer))
+ summary.append(get_recipe_result(args, recipe_file, remoteexec,
+ cleanup, res_serializer,
+ packet_capture))
Logs.set_logging_root_path(clean=False)
else:
- summary.append(get_recipe_result(args, recipe_path,
- remoteexec, cleanup, res_serializer))
+ summary.append(get_recipe_result(args, recipe_path, remoteexec,
+ cleanup, res_serializer,
+ packet_capture))
Logs.set_logging_root_path(clean=False)
print_summary(summary)