[lnst] XmlPreprocessor: Modify template detection
by Jiří Pírko
commit 2dfa60872151f2d245d589ef362885fcc77b9032
Author: Radek Pazdera <rpazdera(a)redhat.com>
Date: Fri Jun 8 12:42:04 2012 +0200
XmlPreprocessor: Modify template detection
The previous solution considered curly braces to be reserved
characters, exclusively for marking templates. But according
to recent development, it will be necessary to deal with
those characters also in non-template context.
This commit enhances template detection code so it will ignore
any occurencies of curly braces unless they form a valid template.
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
Common/XmlPreprocessor.py | 37 ++++++++++++++-----------------------
1 files changed, 14 insertions(+), 23 deletions(-)
---
diff --git a/Common/XmlPreprocessor.py b/Common/XmlPreprocessor.py
index b20fec8..be2cab2 100644
--- a/Common/XmlPreprocessor.py
+++ b/Common/XmlPreprocessor.py
@@ -31,9 +31,8 @@ class XmlPreprocessor:
This class serves as template processor within a XML DOM tree object.
"""
- _template_re = "\{([^\{\}]+)\}"
- _alias_re = "^\$([a-zA-Z0-9_]+)(\[.+\])*$"
- _func_re = "^([a-zA-Z0-9_]+)\(([^\(\)]*)\)$"
+ _alias_re = "\{\$([a-zA-Z0-9_]+)(\[.+\])*\}"
+ _func_re = "\{([a-zA-Z0-9_]+)\(([^\(\)]*)\)\}"
def __init__(self):
self._definitions = {}
@@ -97,31 +96,23 @@ class XmlPreprocessor:
def _expand_string(self, string):
while True:
- template_match = re.search(self._template_re, string)
- if template_match:
- template_string = template_match.group(0)
- template = template_match.group(1)
- template_result = self._process_template(template)
+ alias_match = re.search(self._alias_re, string)
+ func_match = re.search(self._func_re, string)
- string = string.replace(template_string, template_result)
+ result = None
+
+ if alias_match:
+ template = alias_match.group(0)
+ result = self._process_alias_template(template)
+ elif func_match:
+ template = func_match.group(0)
+ result = self._process_func_template(template)
else:
break
- return string
-
- def _process_template(self, string):
- string = string.strip()
- result = None
+ string = string.replace(template, result)
- if re.match(self._alias_re, string):
- result = self._process_alias_template(string)
- return result
-
- if re.match(self._func_re, string):
- result = self._process_func_template(string)
- return result
-
- raise XmlTemplateError("Unknown template type '%s'" % string)
+ return string
def _process_alias_template(self, string):
result = None
11 years, 2 months
Parser Progress
by Radek Pazdera
Hi!
This will be another annoyingly lengthy email from me :-).
I worked on the parser and I'm finally getting some results. I have a
demo implementation which can parse only some simple recipes.
The center of it is XmlParser, which is a base class with the necessary
helper functions, that handle proper resolving of included parts, processing
aliases and template functions.
The actual parser code can be split among multiple parser classes. In this
example case, I used NetTestParse as the root recipe parser, MachineParse
for parsing netmachineconfig and nettestconfig and CommandParse for parsing
commands. All these parsers have a reference to recipe dictionary to which
they store the values acquired during parsing - so the recipe is always
up to date.
XmlParser objects are connected into a sort-of-a tree. For instance
NetTestParse is processing a XML and when it finds <machine> tag, it creates
an instance of MachineParse and let's it take care for the whole tag.
MachineParse could create some more subparsers if it wanted to, etc.
Now, how exactly the parsing works. The code consists of element-handlers.
Each element handler is a function/method with the following prototype:
def _element_handler(node, params)
where node is the DOM node object and params is a dictionary with any
optional parameters. The root handler of each parser is the parse() method
of XmlParser class.
What a handler does is totally up to the programmer, but the usual actions
are:
1. Process the element
- check for attributes
- acquire and check/convert attribute values
- store the values to recipe dictionary
- execute some actions tied to this particular element
2. Process child elements
For processing child elements there's a predefined helper method in
XmlParser
called _process_child_elements().
def _process_child_elements(self, node, scheme)
It takes two args, the current node and a scheme. Scheme is a dictionary
mapping tag names to element-handler functions. The scheme defines what tags
are expected and therefore allowed children of this tag. For instance
handler
function for <nettestrecipe> tag looks like this:
def _nettestrecipe(self, node, params):
scheme = {"machines": self._machines,
"command_sequence": self._command_sequence}
self._process_child_nodes(node, scheme)
There is no processing tied to the tag itself, so the function only defines
the scheme dict for children and processes them. Here is another example
for the <info> tag:
def _info(self, node, params):
info = self._recipe["machines"][self._id]["info"] = {}
info["hostname"] = self._get_attribute(node, "hostname")
info["rootpass"] = self._get_attribute(node, "rootpass")
Here we store some info directly into the recipe variable and we also don't
go any further into the tree, because info tag should not have any children
(in case it does, they're ignored).
The XmlParser has some predefined handlers for <define> tag, so the later
parsers doesn't need to worry about that at all.
Another feature I added to the parser is the ability to report line+column
numbers in error messages. So in case something goes wrong, you'll know
exactly where it is.
There's one question about the actions tied to the parsing. Should parser
be able to send RPC requests to slaves or create interfaces in libvirt?
I though about this and I think that our situation is comparable with
syntax-driven compilation. Our parser needs to perform some additional
checks (and requests) outside of its area (same as semantic checks that
are performed by the parser in syntax-driven compilers).
What do you think about this approach?
I like it because it keeps the current basic structure of the code, so we'll
be able to reuse lots of the existing code from the current
NetTestParser and
NetConfigParser. It should also blend nicely to the other parts of the
system
as well (to NetConfig and NetTestController).
Here's the full prototype implementation:
https://url.corp.redhat.com/fd868a1
We definitely need to discuss this in person sometime when we all return
from PTO, so I'm stopping the work on this until we discuss it.
Radek
11 years, 3 months
LNST on RHEL5
by Jan Tluka
For everyone who still runs LNST on RHEL5.
Besides the python-ctypes requirement I've hit another issue:
<cut>
27/06 12:18:50| (127.0.0.1) NetTestSlave:0095| ERROR:
Traceback (most recent call last):
File "/root/lnst/NetTest/NetTestSlave.py", line 91, in run_command
return NetTestCommand(command).run()
File "/root/lnst/NetTest/NetTestCommand.py", line 224, in __init__
self._command_class = get_command_class(command)
File "/root/lnst/NetTest/NetTestCommand.py", line 209, in
get_command_class
return NetTestCommandTest(command)
File "/root/lnst/NetTest/NetTestCommand.py", line 65, in
NetTestCommandTest
fp, pathname, description)
File "/root/lnst/Tests/TestIperf.py", line 86
except OSError as e:
^
SyntaxError: invalid syntax
</cut>
The reason is that RHEL5 comes with python version 2.4 and 'as' keyword
is not known to python2.4.
I was able to workaround it with following steps:
1. install python2.6 from epel [1]
2. run nettestslave with following command line:
# PYTHON_PATH=/usr/lib64/python2.6 /usr/bin/python26 ./nettestslave.py -d
Or another way is to modify TestIperf code to use:
except OSError, e:
Hth, Jan
[1] http://fedoraproject.org/wiki/EPEL
11 years, 3 months
[lnst] Use modinfo instead of deprecated modprobe
by Jiří Pírko
commit 745ad9442c448e5f165f5168ac0e99acf77ba132
Author: Jan Tluka <jtluka(a)redhat.com>
Date: Wed Jun 27 16:02:25 2012 +0200
Use modinfo instead of deprecated modprobe
Since modprobe -l is deprecated and modinfo should be used instead
I made the update to type_check method.
I have tested it on RHEL5 and RHEL6. On RHEL5 I've hit another issue
on macvlan check. The module is not available on RHEL5 and the slave
instance failed due to die_on_err=True (default). I changed this to
False since we might have cases when the module for a network device
is not available.
NetConfig/NetConfigDevice.py | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
---
diff --git a/NetConfig/NetConfigDevice.py b/NetConfig/NetConfigDevice.py
index be8e673..8e89c36 100644
--- a/NetConfig/NetConfigDevice.py
+++ b/NetConfig/NetConfigDevice.py
@@ -68,7 +68,7 @@ class NetConfigDeviceGeneric:
@classmethod
def type_check(self):
if self._modulename:
- output = exec_cmd("modprobe -l %s" % self._modulename)[0]
+ output = exec_cmd("modinfo -F filename %s" % self._modulename, die_on_err=False)[0]
for line in output.split("\n"):
if re.match(r'^.*\/%s\.ko$' % self._modulename, line):
return True
11 years, 3 months
[lnst] NetTestController: Adding missing cleanup
by Jiří Pírko
commit 791fd48e6a95a10dfef60ac442f2a34f225e672a
Author: Radek Pazdera <rpazdera(a)redhat.com>
Date: Fri Jun 8 12:19:26 2012 +0200
NetTestController: Adding missing cleanup
Call to _cleanup() was added to 'all_dump' and 'config_only' actions, so
the slaves are properly deconfigured when '-c' argument is present.
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
NetTest/NetTestController.py | 7 ++++++-
1 files changed, 6 insertions(+), 1 deletions(-)
---
diff --git a/NetTest/NetTestController.py b/NetTest/NetTestController.py
index 8f46bcb..5455921 100644
--- a/NetTest/NetTestController.py
+++ b/NetTest/NetTestController.py
@@ -200,10 +200,14 @@ class NetTestController:
def all_dump_recipe(self):
self._prepare()
pprint(self._recipe)
+ if self._docleanup:
+ self._cleanup()
return True
def config_only_recipe(self):
self._prepare()
+ if self._docleanup:
+ self._cleanup()
return True
def run_recipe(self):
@@ -218,7 +222,8 @@ class NetTestController:
if not res:
break
- self._cleanup()
+ if self._docleanup:
+ self._cleanup()
return res
def eval_expression_recipe(self, expr):
11 years, 3 months
[PATCH] Use modinfo instead of deprecated modprobe
by Jan Tluka
Since modprobe -l is deprecated and modinfo should be used instead
I made the update to type_check method.
I have tested it on RHEL5 and RHEL6. On RHEL5 I've hit another issue
on macvlan check. The module is not available on RHEL5 and the slave
instance failed due to die_on_err=True (default). I changed this to
False since we might have cases when the module for a network device
is not available.
---
NetConfig/NetConfigDevice.py | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/NetConfig/NetConfigDevice.py b/NetConfig/NetConfigDevice.py
index be8e673..8e89c36 100644
--- a/NetConfig/NetConfigDevice.py
+++ b/NetConfig/NetConfigDevice.py
@@ -68,7 +68,7 @@ class NetConfigDeviceGeneric:
@classmethod
def type_check(self):
if self._modulename:
- output = exec_cmd("modprobe -l %s" % self._modulename)[0]
+ output = exec_cmd("modinfo -F filename %s" % self._modulename, die_on_err=False)[0]
for line in output.split("\n"):
if re.match(r'^.*\/%s\.ko$' % self._modulename, line):
return True
--
1.7.6.5
11 years, 3 months
Recipe Parsing
by Radek Pazdera
Hi,
I'm thinking about the parser implementation that would suit best to
LNST's current status (with all the changes so far) and I think I'm
finally starting to get it.
The change introduced in the new parsing approach should be, the way
the parser processes each tag. In the current implementation, tags are
processed in order that is defined by the parser. It's usually
achieved like this:
tag1_grp = parent.getElementsByTagName("tag1")
self._parse(tag1_grp)
tag2_grp = parent.getElementsByTagName("tag2")
self._parse(tag2_grp)
This is a problem for aliases and functions, because some tags could
have been processed in a wrong order or left out completely.
For instance:
<parent>
<tag1 />
<tag1 />
<tag2 />
<tag1 />
</parent>
In this case, the above code will process the last "tag1" before
processing "tag2".
In the new parsing approach, we should focus on parsing the tags in
order which is defined by the document. For example:
for node in parent.childNodes:
if node.nodeType != node.Element:
raise ParsingError("Only tags allowed here")
if node.nodeName == "tag1":
self._parse(node)
elif node.nodeName == "tag2":
self._parse(node)
else:
raise ParsingError("%s not allowed here" % self.NodeName)
This would be achievable with keeping the current parser structure
(the parser is split into multiple objects - which makes the code
look better). We could create a base XmlParser class that would
implement some basic parsing API and also take care of the aliases,
functions and includes.
There would be a top level NetTestParser (as it is now) that would
parse the basic structure and then call NetConfigParser for configs,
NetTestCommandParser for commands etc.
Each parser will hold a reference to a "recipe" dictionary and update
it as it parses the XML. The NetConfigParser also will be able to
contact slaves through RPC and validate MAC's / device names.
This approach will allow us to make the aliases definitions local
within the parent tag. Which makes a lot of sense. Now they're global
only across the whole recipe (and even the includes).
<parent attr="{$abc}">
<define>
<alias name="abc" value="5" />
</define>
<child attr="{$abc}" />
</parent>
The first $abc would be undefined, in the current implementation on
the other hand, it would resolve to 5.
Another problem is with aliases within includes. The includes are
preprocessed even before the aliases are resolved, so currently
<tag source="{$tag_src}" />
will not resolve even in case the alias has been defined correctly. The
sequential approach would solve this as well.
Radek
11 years, 3 months
[lnst] SshUtils: Adding quotes to scp paths
by Jiří Pírko
commit 882ca5c36f1db3e213dbcd5983d30ebf2c928e6b
Author: Radek Pazdera <rpazdera(a)redhat.com>
Date: Thu Jun 21 12:15:42 2012 +0200
SshUtils: Adding quotes to scp paths
There was a problem with scp commands that occured when a path
containing a space was passed to any scp functions. This commit
fixes that by enclosing paths within scp commands in single
qoutes (').
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
Common/SshUtils.py | 6 +++---
1 files changed, 3 insertions(+), 3 deletions(-)
---
diff --git a/Common/SshUtils.py b/Common/SshUtils.py
index e306976..340f119 100644
--- a/Common/SshUtils.py
+++ b/Common/SshUtils.py
@@ -362,7 +362,7 @@ def scp_to_remote(host, port, username, password, local_path, remote_path,
@raise: Whatever remote_scp() raises
"""
command = ("scp -v -o UserKnownHostsFile=/dev/null "
- " -r -P %s %s %s@%s:%s" % #-o PreferredAuthentications=password
+ " -r -P %s '%s' '%s@%s:%s'" % #-o PreferredAuthentications=password
(port, local_path, username, host, remote_path))
password_list = []
password_list.append(password)
@@ -386,7 +386,7 @@ def scp_from_remote(host, port, username, password, remote_path, local_path,
@raise: Whatever remote_scp() raises
"""
command = ("scp -v -o UserKnownHostsFile=/dev/null "
- "-o PreferredAuthentications=password -r -P %s %s@%s:%s %s" %
+ "-o PreferredAuthentications=password -r -P %s '%s@%s:%s' '%s'" %
(port, username, host, remote_path, local_path))
password_list = []
password_list.append(password)
@@ -411,7 +411,7 @@ def scp_between_remotes(src, dst, port, s_passwd, d_passwd, s_name, d_name,
@return: True on success and False on failure.
"""
command = ("scp -v -o UserKnownHostsFile=/dev/null -o "
- "PreferredAuthentications=password -r -P %s %s@%s:%s %s@%s:%s" %
+ "PreferredAuthentications=password -r -P %s '%s@%s:%s' '%s@%s:%s'" %
(port, s_name, src, s_path, d_name, dst, d_path))
password_list = []
password_list.append(s_passwd)
11 years, 3 months
[PATCH] SshUtils: Adding quotes to scp paths
by Radek Pazdera
From: Radek Pazdera <rpazdera(a)redhat.com>
There was a problem with scp commands that occured when a path
containing a space was passed to any scp functions. This commit
fixes that by enclosing paths within scp commands in single
qoutes (').
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
---
Common/SshUtils.py | 6 +++---
1 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Common/SshUtils.py b/Common/SshUtils.py
index e306976..340f119 100644
--- a/Common/SshUtils.py
+++ b/Common/SshUtils.py
@@ -362,7 +362,7 @@ def scp_to_remote(host, port, username, password, local_path, remote_path,
@raise: Whatever remote_scp() raises
"""
command = ("scp -v -o UserKnownHostsFile=/dev/null "
- " -r -P %s %s %s@%s:%s" % #-o PreferredAuthentications=password
+ " -r -P %s '%s' '%s@%s:%s'" % #-o PreferredAuthentications=password
(port, local_path, username, host, remote_path))
password_list = []
password_list.append(password)
@@ -386,7 +386,7 @@ def scp_from_remote(host, port, username, password, remote_path, local_path,
@raise: Whatever remote_scp() raises
"""
command = ("scp -v -o UserKnownHostsFile=/dev/null "
- "-o PreferredAuthentications=password -r -P %s %s@%s:%s %s" %
+ "-o PreferredAuthentications=password -r -P %s '%s@%s:%s' '%s'" %
(port, username, host, remote_path, local_path))
password_list = []
password_list.append(password)
@@ -411,7 +411,7 @@ def scp_between_remotes(src, dst, port, s_passwd, d_passwd, s_name, d_name,
@return: True on success and False on failure.
"""
command = ("scp -v -o UserKnownHostsFile=/dev/null -o "
- "PreferredAuthentications=password -r -P %s %s@%s:%s %s@%s:%s" %
+ "PreferredAuthentications=password -r -P %s '%s@%s:%s' '%s@%s:%s'" %
(port, s_name, src, s_path, d_name, dst, d_path))
password_list = []
password_list.append(s_passwd)
--
1.7.7.6
11 years, 3 months
[lnst] nettestctl: New CLI option --packet_capture (-p)
by Jiří Pírko
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)
11 years, 3 months