IP -> device name translation
by Radek Pazdera
I looked at the IP address to device translation function
(what we discussed earlier).
I found out, that the translation is already implemented
in LNST, device names are resolved and conveniently
available from in the recipe. They can be accessed
through recipe_eval or the equivalent alias:
{$recipe['machines'][1]['netconfig'][1]['name']}
This can be used together with the new system_config
as follows:
<command type="system_config"
option="/proc/sys/net/ipv4/config/{$recipe['machines'][1]['netconfig'][1]['name']}/force_igmp_version"
value="2" machine_id="1" />
The only problem with this is, that it can get pretty long.
We cannot break it down onto multiple lines, because
it's a path and also multi-line tag attributes could cause
some trouble.
I'm thinking, what if we added some sort of "predefined
aliases" for such things as IP/MAC/device name for
each host? It could be done as functions too. For
instance
{dev_name(1, 1)} ~ {$recipe['machines'][1]['netconfig'][1]['name']}
{ip(host_id, if_id)} for ip addresses
{mac(host_id, if_id)} for MAC's
The goal is to make referencing those values as short as
possible (especially referencing IP addresses is used a lot).
Radek
11 years, 10 months
[PATCH v2] NetTest: Adding new command type 'system_config'
by Radek Pazdera
From: Radek Pazdera <rpazdera(a)redhat.com>
This commit introduces a new command type 'system_config', that can be
used in the command sequences. Using this command you can change system
configuration through /proc and /sys filesystems.
There are two different syntax options:
a) inline
<command type="system_config" option="/proc/sys/net/option"
value="2" machine_id="1" />
b) multiline
<command type="system_config" machine_id="1">
<options>
<option name="/proc/sys/net/option" value="1" />
<option name="/sys/class/net/option" value="2" />
</options>
</command>
You can set multiple options at once using the multiline version.
LNST does all the error checking and it also saves the original values
and restores them upon finishing each command sequence. For instance
<command_sequence>
<command type="system_config" ... />
...
<!-- Cleanup occurs HERE -->
</command_sequence>
<!-- System is configured with default values when it
executes the following sequence -->
<command_sequence source="max_groups.xml" />
The command also has a bool option that makes the setting permanent
and the former value is not restored. For instance
<command type="system_config" option="/proc/sys/net/option"
value="2" persistent="true" machine_id="1" />
<command type="system_config" persistent="1" machine_id="1">
<options>
<option name="/proc/sys/net/option" value="1" />
<option name="/sys/class/net/option" value="2" />
</options>
</command>
When omitted, persistent is set to "false" by default (values are
restored).
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
---
NetTest/NetTestCommand.py | 45 +++++++++++++++++++++++++++++++++
NetTest/NetTestController.py | 56 +++++++++++++++++++++++++++++++++++++++---
NetTest/NetTestParse.py | 36 ++++++++++++++++++++++++---
3 files changed, 129 insertions(+), 8 deletions(-)
diff --git a/NetTest/NetTestCommand.py b/NetTest/NetTestCommand.py
index ca28abf..1dbd2a2 100644
--- a/NetTest/NetTestCommand.py
+++ b/NetTest/NetTestCommand.py
@@ -111,6 +111,49 @@ class NetTestCommandExec(NetTestCommandGeneric):
else:
self.set_fail("Command failed to execute")
+class NetTestCommandSystemConfig(NetTestCommandGeneric):
+ def _retrive_option(self, option):
+ cmd_str = "cat %s" % option
+ (stdout, stderr) = exec_cmd(cmd_str)
+ return stdout.strip()
+
+ def _set_option(self, option, value):
+ cmd_str = "echo \"%s\" >%s" % (value, option)
+ (stdout, stderr) = exec_cmd(cmd_str)
+
+ def run(self):
+ res_data = {}
+
+ # inline version
+ if "option" in self._command:
+ opt = self._command["option"]
+ val = [{"value": self._command["value"]}]
+ self._command["options"] = {opt: val}
+
+ for option, values in self._command["options"].iteritems():
+ new_val = values[0]["value"]
+
+ option_abspath = os.path.abspath(option)
+ if option_abspath[0:5] != "/sys/" and \
+ option_abspath[0:6] != "/proc/":
+ err = "Wrong config option %s. Only /proc or /sys paths are allowed." % option
+ self.set_fail(err)
+ return
+
+ try:
+ prev_val = self._retrive_option(option)
+ self._set_option(option, new_val)
+ except ExecCmdFail:
+ self.set_fail("Unable to set %s config option!" % option)
+ return
+
+ res_data[option] = {"current_val": new_val,
+ "previous_val": prev_val}
+
+ res = {"passed": True}
+ res["res_data"] = res_data
+ self.set_result(res)
+
class BgProcessException(Exception):
"""Base class for client errors."""
def __init__(self, str):
@@ -170,6 +213,8 @@ def get_command_class(command):
return NetTestCommandIntr(command)
elif cmd_type == "kill":
return NetTestCommandKill(command)
+ elif cmd_type == "system_config":
+ return NetTestCommandSystemConfig(command)
else:
logging.error("Unknown comamnd type \"%s\"" % cmd_type)
raise Exception("Unknown command type \"%s\"" % cmd_type)
diff --git a/NetTest/NetTestController.py b/NetTest/NetTestController.py
index f2efbaa..12a871d 100644
--- a/NetTest/NetTestController.py
+++ b/NetTest/NetTestController.py
@@ -58,6 +58,7 @@ class NetTestController:
"nettestslave.py")
session.add_kill_handler(self._session_die)
info["session"] = session
+ info["system_config"] = {}
def _cleanup_slaves(self):
for machine_id in self._recipe["machines"]:
@@ -163,10 +164,19 @@ class NetTestController:
if "timeout" in command:
logging.debug("Setting socket timeout to default value")
socket.setdefaulttimeout(None)
+
+ if command["type"] == "system_config":
+ if cmd_res["passed"]:
+ self._update_system_config(machine_id, cmd_res["res_data"],
+ command["persistent"])
+ else:
+ err = "Error occured while setting system configuration (%s)" \
+ % cmd_res["err_msg"]
+ logging.error(err)
+
return cmd_res
- def _run_command_sequence(self):
- sequence = self._recipe["sequence"]
+ def _run_command_sequence(self, sequence):
for command in sequence:
logging.info("Executing command: [%s]" % str_command(command))
cmd_res = self._run_command(command)
@@ -198,12 +208,50 @@ class NetTestController:
def run_recipe(self):
self._prepare()
- res = self._run_command_sequence()
+ for sequence in self._recipe["sequences"]:
+ res = self._run_command_sequence(sequence)
+
+ for machine_id in self._recipe["machines"]:
+ self._restore_system_config(machine_id)
+
+ # stop when sequence fails
+ if not res:
+ break
+
self._cleanup()
return res
def eval_expression_recipe(self, expr):
self._prepare()
value = eval("self._recipe%s" % expr)
- print "Evaluated expression \"%s\": \"%s\"" % (expr, value)
return True
+
+ def _update_system_config(self, machine_id, res_data, persistent=False):
+ info = self._get_machineinfo(machine_id)
+ system_config = info["system_config"]
+ for option, values in res_data.iteritems():
+ if persistent:
+ if option in system_config:
+ del system_config[option]
+ else:
+ if not option in system_config:
+ system_config[option] = {"initial_val": values["previous_val"]}
+ system_config[option]["current_val"] = values["current_val"]
+
+
+ def _restore_system_config(self, machine_id):
+ info = self._get_machineinfo(machine_id)
+ system_config = info["system_config"]
+
+ if len(system_config) > 0:
+ command = {}
+ command["machine_id"] = machine_id
+ command["type"] = "system_config"
+ command["value"] = ""
+ command["options"] = {}
+ command["persistent"] = True
+ for option, values in system_config.iteritems():
+ command["options"][option] = [{"value": values["initial_val"]}]
+
+ self._run_command_sequence([command])
+ info["system_config"] = {}
diff --git a/NetTest/NetTestParse.py b/NetTest/NetTestParse.py
index c1493b5..69833b1 100644
--- a/NetTest/NetTestParse.py
+++ b/NetTest/NetTestParse.py
@@ -128,11 +128,27 @@ class NetTestParse:
try:
return str(eval("self._recipe%s" % eval_data))
except (KeyError, IndexError):
- print self._recipe
logging.error("Wrong recipe_eval value \"%s\" passed"
% eval_data)
raise Exception
+ @classmethod
+ def _int_it(cls, val):
+ try:
+ num = int(val)
+ except ValueError:
+ num = 0
+ return num
+
+ @classmethod
+ def _bool_it(cls, val):
+ if isinstance(val, str):
+ if re.match("^\s*(?i)(true)", val):
+ return True
+ elif re.match("^\s*(?i)(false)", val):
+ return False
+ return True if cls._int_it(val) else False
+
def _parse_command_option(self, dom_option, options):
logging.debug("Parsing command option")
option_type = str(dom_option.getAttribute("type"))
@@ -185,6 +201,17 @@ class NetTestParse:
command["desc"] = str(tmp)
logging.debug("Parsed command: [%s]" % str_command(command))
+ if cmd_type == "system_config":
+ tmp = dom_command.getAttribute("option")
+ if tmp:
+ command["option"] = str(tmp)
+
+ tmp = dom_command.getAttribute("persistent")
+ if tmp:
+ command["persistent"] = self._bool_it(tmp)
+ else:
+ command["persistent"] = False
+
dom_options_grp = dom_command.getElementsByTagName("options")
options = {}
for dom_options_item in dom_options_grp:
@@ -233,17 +260,18 @@ class NetTestParse:
raise WrongCommandSequenceException
def parse_recipe_command_sequence(self):
- sequence = []
dom_sequences = self._dom_nettestrecipe.getElementsByTagName("command_sequence")
self._expand_group(dom_sequences, recipe_eval=True)
+ self._recipe["sequences"] = []
for dom_sequence in dom_sequences:
+ sequence = []
dom_commands = dom_sequence.getElementsByTagName("command")
for dom_command in dom_commands:
sequence.append(self._parse_command(dom_command))
- self._check_sequence(sequence)
- self._recipe["sequence"] = sequence
+ self._check_sequence(sequence)
+ self._recipe["sequences"].append(sequence)
def _expand(self, node, recipe_eval=False):
if node.nodeType == node.ELEMENT_NODE:
--
1.7.7.6
11 years, 10 months
[lnst] NetTest: Adding new command type 'system_config'
by Jiří Pírko
commit 223c8e2d97f75ebb967db0944e45de4864a83411
Author: Radek Pazdera <rpazdera(a)redhat.com>
Date: Tue May 22 13:26:58 2012 +0200
NetTest: Adding new command type 'system_config'
This commit introduces a new command type 'system_config', that can be
used in the command sequences. Using this command you can change system
configuration through /proc and /sys filesystems.
There are two different syntax options:
a) inline
<command type="system_config" option="/proc/sys/net/option"
value="2" machine_id="1" />
b) multiline
<command type="system_config" machine_id="1">
<options>
<option name="/proc/sys/net/option" value="1" />
<option name="/sys/class/net/option" value="2" />
</options>
</command>
You can set multiple options at once using the multiline version.
LNST does all the error checking and it also saves the original values
and restores them upon finishing each command sequence. For instance
<command_sequence>
<command type="system_config" ... />
...
<!-- Cleanup occurs HERE -->
</command_sequence>
<!-- System is configured with default values when it
executes the following sequence -->
<command_sequence source="max_groups.xml" />
The command also has a bool option that makes the setting permanent
and the former value is not restored. For instance
<command type="system_config" option="/proc/sys/net/option"
value="2" persistent="true" machine_id="1" />
<command type="system_config" persistent="1" machine_id="1">
<options>
<option name="/proc/sys/net/option" value="1" />
<option name="/sys/class/net/option" value="2" />
</options>
</command>
When omitted, persistent is set to "false" by default (values are
restored).
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
NetTest/NetTestCommand.py | 45 +++++++++++++++++++++++++++++++++
NetTest/NetTestController.py | 56 +++++++++++++++++++++++++++++++++++++++---
NetTest/NetTestParse.py | 36 ++++++++++++++++++++++++---
3 files changed, 129 insertions(+), 8 deletions(-)
---
diff --git a/NetTest/NetTestCommand.py b/NetTest/NetTestCommand.py
index ca28abf..1dbd2a2 100644
--- a/NetTest/NetTestCommand.py
+++ b/NetTest/NetTestCommand.py
@@ -111,6 +111,49 @@ class NetTestCommandExec(NetTestCommandGeneric):
else:
self.set_fail("Command failed to execute")
+class NetTestCommandSystemConfig(NetTestCommandGeneric):
+ def _retrive_option(self, option):
+ cmd_str = "cat %s" % option
+ (stdout, stderr) = exec_cmd(cmd_str)
+ return stdout.strip()
+
+ def _set_option(self, option, value):
+ cmd_str = "echo \"%s\" >%s" % (value, option)
+ (stdout, stderr) = exec_cmd(cmd_str)
+
+ def run(self):
+ res_data = {}
+
+ # inline version
+ if "option" in self._command:
+ opt = self._command["option"]
+ val = [{"value": self._command["value"]}]
+ self._command["options"] = {opt: val}
+
+ for option, values in self._command["options"].iteritems():
+ new_val = values[0]["value"]
+
+ option_abspath = os.path.abspath(option)
+ if option_abspath[0:5] != "/sys/" and \
+ option_abspath[0:6] != "/proc/":
+ err = "Wrong config option %s. Only /proc or /sys paths are allowed." % option
+ self.set_fail(err)
+ return
+
+ try:
+ prev_val = self._retrive_option(option)
+ self._set_option(option, new_val)
+ except ExecCmdFail:
+ self.set_fail("Unable to set %s config option!" % option)
+ return
+
+ res_data[option] = {"current_val": new_val,
+ "previous_val": prev_val}
+
+ res = {"passed": True}
+ res["res_data"] = res_data
+ self.set_result(res)
+
class BgProcessException(Exception):
"""Base class for client errors."""
def __init__(self, str):
@@ -170,6 +213,8 @@ def get_command_class(command):
return NetTestCommandIntr(command)
elif cmd_type == "kill":
return NetTestCommandKill(command)
+ elif cmd_type == "system_config":
+ return NetTestCommandSystemConfig(command)
else:
logging.error("Unknown comamnd type \"%s\"" % cmd_type)
raise Exception("Unknown command type \"%s\"" % cmd_type)
diff --git a/NetTest/NetTestController.py b/NetTest/NetTestController.py
index f2efbaa..12a871d 100644
--- a/NetTest/NetTestController.py
+++ b/NetTest/NetTestController.py
@@ -58,6 +58,7 @@ class NetTestController:
"nettestslave.py")
session.add_kill_handler(self._session_die)
info["session"] = session
+ info["system_config"] = {}
def _cleanup_slaves(self):
for machine_id in self._recipe["machines"]:
@@ -163,10 +164,19 @@ class NetTestController:
if "timeout" in command:
logging.debug("Setting socket timeout to default value")
socket.setdefaulttimeout(None)
+
+ if command["type"] == "system_config":
+ if cmd_res["passed"]:
+ self._update_system_config(machine_id, cmd_res["res_data"],
+ command["persistent"])
+ else:
+ err = "Error occured while setting system configuration (%s)" \
+ % cmd_res["err_msg"]
+ logging.error(err)
+
return cmd_res
- def _run_command_sequence(self):
- sequence = self._recipe["sequence"]
+ def _run_command_sequence(self, sequence):
for command in sequence:
logging.info("Executing command: [%s]" % str_command(command))
cmd_res = self._run_command(command)
@@ -198,12 +208,50 @@ class NetTestController:
def run_recipe(self):
self._prepare()
- res = self._run_command_sequence()
+ for sequence in self._recipe["sequences"]:
+ res = self._run_command_sequence(sequence)
+
+ for machine_id in self._recipe["machines"]:
+ self._restore_system_config(machine_id)
+
+ # stop when sequence fails
+ if not res:
+ break
+
self._cleanup()
return res
def eval_expression_recipe(self, expr):
self._prepare()
value = eval("self._recipe%s" % expr)
- print "Evaluated expression \"%s\": \"%s\"" % (expr, value)
return True
+
+ def _update_system_config(self, machine_id, res_data, persistent=False):
+ info = self._get_machineinfo(machine_id)
+ system_config = info["system_config"]
+ for option, values in res_data.iteritems():
+ if persistent:
+ if option in system_config:
+ del system_config[option]
+ else:
+ if not option in system_config:
+ system_config[option] = {"initial_val": values["previous_val"]}
+ system_config[option]["current_val"] = values["current_val"]
+
+
+ def _restore_system_config(self, machine_id):
+ info = self._get_machineinfo(machine_id)
+ system_config = info["system_config"]
+
+ if len(system_config) > 0:
+ command = {}
+ command["machine_id"] = machine_id
+ command["type"] = "system_config"
+ command["value"] = ""
+ command["options"] = {}
+ command["persistent"] = True
+ for option, values in system_config.iteritems():
+ command["options"][option] = [{"value": values["initial_val"]}]
+
+ self._run_command_sequence([command])
+ info["system_config"] = {}
diff --git a/NetTest/NetTestParse.py b/NetTest/NetTestParse.py
index c1493b5..69833b1 100644
--- a/NetTest/NetTestParse.py
+++ b/NetTest/NetTestParse.py
@@ -128,11 +128,27 @@ class NetTestParse:
try:
return str(eval("self._recipe%s" % eval_data))
except (KeyError, IndexError):
- print self._recipe
logging.error("Wrong recipe_eval value \"%s\" passed"
% eval_data)
raise Exception
+ @classmethod
+ def _int_it(cls, val):
+ try:
+ num = int(val)
+ except ValueError:
+ num = 0
+ return num
+
+ @classmethod
+ def _bool_it(cls, val):
+ if isinstance(val, str):
+ if re.match("^\s*(?i)(true)", val):
+ return True
+ elif re.match("^\s*(?i)(false)", val):
+ return False
+ return True if cls._int_it(val) else False
+
def _parse_command_option(self, dom_option, options):
logging.debug("Parsing command option")
option_type = str(dom_option.getAttribute("type"))
@@ -185,6 +201,17 @@ class NetTestParse:
command["desc"] = str(tmp)
logging.debug("Parsed command: [%s]" % str_command(command))
+ if cmd_type == "system_config":
+ tmp = dom_command.getAttribute("option")
+ if tmp:
+ command["option"] = str(tmp)
+
+ tmp = dom_command.getAttribute("persistent")
+ if tmp:
+ command["persistent"] = self._bool_it(tmp)
+ else:
+ command["persistent"] = False
+
dom_options_grp = dom_command.getElementsByTagName("options")
options = {}
for dom_options_item in dom_options_grp:
@@ -233,17 +260,18 @@ class NetTestParse:
raise WrongCommandSequenceException
def parse_recipe_command_sequence(self):
- sequence = []
dom_sequences = self._dom_nettestrecipe.getElementsByTagName("command_sequence")
self._expand_group(dom_sequences, recipe_eval=True)
+ self._recipe["sequences"] = []
for dom_sequence in dom_sequences:
+ sequence = []
dom_commands = dom_sequence.getElementsByTagName("command")
for dom_command in dom_commands:
sequence.append(self._parse_command(dom_command))
- self._check_sequence(sequence)
- self._recipe["sequence"] = sequence
+ self._check_sequence(sequence)
+ self._recipe["sequences"].append(sequence)
def _expand(self, node, recipe_eval=False):
if node.nodeType == node.ELEMENT_NODE:
11 years, 10 months
LNST libvirt integration proposal
by Radek Pazdera
Hi,
I have another idea for a feature to LNST. I'm using it together with
libvirt and I
thought that it could be nice to have direct control over the network
topology
from LNST. When using libvirt for testing, this could be easily
achievable through
the virsh command.
Now, when you're running some tests, you have to set up the required network
topology by yourself and put MAC and IP addresses into the recipe. When you
want to run the test on a different VM (with different version of
kernel/RHEL etc),
you need to set up the topology again by hand and also alter the recipe.
I thought we could make LNST to be able to control this setup. Virtually
all the
configuration of libvirt's networks and domains are accessible through XML,
so they can be easily parsed and modified.
There could be a pool of installed VM's available on the controller.
LNST could detect
available machines (virsh list --all), modify the number of interfaces
they have
(virsh dumpxml/define), define some networks (bridges/tap devices -
virsh net-edit/net-define)
and connect them into some topology. Everything would be controlled from
within
the recipe XML file. At the end LNST would boot up the machines and run
the tests.
Some of those machines could act as switches (with openvswitch) or even
routers so
we could build a whole virtual network.
What do you think?
Could this approach be useful?
Radek :)
11 years, 10 months
[PATCH] NetTest: Adding new command type 'system_config'
by Radek Pazdera
From: Radek Pazdera <rpazdera(a)redhat.com>
This commit introduces a new command type 'system_config', that can be
used in the command sequences. Using this command you can change system
configuration through /proc and /sys filesystems.
There are two different syntax options:
a) inline
<command type="system_config" option="/proc/sys/net/option"
value="2" machine_id="1" />
b) multiline
<command type="system_config" machine_id="1">
<options>
<option name="/proc/sys/net/option" value="1" />
<option name="/sys/class/net/option" value="2" />
</options>
</command>
You can set multiple options at once using the multiline version.
LNST does all the error checking and it also saves the original values
and restores them upon finishing each command sequence. For instance
<command_sequence>
<command type="system_config" ... />
...
<!-- Cleanup occurs HERE -->
</command_sequence>
<!-- System is configured with default values when it
executes the following sequence -->
<command_sequence source="max_groups.xml" />
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
---
NetTest/NetTestCommand.py | 45 +++++++++++++++++++++++++++++++++++++++
NetTest/NetTestController.py | 48 +++++++++++++++++++++++++++++++++++++++--
NetTest/NetTestParse.py | 12 +++++++--
3 files changed, 99 insertions(+), 6 deletions(-)
diff --git a/NetTest/NetTestCommand.py b/NetTest/NetTestCommand.py
index ca28abf..1dbd2a2 100644
--- a/NetTest/NetTestCommand.py
+++ b/NetTest/NetTestCommand.py
@@ -111,6 +111,49 @@ class NetTestCommandExec(NetTestCommandGeneric):
else:
self.set_fail("Command failed to execute")
+class NetTestCommandSystemConfig(NetTestCommandGeneric):
+ def _retrive_option(self, option):
+ cmd_str = "cat %s" % option
+ (stdout, stderr) = exec_cmd(cmd_str)
+ return stdout.strip()
+
+ def _set_option(self, option, value):
+ cmd_str = "echo \"%s\" >%s" % (value, option)
+ (stdout, stderr) = exec_cmd(cmd_str)
+
+ def run(self):
+ res_data = {}
+
+ # inline version
+ if "option" in self._command:
+ opt = self._command["option"]
+ val = [{"value": self._command["value"]}]
+ self._command["options"] = {opt: val}
+
+ for option, values in self._command["options"].iteritems():
+ new_val = values[0]["value"]
+
+ option_abspath = os.path.abspath(option)
+ if option_abspath[0:5] != "/sys/" and \
+ option_abspath[0:6] != "/proc/":
+ err = "Wrong config option %s. Only /proc or /sys paths are allowed." % option
+ self.set_fail(err)
+ return
+
+ try:
+ prev_val = self._retrive_option(option)
+ self._set_option(option, new_val)
+ except ExecCmdFail:
+ self.set_fail("Unable to set %s config option!" % option)
+ return
+
+ res_data[option] = {"current_val": new_val,
+ "previous_val": prev_val}
+
+ res = {"passed": True}
+ res["res_data"] = res_data
+ self.set_result(res)
+
class BgProcessException(Exception):
"""Base class for client errors."""
def __init__(self, str):
@@ -170,6 +213,8 @@ def get_command_class(command):
return NetTestCommandIntr(command)
elif cmd_type == "kill":
return NetTestCommandKill(command)
+ elif cmd_type == "system_config":
+ return NetTestCommandSystemConfig(command)
else:
logging.error("Unknown comamnd type \"%s\"" % cmd_type)
raise Exception("Unknown command type \"%s\"" % cmd_type)
diff --git a/NetTest/NetTestController.py b/NetTest/NetTestController.py
index f2efbaa..b76bd5f 100644
--- a/NetTest/NetTestController.py
+++ b/NetTest/NetTestController.py
@@ -58,6 +58,7 @@ class NetTestController:
"nettestslave.py")
session.add_kill_handler(self._session_die)
info["session"] = session
+ info["system_config"] = {}
def _cleanup_slaves(self):
for machine_id in self._recipe["machines"]:
@@ -163,10 +164,18 @@ class NetTestController:
if "timeout" in command:
logging.debug("Setting socket timeout to default value")
socket.setdefaulttimeout(None)
+
+ if command["type"] == "system_config":
+ if cmd_res["passed"]:
+ self._update_system_config(machine_id, cmd_res["res_data"])
+ else:
+ err = "Error occured while setting system configuration (%s)" \
+ % cmd_res["err_msg"]
+ logging.error(err)
+
return cmd_res
- def _run_command_sequence(self):
- sequence = self._recipe["sequence"]
+ def _run_command_sequence(self, sequence):
for command in sequence:
logging.info("Executing command: [%s]" % str_command(command))
cmd_res = self._run_command(command)
@@ -198,7 +207,16 @@ class NetTestController:
def run_recipe(self):
self._prepare()
- res = self._run_command_sequence()
+ for sequence in self._recipe["sequences"]:
+ res = self._run_command_sequence(sequence)
+
+ for machine_id in self._recipe["machines"]:
+ self._restore_system_config(machine_id)
+
+ # stop when sequence fails
+ if not res:
+ break
+
self._cleanup()
return res
@@ -207,3 +225,27 @@ class NetTestController:
value = eval("self._recipe%s" % expr)
print "Evaluated expression \"%s\": \"%s\"" % (expr, value)
return True
+
+ def _update_system_config(self, machine_id, res_data):
+ info = self._get_machineinfo(machine_id)
+ system_config = info["system_config"]
+ for option, values in res_data.iteritems():
+ if not option in system_config:
+ system_config[option] = {"initial_val": values["previous_val"]}
+ system_config[option]["current_val"] = values["current_val"]
+
+ def _restore_system_config(self, machine_id):
+ info = self._get_machineinfo(machine_id)
+ system_config = info["system_config"]
+
+ if len(system_config) > 0:
+ command = {}
+ command["machine_id"] = machine_id
+ command["type"] = "system_config"
+ command["value"] = ""
+ command["options"] = {}
+ for option, values in system_config.iteritems():
+ command["options"][option] = [{"value": values["initial_val"]}]
+
+ self._run_command_sequence([command])
+ info["system_config"] = {}
diff --git a/NetTest/NetTestParse.py b/NetTest/NetTestParse.py
index c1493b5..6b20d22 100644
--- a/NetTest/NetTestParse.py
+++ b/NetTest/NetTestParse.py
@@ -185,6 +185,11 @@ class NetTestParse:
command["desc"] = str(tmp)
logging.debug("Parsed command: [%s]" % str_command(command))
+ if cmd_type == "system_config":
+ tmp = dom_command.getAttribute("option")
+ if tmp:
+ command["option"] = str(tmp)
+
dom_options_grp = dom_command.getElementsByTagName("options")
options = {}
for dom_options_item in dom_options_grp:
@@ -233,17 +238,18 @@ class NetTestParse:
raise WrongCommandSequenceException
def parse_recipe_command_sequence(self):
- sequence = []
dom_sequences = self._dom_nettestrecipe.getElementsByTagName("command_sequence")
self._expand_group(dom_sequences, recipe_eval=True)
+ self._recipe["sequences"] = []
for dom_sequence in dom_sequences:
+ sequence = []
dom_commands = dom_sequence.getElementsByTagName("command")
for dom_command in dom_commands:
sequence.append(self._parse_command(dom_command))
- self._check_sequence(sequence)
- self._recipe["sequence"] = sequence
+ self._check_sequence(sequence)
+ self._recipe["sequences"].append(sequence)
def _expand(self, node, recipe_eval=False):
if node.nodeType == node.ELEMENT_NODE:
--
1.7.7.6
11 years, 10 months
[lnst] Adding documentation for LNST framework.
by Jiří Pírko
commit 6961b4fb124c38908453d17899718403ed18ee7f
Author: Jan Tluka <jtluka(a)redhat.com>
Date: Thu May 3 17:18:02 2012 +0200
Adding documentation for LNST framework.
This is quite up-to-date documentation about the LNST. It has been
around for quite a long time but thanks to Radek we have the new bits
documented as well.
Documentation/LNSTIntro.html | 535 +++++++++++++++++++++
Documentation/README | 24 +
Documentation/controller-slave-communication.dia | Bin 0 -> 3304 bytes
Documentation/lnst_intro_images.sh | 15 +
Documentation/machineconfig-netconfig-mapping.dia | Bin 0 -> 3745 bytes
Documentation/real-hardware-setup.dia | Bin 0 -> 2925 bytes
6 files changed, 574 insertions(+), 0 deletions(-)
---
diff --git a/Documentation/LNSTIntro.html b/Documentation/LNSTIntro.html
new file mode 100644
index 0000000..227b560
--- /dev/null
+++ b/Documentation/LNSTIntro.html
@@ -0,0 +1,535 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>LNST - Linux Network Stack Test</title>
+ <meta name="generator" content="Amaya, see http://www.w3.org/Amaya/" />
+</head>
+
+<body>
+<h1>LNST Project</h1>
+
+<p></p>
+
+<h2>About</h2>
+
+<p>LNST is an automated testing framework focused on the Linux network stack
+testing. It supports bonding, vlan, bridging and macvlan.</p>
+
+<h3><em>People</em></h3>
+<ul>
+ <li>Jiří Pírko (jpirko) – author of the project, kernel developer</li>
+ <li>Jiří Župka (jzupka) – contributor, framework infrastructure implementation,
+ quality engineer</li>
+ <li>Jan Tluka (jtluka) – contributor, test development, Beaker
+ integration, quality engineer</li>
+ <li>Radek Pazdera - contributor, framework infrastructure implementation, test development, quality engineer
+</ul>
+
+<h3><em>Communication channels</em></h3>
+
+<ul>
+ <li>We are on irc: #lnst @ freenode (irc://irc.freenode.net#lnst)</li>
+ <li>The development mailing list: <a href="https://fedorahosted.org/mailman/listinfo/lnst-developers">https://fedorahosted.org/mailman/listinfo/lnst-developers</a></li>
+ </ul>
+
+
+<h3><em>Goals</em></h3>
+<ul>
+ <li>QE: extend our current network code coverage in Tier tests</li>
+ <li>Devel: create a tool/test environment to easily catch regressions in
+ network stack during development</li>
+</ul>
+
+<h3><em>Target audience</em></h3>
+<ul>
+ <li>developers (to catch regressions)</li>
+ <li>quality engineers (to develop new tests)</li>
+</ul>
+
+<h3><em>Topology</em></h3>
+
+<p>In the following picture you can see an example of the network topology
+along with the roles and communication paths. There are 3 basic entities, </p>
+<ul>
+ <li>controller </li>
+ <li>test machines</li>
+ <li>network infrastructure (network switches)</li>
+</ul>
+
+<p></p>
+
+<p style="text-align:left;margin-left:0;margin-right:auto;"><img
+alt="Roles and communication paths" src="real-hardware-setup.png"
+style="display: block; text-align: center; margin-left: auto; margin-right: auto"
+width="712" height="279" /></p>
+
+<p></p>
+
+<p>In the picture you can see two network connections drawn in different
+colors. </p>
+
+<p>The green one is the <strong>controller path</strong> and is used to setup
+network interfaces on machine1 and machine2 through the <strong>controller
+interfaces</strong> (eth3 on both test machines). The <strong>controller
+path</strong> is also used to do setup of the test switch and live changes on
+it such as vlan and bonding configuration, port disconnection, etc.</p>
+
+<p>The red one is the <strong>test path</strong> and is used for network
+traffic generated in tests executed on test machines.</p>
+
+<p></p>
+<hr />
+
+<h2>Getting the source</h2>
+
+<p>You can access the sources at the following urls:</p>
+<ul>
+ <li><a
+ href="http://git.fedorahosted.org/git/?p=lnst.git">http://git.fedorahosted.org/git/?p=lnst.git</a></li>
+</ul>
+
+<p>In your comand line checkout the code:</p>
+<pre>$ git clone git://git.fedorahosted.org/lnst.git</pre>
+
+<p></p>
+<hr />
+
+<h2>Source code structure</h2>
+
+<p></p>
+<pre>[./]
+ netconfig.py (tool to configure network interfaces based on xml description)
+ nettestctl.py (tool to control remote machines/execute tests)
+ nettestslave.py (app that runs on remote machine and
+ accepts commands from controller (nettestctl.py))
+ switchconfig.py
+ swswitch.py
+
+[./Common] (common code for the framework)
+ Daemon.py
+ ExecCmd.py
+ LoggingServer.py
+ Logs.py
+ ProcessManager.py
+ ShellProcess.py
+ SlaveUtils.py
+ SshUtils.py
+ TestsCommon.py
+ Utils.py
+ XmlRpc.py
+
+[./example_recipes] (inspiration for test setups – netconfigs, machineconfigs, recipes)
+
+[./NetConfig] (network configuration class implementation)
+ NetConfigCommon.py
+ NetConfigDevice.py
+ NetConfigDevNames.py
+ NetConfigParse.py (netconfig xml parser)
+ NetConfig.py
+
+[./NetTest] (test execution class implementation)
+ NetTestCommand.py
+ NetTestController.py
+ NetTestParse.py
+ NetTestParse.pyc
+ NetTestResultSerializer.py
+ NetTestSlave.py
+
+[./Switch] (network switch configuration)
+ SwitchConfigParse.py
+ SwitchCtl.py
+ SwitchDriversCommon.py
+
+[./Tests] (implementation of network tests)
+ TestDummyFailing.py
+ TestIcmp6Ping.py
+ TestIcmpPing.py
+ TestIperf.py
+ TestNetCat.py
+ TestPktCounter.py
+ TestPktgenTx.py
+</pre>
+
+<p></p>
+<hr />
+
+<h2>Setting up the test environmnent</h2>
+
+<h3><em>Remote connection setup</em></h3>
+
+<p>Every test machine has to be configured to allow remote SSH connection. This
+can be achieved either using keys or specifying root password in XML files
+(described below). It's mandatory to have a dedicated control network interface
+for the SSH connection that is used to setup network on additional network
+interfaces and execute tests. It is also important to separate "controller
+path" and "test path" infrastructure, e.g. using two switches.</p>
+
+<p></p>
+
+<h3><em>Required packages</em></h3>
+
+<p>Basically you need to have python installed. If you use RHEL5 on a machine
+you need to install additional package <strong>python-ctypes</strong> from EPEL
+repository on it.</p>
+<ul>
+ <li><a
+ href="http://fedoraproject.org/wiki/EPEL">http://fedoraproject.org/wiki/EPEL</a></li>
+ <li>rpm -ivh <a
+ href="http://download.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch...">http://download.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch...</a></li>
+ <li><a
+ href="http://download.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch..."></a>yum
+ install python-ctypes</li>
+</ul>
+
+<p></p>
+<hr />
+
+<h2>LNST Recipes</h2>
+
+<p>The LNST recipes contains all information to do a test run.</p>
+
+<p>It consists of:</p>
+<ul>
+ <li><span style="background-color:#ffc0cb">machineconfigs</span></li>
+ <li><span style="background-color:#90ee90">netconfigs</span></li>
+ <li><span style="background-color:#add8e6">command sequences</span></li>
+</ul>
+
+<p>The following XML code is an example of the LNST recipe:</p>
+<pre><nettestrecipe>
+ <machines>
+ <machine id="1">
+ <span style="background-color:#ffc0cb"><netmachineconfig source="example_recipes/machine_configs/config-f14peanut.xml"/></span>
+ <span style="background-color:#90ee90"><netconfig source="example_recipes/net_configs/netconfig1.xml"/></span>
+ </machine>
+ <machine id="2">
+ <span style="background-color:#ffc0cb"><netmachineconfig source="my_recipes/config-f15.xml"/></span>
+ <span style="background-color:#90ee90"><netconfig source="my_recipes/netconfig2.xml"/></span>
+ </machine>
+ </machines>
+
+ <span style="background-color:#add8e6"><command_sequence></span>
+ <span style="background-color:#add8e6"><command type="exec" value="sleep 4"/></span></pre>
+<pre> <span style="background-color:#add8e6"><command type="test" machine_id="1" value="IcmpPing" timeout="30"></span>
+ <span style="background-color:#add8e6"><options></span>
+ <span style="background-color:#add8e6"><option type="recipe_eval" name="addr" value="['machines'][2]['netconfig'][1]['addresses'][0]"/></span>
+ <span style="background-color:#add8e6"><option name="count" value="40"/></span>
+ <span style="background-color:#add8e6"><option name="interval" value="0.2"/></span>
+ <span style="background-color:#add8e6"><option name="limit_rate" value="95"/></span>
+ <span style="background-color:#add8e6"></options></span>
+ <span style="background-color:#add8e6"></command></span>
+ <span style="background-color:#add8e6"></command_sequence></span>
+</nettestrecipe></pre>
+
+<p></p>
+
+<p>Every test machine's network setup is defined by two configuration XML
+snippets – MachineConfig describing the machine's real hardware (available
+NICs) and NetConfig describing configuration of these devices (IP addresses,
+bonding setup, bridging, vlans, etc.)</p>
+
+<h3><em>MachineConfig</em></h3>
+
+<p>The MachineConfig</p>
+<ul>
+ <li>describes test machine's control interface and login information (for
+ root access) - <strong>info</strong> tag</li>
+ <li>describes available network interfaces of a test machine –
+ <strong>netdevice</strong> tags</li>
+</ul>
+
+<p>Example (<em>example_recipes/machine_configs/config-f14peanut.xml</em>):</p>
+
+<p></p>
+<pre><netmachineconfig>
+ <info hostname="10.34.37.141" rootpass="aaa"/>
+ <netdevice type="eth" <span style="background-color:#00ff00">phys_id="1"</span> hwaddr="00:E0:4C:14:2E:5D"/>
+ <netdevice type="eth" <span style="background-color:#00ff00">phys_id="2"</span> hwaddr="00:30:4F:7F:FD:30"/>
+</netmachineconfig></pre>
+
+<p></p>
+
+<p>Test machine with this configuration will be accessible via IP address
+10.34.37.141 using root password 'aaa' (<strong>rootpass</strong>). Two
+interfaces are made available for testing - one with MAC address
+00:E0:4C:14:2E:5D (<strong>hwaddr</strong>) exported as physical device '1'
+(<strong>phys_id</strong>) and second with MAC address 00:30:4F:7F:FD:30
+exported as physical device '2' in LNST framework.</p>
+
+<p></p>
+
+<h3><em>NetConfig</em></h3>
+
+<p>Example (<em>example_recipes/net_configs/netconfig1.xml</em>):</p>
+<pre><netconfig>
+ <netdevice id="1" type="eth" <span style="background-color:#00ff00">phys_id="1"</span>/>
+ <netdevice id="2" type="eth" <span style="background-color:#00ff00">phys_id="2"</span>/>
+ <netdevice id="3" <span style="background-color:#add8e6">type="bond"</span>>
+ <options>
+ <option name="mode" value="1"/>
+ <option name="miimon" value="100"/>
+ <option name="primary" value="2"/>
+ </options>
+ <slaves>
+ <slave id="1"/>
+ <slave id="2"/>
+ </slaves>
+ <addresses>
+ <address value="192.168.101.1/24"/>
+ </addresses>
+ </netdevice>
+</netconfig></pre>
+
+<p></p>
+
+<p>This configuration snippet uses two physical devices
+(<strong>phys_id</strong>="1" and <strong>phys_id</strong>="2") defined in the
+previous MachineConfig. It also defines third network device (<strong>netdevice
+id="3"</strong>) as <strong>bond</strong> device and adds the two physical
+device as its <strong>slaves</strong>. Further options are defined in
+<strong>options</strong> element and IP address of the bond interface is
+defined in <strong>addresses</strong> element.</p>
+
+<p></p>
+
+<h3><em>Mapping of physical interfaces inside LNST recipes</em></h3>
+
+<p></p>
+
+<p><img alt="machineconfig-netconfig-mapping"
+src="machineconfig-netconfig-mapping.png" width="793" height="318" /></p>
+
+<p></p>
+
+<h3><em>Defining aliases</em></h3>
+<p>LNST allows you to define arbitrary <strong>aliases</strong> and use
+them to access certain values from the whole recipe file while the value
+itself is included in the file only once. Definition of an alias occurs
+in the <code><define></code> tag anywhere in the document:</p>
+<pre>
+<define>
+ <span style="background-color:#90ee90"><alias name="ip_addr" value="192.168.0.1" /></span>
+ <span style="background-color:#add8e6"><alias name="mask" value="24" /></span>
+</define>
+</pre>
+
+<p>Defined alias can be referenced from any element's attribute or text.
+For instance:</p>
+<pre>
+<netdevice id="2" type="eth">
+ <addresses>
+ <address value="<span style="background-color:#90ee90">{$ip_addr}</span>/<span style="background-color:#add8e6">{$mask}</span>"/>
+ </addresses>
+</netdevice>
+</pre>
+
+<h3><em>Command sequences</em></h3>
+
+<p>Command sequence in a LNST recipe is a list of commands that is executed
+on test machines or controller. </p>
+
+<p>Tasks can be of type:</p>
+<ul>
+ <li><strong>exec</strong> or</li>
+ <li><strong>test </strong>(can be run on test machines only)</li>
+</ul>
+
+<p>The <strong>exec</strong> command is anything that you can enter on a
+command line. E.g. </p>
+<ul>
+ <li>yum install tcpdump</li>
+ <li>echo 1 > /proc/net/ipv4/ip_forwarding</li>
+</ul>
+
+<p></p>
+
+<p>The <strong>test</strong> command is an implemented test. The code of the
+test is present in Tests/Test<strong>IcmpPing</strong>.py in the example below.
+You can pass variables to a test using <strong>option</strong> tag. The options
+value can be obtained using <strong>get_opt()</strong> method. For further
+details see section <strong>Writing tests</strong> or look at code examples
+under <strong>Tests</strong> directory.</p>
+
+<p></p>
+
+<p>Example of a command sequence:</p>
+<pre><command_sequence>
+ <command type="exec" value="sleep 4"/>
+ <command type="test" machine_id="1" value="IcmpPing" timeout="30">
+ <options>
+ <option name="addr" value="<span style="background-color:#ffc0cb">{$recipe['machines'][2]['netconfig'][1]['addresses'][0]}</span>" />
+ <option name="count" value="40"/>
+ <option name="interval" value="0.2"/>
+ <option name="limit_rate" value="95"/>
+ </options>
+ </command>
+</command_sequence></pre>
+
+<p></p>
+
+<p>There are two commands in this example. </p>
+
+<p>First one would execute command sleep on the controller machine because
+attribute <strong>machine_id</strong> is not supplied (default behavior). The
+<strong>machine_id</strong> attributes defines on which of the test machines
+command is run. It's value matches one of the machine ids defined in
+<strong><machine></strong> tag in LNST recipe.</p>
+
+<p>The second command would start <strong>IcmpPing</strong> test from the
+LNST library on the first (<strong>id=1</strong>) test machine. </p>
+
+<p>You can also access the configuration of machines and their interfaces from
+the command sequence through a special alias <span style="background-color:#ffc0cb"><strong>{$recipe}</strong></span>. In the
+previous example, addr option of IcmpPing test will be set to <em>first</em>
+assigned address from netconfig of <em>first</em> device on <em>second</em>
+machine.
+</p>
+
+<h3><em>Splitting recipes into multiple files</em></h3>
+<p>
+LNST also offers a possibility of splitting the recipe into several
+files. This can be achieved by supplying <strong>source</strong> argument
+to an arbitrary tag. The contents of that tag then will be loaded from the
+file specified in the attribute's value. For instance, the following
+machine configuration will be loaded from file <em>peanut.xml</em>:
+</p>
+<pre>
+<machine <span style="background-color:#90ee90">source="machine_configs/peanut.xml"</span> />
+</pre>
+Example (<em>peanut.xml</em>):
+<pre>
+<machine id="1">
+ <netmachineconfig <span style="background-color:#add8e6">source="example_recipes/machine_configs/config-f14peanut.xml"</span> />
+ <netconfig <span style="background-color:#add8e6">source="example_recipes/net_configs/netconfig1.xml"</span> />
+</machine>
+</pre>
+<p>Note that parts of the included machine config are again external.</p>
+
+<hr />
+
+<h2>Writing a test</h2>
+
+<p>[TODO]</p>
+
+<p></p>
+<hr />
+
+<h2>Running in virtual environment</h2>
+
+<p></p>
+<ul>
+ <li>good for developing tests</li>
+ <li>easily reboot a panicked machine (virsh destroy <domain>; virsh
+ start <domain></li>
+</ul>
+
+<p>Following is an example of libvirt xmls (reduced to network snippets only).
+MAC addresses marked with green color should be specified in the relevant
+machine configs. Don't forget to add dedicated control network interface that
+has to stay up during the whole testing. The configuration below expects that
+virt guests are accessible via host's network interface therefore they're set
+up as <strong>type='bridge'</strong>.</p>
+<pre><domain type='kvm'>
+ <name>rhel5.6-snap1</name>
+ ...
+ <devices>
+ ...
+ <interface type='bridge'> <!-- dedicated control interface (therefore bridged) -->
+ <mac address='52:54:00:cc:f1:88'/>
+ <source bridge='br0'/>
+ <model type='virtio'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
+ </interface>
+ <interface type='network'>
+ <mac address='<span style="background-color:#ffffff"></span><span style="background-color:#00ff00">52:54:00:3d:3e:9b</span>'/>
+ <source network='default'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
+ </interface>
+ <interface type='network'>
+ <mac address='<span style="background-color:#00ff00">52:54:00:44:1a:fb</span>'/>
+ <source network='default'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>
+ </interface>
+ ...
+ </devices>
+</domain>
+
+
+<domain type='kvm'>
+ <name>rhel6ga</name>
+ ...</pre>
+<pre> <devices>
+ ...
+ <interface type='bridge'>
+ <mac address='52:54:00:9f:be:73'/>
+ <source bridge='br0'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
+ </interface>
+ <interface type='network'>
+ <mac address='<span style="background-color:#00ff00">52:54:00:2f:cc:e1</span>'/>
+ <source network='default'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
+ </interface>
+ <interface type='network'>
+ <mac address='<span style="background-color:#00ff00">52:54:00:dd:44:f0</span>'/>
+ <source network='default'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>
+ </interface>
+ ...
+ </devices>
+</domain></pre>
+<hr />
+
+<h2>The workflow - running LNST</h2>
+
+<h3><em>Running LNST using remote execution</em></h3>
+
+<p>On the controller machine run following command:</p>
+<pre>> ./nettestctl.py <span style="color:#ff0000">-e</span> -c -r my_recipe.xml run</pre>
+
+<p>The <strong>-e</strong> option of the nettestctl.py command tells the
+controller to make SSH connections to all machines specified in the xml file
+my_recipe.xml and start nettestslave.py processes on these machines. The
+nettestslave.py processes will start listening for XMLRPC connections through
+which the network interfaces will get configured. The <strong>-c</strong>
+option tells the controller to cleanup the test machines before any network
+configuration, that means removal of relevant kernel module (bonding, bridge,
+8021q, etc.).</p>
+
+<h3><em>Running LNST without remote execution</em></h3>
+
+<p>In this case you have to start nettestslave.py processes first on all
+machines that are defined in recipe by yourself. Let's assume there are
+2 machines participating in the test.</p>
+
+<p>On both of these machines you have to run following command:</p>
+<pre>$ ./nettestslave.py [-d]</pre>
+
+<p>If anything goes wrong it is a good practice to pass the debug option
+<strong>-d</strong> because you will get information about established xmlrpc
+connection from the controller and also information about getting the network
+test interfaces ready and possible problems during the setup.</p>
+
+<p>After a successful startup you should get following line on the output:</p>
+<pre>$ ./nettestslave.py
+03/05 12:32:20| (127.0.0.1) nettestslave:0063| INFO: Started</pre>
+
+<p>After that you need to start nettestctl.py on the controller to run the test
+in a recipe:</p>
+<pre>$ ./nettestctl.py -c -r my_recipe.xml run</pre>
+
+<h3><em>Using LNST to configure interfaces only</em></h3>
+
+<p>You can also use LNST to do just the network configuration of the test
+machines. None of the tests inside your recipe file will get executed.</p>
+
+<p>To do this pass <strong>config_only</strong> instead of 'run' argument to
+nettestctl.py script.</p>
+<pre>$ ./nettestctl.py -r my_recipe.xml <span style="color:#ff0000"><span style="background-color:#ffffff">config_only</span></span></pre>
+
+<p></p>
+</body>
+</html>
diff --git a/Documentation/README b/Documentation/README
new file mode 100644
index 0000000..000306a
--- /dev/null
+++ b/Documentation/README
@@ -0,0 +1,24 @@
+This directory contains useful documentation to help you understand how
+Linux Network Stack Test (LNST) works and show you some hints how to start
+using it.
+
+Description of the files in Documentation directory follows.
+
+
+LNSTIntro.html
+--------------
+
+General introduction to the LNST including all concepts required for
+understanding the framework.
+
+IMPORTANT NOTE:
+
+The document contains few interesting pictures that were stored in gzipped XML
+format and conversion to png is needed before viewing them. The pictures were
+created with Dia (program for drawing structured diagrams [1]). We have
+provided you a script named "lnst_intro_images.sh" to help you with the
+conversion. First install the dia package for your distribution and then
+just run the script and you'll be ready to read the document with all the
+fancy pictures.
+
+[1] http://live.gnome.org/Dia
diff --git a/Documentation/controller-slave-communication.dia b/Documentation/controller-slave-communication.dia
new file mode 100644
index 0000000..b33fa28
Binary files /dev/null and b/Documentation/controller-slave-communication.dia differ
diff --git a/Documentation/lnst_intro_images.sh b/Documentation/lnst_intro_images.sh
new file mode 100755
index 0000000..0c584ae
--- /dev/null
+++ b/Documentation/lnst_intro_images.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+FILES="real-hardware-setup machineconfig-netconfig-mapping"
+
+if [ $# -gt 0 ] && [ $1 == "clean" ]; then
+ echo "Removing png files for LNSTIntro"
+ for f in $FILES; do
+ rm -f $f.png
+ done
+else
+ for f in $FILES; do
+ /usr/bin/dia -e $f.png $f.dia
+ done
+fi
+
diff --git a/Documentation/machineconfig-netconfig-mapping.dia b/Documentation/machineconfig-netconfig-mapping.dia
new file mode 100644
index 0000000..17eeca5
Binary files /dev/null and b/Documentation/machineconfig-netconfig-mapping.dia differ
diff --git a/Documentation/real-hardware-setup.dia b/Documentation/real-hardware-setup.dia
new file mode 100644
index 0000000..b049fa7
Binary files /dev/null and b/Documentation/real-hardware-setup.dia differ
11 years, 10 months
[PATCH] Adding documentation for LNST framework.
by Jan Tluka
Hello everyone!
This is quite up-to-date documentation about the LNST. It has been
around for quite a long time but thanks to Radek we have the new bits
documented as well.
---
Documentation/LNSTIntro.html | 535 +++++++++++++++++++++
Documentation/README | 24 +
Documentation/controller-slave-communication.dia | Bin 0 -> 3304 bytes
Documentation/lnst_intro_images.sh | 15 +
Documentation/machineconfig-netconfig-mapping.dia | Bin 0 -> 3745 bytes
Documentation/real-hardware-setup.dia | Bin 0 -> 2925 bytes
6 files changed, 574 insertions(+), 0 deletions(-)
create mode 100644 Documentation/LNSTIntro.html
create mode 100644 Documentation/README
create mode 100644 Documentation/controller-slave-communication.dia
create mode 100755 Documentation/lnst_intro_images.sh
create mode 100644 Documentation/machineconfig-netconfig-mapping.dia
create mode 100644 Documentation/real-hardware-setup.dia
diff --git a/Documentation/LNSTIntro.html b/Documentation/LNSTIntro.html
new file mode 100644
index 0000000..227b560
--- /dev/null
+++ b/Documentation/LNSTIntro.html
@@ -0,0 +1,535 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>LNST - Linux Network Stack Test</title>
+ <meta name="generator" content="Amaya, see http://www.w3.org/Amaya/" />
+</head>
+
+<body>
+<h1>LNST Project</h1>
+
+<p></p>
+
+<h2>About</h2>
+
+<p>LNST is an automated testing framework focused on the Linux network stack
+testing. It supports bonding, vlan, bridging and macvlan.</p>
+
+<h3><em>People</em></h3>
+<ul>
+ <li>Jiří Pírko (jpirko) – author of the project, kernel developer</li>
+ <li>Jiří Župka (jzupka) – contributor, framework infrastructure implementation,
+ quality engineer</li>
+ <li>Jan Tluka (jtluka) – contributor, test development, Beaker
+ integration, quality engineer</li>
+ <li>Radek Pazdera - contributor, framework infrastructure implementation, test development, quality engineer
+</ul>
+
+<h3><em>Communication channels</em></h3>
+
+<ul>
+ <li>We are on irc: #lnst @ freenode (irc://irc.freenode.net#lnst)</li>
+ <li>The development mailing list: <a href="https://fedorahosted.org/mailman/listinfo/lnst-developers">https://fedorahosted.org/mailman/listinfo/lnst-developers</a></li>
+ </ul>
+
+
+<h3><em>Goals</em></h3>
+<ul>
+ <li>QE: extend our current network code coverage in Tier tests</li>
+ <li>Devel: create a tool/test environment to easily catch regressions in
+ network stack during development</li>
+</ul>
+
+<h3><em>Target audience</em></h3>
+<ul>
+ <li>developers (to catch regressions)</li>
+ <li>quality engineers (to develop new tests)</li>
+</ul>
+
+<h3><em>Topology</em></h3>
+
+<p>In the following picture you can see an example of the network topology
+along with the roles and communication paths. There are 3 basic entities, </p>
+<ul>
+ <li>controller </li>
+ <li>test machines</li>
+ <li>network infrastructure (network switches)</li>
+</ul>
+
+<p></p>
+
+<p style="text-align:left;margin-left:0;margin-right:auto;"><img
+alt="Roles and communication paths" src="real-hardware-setup.png"
+style="display: block; text-align: center; margin-left: auto; margin-right: auto"
+width="712" height="279" /></p>
+
+<p></p>
+
+<p>In the picture you can see two network connections drawn in different
+colors. </p>
+
+<p>The green one is the <strong>controller path</strong> and is used to setup
+network interfaces on machine1 and machine2 through the <strong>controller
+interfaces</strong> (eth3 on both test machines). The <strong>controller
+path</strong> is also used to do setup of the test switch and live changes on
+it such as vlan and bonding configuration, port disconnection, etc.</p>
+
+<p>The red one is the <strong>test path</strong> and is used for network
+traffic generated in tests executed on test machines.</p>
+
+<p></p>
+<hr />
+
+<h2>Getting the source</h2>
+
+<p>You can access the sources at the following urls:</p>
+<ul>
+ <li><a
+ href="http://git.fedorahosted.org/git/?p=lnst.git">http://git.fedorahosted.org/git/?p=lnst.git</a></li>
+</ul>
+
+<p>In your comand line checkout the code:</p>
+<pre>$ git clone git://git.fedorahosted.org/lnst.git</pre>
+
+<p></p>
+<hr />
+
+<h2>Source code structure</h2>
+
+<p></p>
+<pre>[./]
+ netconfig.py (tool to configure network interfaces based on xml description)
+ nettestctl.py (tool to control remote machines/execute tests)
+ nettestslave.py (app that runs on remote machine and
+ accepts commands from controller (nettestctl.py))
+ switchconfig.py
+ swswitch.py
+
+[./Common] (common code for the framework)
+ Daemon.py
+ ExecCmd.py
+ LoggingServer.py
+ Logs.py
+ ProcessManager.py
+ ShellProcess.py
+ SlaveUtils.py
+ SshUtils.py
+ TestsCommon.py
+ Utils.py
+ XmlRpc.py
+
+[./example_recipes] (inspiration for test setups – netconfigs, machineconfigs, recipes)
+
+[./NetConfig] (network configuration class implementation)
+ NetConfigCommon.py
+ NetConfigDevice.py
+ NetConfigDevNames.py
+ NetConfigParse.py (netconfig xml parser)
+ NetConfig.py
+
+[./NetTest] (test execution class implementation)
+ NetTestCommand.py
+ NetTestController.py
+ NetTestParse.py
+ NetTestParse.pyc
+ NetTestResultSerializer.py
+ NetTestSlave.py
+
+[./Switch] (network switch configuration)
+ SwitchConfigParse.py
+ SwitchCtl.py
+ SwitchDriversCommon.py
+
+[./Tests] (implementation of network tests)
+ TestDummyFailing.py
+ TestIcmp6Ping.py
+ TestIcmpPing.py
+ TestIperf.py
+ TestNetCat.py
+ TestPktCounter.py
+ TestPktgenTx.py
+</pre>
+
+<p></p>
+<hr />
+
+<h2>Setting up the test environmnent</h2>
+
+<h3><em>Remote connection setup</em></h3>
+
+<p>Every test machine has to be configured to allow remote SSH connection. This
+can be achieved either using keys or specifying root password in XML files
+(described below). It's mandatory to have a dedicated control network interface
+for the SSH connection that is used to setup network on additional network
+interfaces and execute tests. It is also important to separate "controller
+path" and "test path" infrastructure, e.g. using two switches.</p>
+
+<p></p>
+
+<h3><em>Required packages</em></h3>
+
+<p>Basically you need to have python installed. If you use RHEL5 on a machine
+you need to install additional package <strong>python-ctypes</strong> from EPEL
+repository on it.</p>
+<ul>
+ <li><a
+ href="http://fedoraproject.org/wiki/EPEL">http://fedoraproject.org/wiki/EPEL</a></li>
+ <li>rpm -ivh <a
+ href="http://download.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch...">http://download.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch...</a></li>
+ <li><a
+ href="http://download.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch..."></a>yum
+ install python-ctypes</li>
+</ul>
+
+<p></p>
+<hr />
+
+<h2>LNST Recipes</h2>
+
+<p>The LNST recipes contains all information to do a test run.</p>
+
+<p>It consists of:</p>
+<ul>
+ <li><span style="background-color:#ffc0cb">machineconfigs</span></li>
+ <li><span style="background-color:#90ee90">netconfigs</span></li>
+ <li><span style="background-color:#add8e6">command sequences</span></li>
+</ul>
+
+<p>The following XML code is an example of the LNST recipe:</p>
+<pre><nettestrecipe>
+ <machines>
+ <machine id="1">
+ <span style="background-color:#ffc0cb"><netmachineconfig source="example_recipes/machine_configs/config-f14peanut.xml"/></span>
+ <span style="background-color:#90ee90"><netconfig source="example_recipes/net_configs/netconfig1.xml"/></span>
+ </machine>
+ <machine id="2">
+ <span style="background-color:#ffc0cb"><netmachineconfig source="my_recipes/config-f15.xml"/></span>
+ <span style="background-color:#90ee90"><netconfig source="my_recipes/netconfig2.xml"/></span>
+ </machine>
+ </machines>
+
+ <span style="background-color:#add8e6"><command_sequence></span>
+ <span style="background-color:#add8e6"><command type="exec" value="sleep 4"/></span></pre>
+<pre> <span style="background-color:#add8e6"><command type="test" machine_id="1" value="IcmpPing" timeout="30"></span>
+ <span style="background-color:#add8e6"><options></span>
+ <span style="background-color:#add8e6"><option type="recipe_eval" name="addr" value="['machines'][2]['netconfig'][1]['addresses'][0]"/></span>
+ <span style="background-color:#add8e6"><option name="count" value="40"/></span>
+ <span style="background-color:#add8e6"><option name="interval" value="0.2"/></span>
+ <span style="background-color:#add8e6"><option name="limit_rate" value="95"/></span>
+ <span style="background-color:#add8e6"></options></span>
+ <span style="background-color:#add8e6"></command></span>
+ <span style="background-color:#add8e6"></command_sequence></span>
+</nettestrecipe></pre>
+
+<p></p>
+
+<p>Every test machine's network setup is defined by two configuration XML
+snippets – MachineConfig describing the machine's real hardware (available
+NICs) and NetConfig describing configuration of these devices (IP addresses,
+bonding setup, bridging, vlans, etc.)</p>
+
+<h3><em>MachineConfig</em></h3>
+
+<p>The MachineConfig</p>
+<ul>
+ <li>describes test machine's control interface and login information (for
+ root access) - <strong>info</strong> tag</li>
+ <li>describes available network interfaces of a test machine –
+ <strong>netdevice</strong> tags</li>
+</ul>
+
+<p>Example (<em>example_recipes/machine_configs/config-f14peanut.xml</em>):</p>
+
+<p></p>
+<pre><netmachineconfig>
+ <info hostname="10.34.37.141" rootpass="aaa"/>
+ <netdevice type="eth" <span style="background-color:#00ff00">phys_id="1"</span> hwaddr="00:E0:4C:14:2E:5D"/>
+ <netdevice type="eth" <span style="background-color:#00ff00">phys_id="2"</span> hwaddr="00:30:4F:7F:FD:30"/>
+</netmachineconfig></pre>
+
+<p></p>
+
+<p>Test machine with this configuration will be accessible via IP address
+10.34.37.141 using root password 'aaa' (<strong>rootpass</strong>). Two
+interfaces are made available for testing - one with MAC address
+00:E0:4C:14:2E:5D (<strong>hwaddr</strong>) exported as physical device '1'
+(<strong>phys_id</strong>) and second with MAC address 00:30:4F:7F:FD:30
+exported as physical device '2' in LNST framework.</p>
+
+<p></p>
+
+<h3><em>NetConfig</em></h3>
+
+<p>Example (<em>example_recipes/net_configs/netconfig1.xml</em>):</p>
+<pre><netconfig>
+ <netdevice id="1" type="eth" <span style="background-color:#00ff00">phys_id="1"</span>/>
+ <netdevice id="2" type="eth" <span style="background-color:#00ff00">phys_id="2"</span>/>
+ <netdevice id="3" <span style="background-color:#add8e6">type="bond"</span>>
+ <options>
+ <option name="mode" value="1"/>
+ <option name="miimon" value="100"/>
+ <option name="primary" value="2"/>
+ </options>
+ <slaves>
+ <slave id="1"/>
+ <slave id="2"/>
+ </slaves>
+ <addresses>
+ <address value="192.168.101.1/24"/>
+ </addresses>
+ </netdevice>
+</netconfig></pre>
+
+<p></p>
+
+<p>This configuration snippet uses two physical devices
+(<strong>phys_id</strong>="1" and <strong>phys_id</strong>="2") defined in the
+previous MachineConfig. It also defines third network device (<strong>netdevice
+id="3"</strong>) as <strong>bond</strong> device and adds the two physical
+device as its <strong>slaves</strong>. Further options are defined in
+<strong>options</strong> element and IP address of the bond interface is
+defined in <strong>addresses</strong> element.</p>
+
+<p></p>
+
+<h3><em>Mapping of physical interfaces inside LNST recipes</em></h3>
+
+<p></p>
+
+<p><img alt="machineconfig-netconfig-mapping"
+src="machineconfig-netconfig-mapping.png" width="793" height="318" /></p>
+
+<p></p>
+
+<h3><em>Defining aliases</em></h3>
+<p>LNST allows you to define arbitrary <strong>aliases</strong> and use
+them to access certain values from the whole recipe file while the value
+itself is included in the file only once. Definition of an alias occurs
+in the <code><define></code> tag anywhere in the document:</p>
+<pre>
+<define>
+ <span style="background-color:#90ee90"><alias name="ip_addr" value="192.168.0.1" /></span>
+ <span style="background-color:#add8e6"><alias name="mask" value="24" /></span>
+</define>
+</pre>
+
+<p>Defined alias can be referenced from any element's attribute or text.
+For instance:</p>
+<pre>
+<netdevice id="2" type="eth">
+ <addresses>
+ <address value="<span style="background-color:#90ee90">{$ip_addr}</span>/<span style="background-color:#add8e6">{$mask}</span>"/>
+ </addresses>
+</netdevice>
+</pre>
+
+<h3><em>Command sequences</em></h3>
+
+<p>Command sequence in a LNST recipe is a list of commands that is executed
+on test machines or controller. </p>
+
+<p>Tasks can be of type:</p>
+<ul>
+ <li><strong>exec</strong> or</li>
+ <li><strong>test </strong>(can be run on test machines only)</li>
+</ul>
+
+<p>The <strong>exec</strong> command is anything that you can enter on a
+command line. E.g. </p>
+<ul>
+ <li>yum install tcpdump</li>
+ <li>echo 1 > /proc/net/ipv4/ip_forwarding</li>
+</ul>
+
+<p></p>
+
+<p>The <strong>test</strong> command is an implemented test. The code of the
+test is present in Tests/Test<strong>IcmpPing</strong>.py in the example below.
+You can pass variables to a test using <strong>option</strong> tag. The options
+value can be obtained using <strong>get_opt()</strong> method. For further
+details see section <strong>Writing tests</strong> or look at code examples
+under <strong>Tests</strong> directory.</p>
+
+<p></p>
+
+<p>Example of a command sequence:</p>
+<pre><command_sequence>
+ <command type="exec" value="sleep 4"/>
+ <command type="test" machine_id="1" value="IcmpPing" timeout="30">
+ <options>
+ <option name="addr" value="<span style="background-color:#ffc0cb">{$recipe['machines'][2]['netconfig'][1]['addresses'][0]}</span>" />
+ <option name="count" value="40"/>
+ <option name="interval" value="0.2"/>
+ <option name="limit_rate" value="95"/>
+ </options>
+ </command>
+</command_sequence></pre>
+
+<p></p>
+
+<p>There are two commands in this example. </p>
+
+<p>First one would execute command sleep on the controller machine because
+attribute <strong>machine_id</strong> is not supplied (default behavior). The
+<strong>machine_id</strong> attributes defines on which of the test machines
+command is run. It's value matches one of the machine ids defined in
+<strong><machine></strong> tag in LNST recipe.</p>
+
+<p>The second command would start <strong>IcmpPing</strong> test from the
+LNST library on the first (<strong>id=1</strong>) test machine. </p>
+
+<p>You can also access the configuration of machines and their interfaces from
+the command sequence through a special alias <span style="background-color:#ffc0cb"><strong>{$recipe}</strong></span>. In the
+previous example, addr option of IcmpPing test will be set to <em>first</em>
+assigned address from netconfig of <em>first</em> device on <em>second</em>
+machine.
+</p>
+
+<h3><em>Splitting recipes into multiple files</em></h3>
+<p>
+LNST also offers a possibility of splitting the recipe into several
+files. This can be achieved by supplying <strong>source</strong> argument
+to an arbitrary tag. The contents of that tag then will be loaded from the
+file specified in the attribute's value. For instance, the following
+machine configuration will be loaded from file <em>peanut.xml</em>:
+</p>
+<pre>
+<machine <span style="background-color:#90ee90">source="machine_configs/peanut.xml"</span> />
+</pre>
+Example (<em>peanut.xml</em>):
+<pre>
+<machine id="1">
+ <netmachineconfig <span style="background-color:#add8e6">source="example_recipes/machine_configs/config-f14peanut.xml"</span> />
+ <netconfig <span style="background-color:#add8e6">source="example_recipes/net_configs/netconfig1.xml"</span> />
+</machine>
+</pre>
+<p>Note that parts of the included machine config are again external.</p>
+
+<hr />
+
+<h2>Writing a test</h2>
+
+<p>[TODO]</p>
+
+<p></p>
+<hr />
+
+<h2>Running in virtual environment</h2>
+
+<p></p>
+<ul>
+ <li>good for developing tests</li>
+ <li>easily reboot a panicked machine (virsh destroy <domain>; virsh
+ start <domain></li>
+</ul>
+
+<p>Following is an example of libvirt xmls (reduced to network snippets only).
+MAC addresses marked with green color should be specified in the relevant
+machine configs. Don't forget to add dedicated control network interface that
+has to stay up during the whole testing. The configuration below expects that
+virt guests are accessible via host's network interface therefore they're set
+up as <strong>type='bridge'</strong>.</p>
+<pre><domain type='kvm'>
+ <name>rhel5.6-snap1</name>
+ ...
+ <devices>
+ ...
+ <interface type='bridge'> <!-- dedicated control interface (therefore bridged) -->
+ <mac address='52:54:00:cc:f1:88'/>
+ <source bridge='br0'/>
+ <model type='virtio'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
+ </interface>
+ <interface type='network'>
+ <mac address='<span style="background-color:#ffffff"></span><span style="background-color:#00ff00">52:54:00:3d:3e:9b</span>'/>
+ <source network='default'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
+ </interface>
+ <interface type='network'>
+ <mac address='<span style="background-color:#00ff00">52:54:00:44:1a:fb</span>'/>
+ <source network='default'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>
+ </interface>
+ ...
+ </devices>
+</domain>
+
+
+<domain type='kvm'>
+ <name>rhel6ga</name>
+ ...</pre>
+<pre> <devices>
+ ...
+ <interface type='bridge'>
+ <mac address='52:54:00:9f:be:73'/>
+ <source bridge='br0'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
+ </interface>
+ <interface type='network'>
+ <mac address='<span style="background-color:#00ff00">52:54:00:2f:cc:e1</span>'/>
+ <source network='default'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
+ </interface>
+ <interface type='network'>
+ <mac address='<span style="background-color:#00ff00">52:54:00:dd:44:f0</span>'/>
+ <source network='default'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>
+ </interface>
+ ...
+ </devices>
+</domain></pre>
+<hr />
+
+<h2>The workflow - running LNST</h2>
+
+<h3><em>Running LNST using remote execution</em></h3>
+
+<p>On the controller machine run following command:</p>
+<pre>> ./nettestctl.py <span style="color:#ff0000">-e</span> -c -r my_recipe.xml run</pre>
+
+<p>The <strong>-e</strong> option of the nettestctl.py command tells the
+controller to make SSH connections to all machines specified in the xml file
+my_recipe.xml and start nettestslave.py processes on these machines. The
+nettestslave.py processes will start listening for XMLRPC connections through
+which the network interfaces will get configured. The <strong>-c</strong>
+option tells the controller to cleanup the test machines before any network
+configuration, that means removal of relevant kernel module (bonding, bridge,
+8021q, etc.).</p>
+
+<h3><em>Running LNST without remote execution</em></h3>
+
+<p>In this case you have to start nettestslave.py processes first on all
+machines that are defined in recipe by yourself. Let's assume there are
+2 machines participating in the test.</p>
+
+<p>On both of these machines you have to run following command:</p>
+<pre>$ ./nettestslave.py [-d]</pre>
+
+<p>If anything goes wrong it is a good practice to pass the debug option
+<strong>-d</strong> because you will get information about established xmlrpc
+connection from the controller and also information about getting the network
+test interfaces ready and possible problems during the setup.</p>
+
+<p>After a successful startup you should get following line on the output:</p>
+<pre>$ ./nettestslave.py
+03/05 12:32:20| (127.0.0.1) nettestslave:0063| INFO: Started</pre>
+
+<p>After that you need to start nettestctl.py on the controller to run the test
+in a recipe:</p>
+<pre>$ ./nettestctl.py -c -r my_recipe.xml run</pre>
+
+<h3><em>Using LNST to configure interfaces only</em></h3>
+
+<p>You can also use LNST to do just the network configuration of the test
+machines. None of the tests inside your recipe file will get executed.</p>
+
+<p>To do this pass <strong>config_only</strong> instead of 'run' argument to
+nettestctl.py script.</p>
+<pre>$ ./nettestctl.py -r my_recipe.xml <span style="color:#ff0000"><span style="background-color:#ffffff">config_only</span></span></pre>
+
+<p></p>
+</body>
+</html>
diff --git a/Documentation/README b/Documentation/README
new file mode 100644
index 0000000..000306a
--- /dev/null
+++ b/Documentation/README
@@ -0,0 +1,24 @@
+This directory contains useful documentation to help you understand how
+Linux Network Stack Test (LNST) works and show you some hints how to start
+using it.
+
+Description of the files in Documentation directory follows.
+
+
+LNSTIntro.html
+--------------
+
+General introduction to the LNST including all concepts required for
+understanding the framework.
+
+IMPORTANT NOTE:
+
+The document contains few interesting pictures that were stored in gzipped XML
+format and conversion to png is needed before viewing them. The pictures were
+created with Dia (program for drawing structured diagrams [1]). We have
+provided you a script named "lnst_intro_images.sh" to help you with the
+conversion. First install the dia package for your distribution and then
+just run the script and you'll be ready to read the document with all the
+fancy pictures.
+
+[1] http://live.gnome.org/Dia
diff --git a/Documentation/controller-slave-communication.dia b/Documentation/controller-slave-communication.dia
new file mode 100644
index 0000000000000000000000000000000000000000..b33fa28a735e80358032454b8095cf7c8ad25382
GIT binary patch
literal 3304
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
literal 0
HcmV?d00001
diff --git a/Documentation/lnst_intro_images.sh b/Documentation/lnst_intro_images.sh
new file mode 100755
index 0000000..0c584ae
--- /dev/null
+++ b/Documentation/lnst_intro_images.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+FILES="real-hardware-setup machineconfig-netconfig-mapping"
+
+if [ $# -gt 0 ] && [ $1 == "clean" ]; then
+ echo "Removing png files for LNSTIntro"
+ for f in $FILES; do
+ rm -f $f.png
+ done
+else
+ for f in $FILES; do
+ /usr/bin/dia -e $f.png $f.dia
+ done
+fi
+
diff --git a/Documentation/machineconfig-netconfig-mapping.dia b/Documentation/machineconfig-netconfig-mapping.dia
new file mode 100644
index 0000000000000000000000000000000000000000..17eeca5ac730ff2cd6dcd3d5444ed0bd21a7dc7c
GIT binary patch
literal 3745
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
Lb?f`B(bWI|1=>LO
literal 0
HcmV?d00001
diff --git a/Documentation/real-hardware-setup.dia b/Documentation/real-hardware-setup.dia
new file mode 100644
index 0000000000000000000000000000000000000000..b049fa72b9e36de3c8e612fe555bc8a5c74aa938
GIT binary patch
literal 2925
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
Xe^b5@|2A2$d;jMD&H!<M2&Mo437ocq
literal 0
HcmV?d00001
--
1.7.6.5
11 years, 11 months
[PATCH] README: Adding some documentation for multicast
by Radek Pazdera
From: Radek Pazdera <rpazdera(a)redhat.com>
New readme file for multicast test tools that documents
behavior of various setups and what can be tested with them.
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
---
test_tools/multicast/README | 120 +++++++++++++++++++++++++++++++++++++++++++
1 files changed, 120 insertions(+), 0 deletions(-)
create mode 100644 test_tools/multicast/README
diff --git a/test_tools/multicast/README b/test_tools/multicast/README
new file mode 100644
index 0000000..b067721
--- /dev/null
+++ b/test_tools/multicast/README
@@ -0,0 +1,120 @@
+Multicast Testing Tools
+=======================
+
+This is a set of test setups and scripts for multicast testing in Linux kernel.
+There are generaly four types of ready-to-use testing tools included in this
+package and also a library you can use to build your own setup for testing.
+
+
+Structure
+---------
+
+The structure is following:
+
+* **client/** - contains client setups (generaly programs that send data)
+* **offline/** - here goes all tests, that are performed offline
+* **scripts/** - helper scripts in bash/python
+* **server/** - setups of servers (programs that receive)
+* *igmp\_utils.h* - sending IGMP packets over IPPROTO\_RAW socket
+* *multicast\_utils.h* - sending/receiving IPv4 multicast
+* *parameters.h* - parsing options
+* *sockopt\_utils.h* - offline conformance testing of socket options
+
+
+Setups
+------
+
+### Parameters ###
+
+Execution of each test setup can be controled via a set of options. These
+parameters are passed to the setup through CLI arguments. There are long and
+short version for each option.
+
+
+### Test Results ###
+
+Each test setup returns results in the following format for easy parsing:
+
+ name="value"
+ packets_transfered=5
+
+Double-quotes around the value are optional.
+
+
+### Clients ###
+
+* **recv\_block\_source** - Join multicast group for some time, start blocking
+ the sender with `IP_BLOCK_SOURCE`. Finaly unblock the source using
+ `IP_UNBLOCK_SOURCE`.
+
+ Returns `packets_received` - number of packets, that correctly arrived and
+ `packets_received_while_blocking` - number of packets received while the
+ source was blocked (which should always be zero).
+
+* **recv\_membership** - Join multicast group and leave in the middle of
+ ongoing communication.
+
+ Returns `packets_received` -- number of correctly received packets and
+ `packets_received_after_drop` -- number of packets received after leaving
+ the multicast group.
+
+* **recv\_simple** - Simple setup that joins multicast group and listens
+ for data.
+
+ Returns `packets_received`.
+
+* **recv\_source\_membership** - Same as the **recv\_membership** setup, only
+ for source specific multicast.
+
+
+### Servers ###
+
+* **send\_simple** - This setup allows you to emitt multicast packets
+ with specific parameters to some multicast group. Run with -h option
+ in order to find out all controlalble parameters of this setup.
+
+ Returns `packets_sent` - number of packets passed to network.
+
+* **send\_igmp\_query** - This setup is able to send IP datagrams with IGMP
+ queries of all versions. It uses RAW sockets so it must be run with
+ super-user priviledges.
+
+ Returns nothing.
+
+
+### Offline ###
+
+* **sockopt\_block\_source** - `IP_BLOCK_SOURCE` and `IP_UNBLOCK_SOURCE`
+ offline conformance tests
+
+ Returns list of passed tests.
+
+* **sockopt\_if** - `IP_MULTICAST_IF` conformance test
+
+ Returns list of passed tests.
+
+* **sockopt\_loop** - `IP_MULTICAST_LOOP` conformance test
+
+ Returns list of passed tests.
+
+* **sockopt\_membership** - `IP_ADD_MEMBERSHIP` and `IP_DROP_MEMBERSHIP`
+ conformance test
+
+ Returns list of passed tests.
+
+* **sockopt\_source\_membership** - `IP_ADD_SOURCE_MEMBERSHIP` and
+ `IP_DROP_SOURCE_MEMBERSHIP` conformance test
+
+ Returns list of passed tests.
+
+* **sockopt\_ttl** - `IP_MULTICAST_TTL` conformance test
+
+ Returns list of passed tests.
+
+
+Author
+------
+
+Radek Pazdera ([rpazdera@redhat.com](mailto:rpazdera@redhat.com))
+
+This was developed as part of LNST framework.
--
1.7.7.6
11 years, 11 months
[PATCH 1/5] multicast: New test case membership.xml
by Radek Pazdera
From: Radek Pazdera <rpazdera(a)redhat.com>
Adding new testcase `membership.xml' with the necessary setup and
offline conformance tests for sockopt ADD/DROP_MEMBERSHIP interface.
This simple test case verifies that if one side leaves multicast group
in the middle of ongoing communication, no further packets are delivered
to the process.
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
---
.../cmd_sequences/multicast/membership.xml | 48 ++++++++++++
test_tools/multicast/Makefile | 4 +-
test_tools/multicast/offline/sockopt_membership.c | 78 ++++++++++++++++++++
test_tools/multicast/server/recv_membership.c | 62 ++++++++++++++++
4 files changed, 190 insertions(+), 2 deletions(-)
create mode 100644 example_recipes/cmd_sequences/multicast/membership.xml
create mode 100644 test_tools/multicast/offline/sockopt_membership.c
create mode 100644 test_tools/multicast/server/recv_membership.c
diff --git a/example_recipes/cmd_sequences/multicast/membership.xml b/example_recipes/cmd_sequences/multicast/membership.xml
new file mode 100644
index 0000000..fd9c166
--- /dev/null
+++ b/example_recipes/cmd_sequences/multicast/membership.xml
@@ -0,0 +1,48 @@
+<!-- IP_ADD/DROP_MEMBERSHIP test -->
+<!-- Requires: 2 hosts
+ - [1] with one interface
+ - [2] with one interface
+ - -->
+
+<command_sequence>
+ <!-- IP_ADD/DROP_MEMBERSHIP sockopt conformance test -->
+ <command type="test" value="Multicast" machine_id="1" timeout="30">
+ <options>
+ <option name="setup" value="sockopt_membership" />
+ <option name="condition" value="status == 'pass'" />
+ </options>
+ </command>
+
+ <!-- This simple test case verifies that if one side leaves multicast group
+ - in the middle of ongoing communication, no further packets are delivered
+ - to the process. -->
+ <command type="exec" machine_id="1" value="sleep 1" />
+ <command type="exec" machine_id="2" value="sleep 1" />
+
+ <command type="test" value="Multicast" machine_id="1" timeout="30" bg_id="1">
+ <options>
+ <option name="setup" value="send_simple" />
+ <option name="address" value="238.0.0.1" />
+ <option name="port" value="1337" />
+ <option name="duration" value="10" />
+ <option name="delay" value="0.1" />
+ <option name="ttl" value="1" />
+ <option type="recipe_eval" name="interface" value="['machines'][1]['netconfig'][1]['addresses'][0]" />
+ </options>
+ </command>
+
+ <command type="test" value="Multicast" machine_id="2" timeout="30">
+ <options>
+ <option name="setup" value="recv_membership" />
+ <option name="address" value="238.0.0.1" />
+ <option name="port" value="1337" />
+ <option name="duration" value="10" />
+ <option type="recipe_eval" name="interface" value="['machines'][2]['netconfig'][1]['addresses'][0]" />
+
+ <option name="condition" value="packets_received > 0" />
+ <option name="condition" value="packets_received_after_drop == 0" />
+ </options>
+ </command>
+
+ <command type="wait" machine_id="1" value="1" />
+</command_sequence>
diff --git a/test_tools/multicast/Makefile b/test_tools/multicast/Makefile
index 47af8f5..68a5406 100644
--- a/test_tools/multicast/Makefile
+++ b/test_tools/multicast/Makefile
@@ -3,8 +3,8 @@ TOOLS_DIR=.
CFLAGS=-Wall -Wextra -I$(TOOLS_DIR)
SENDERS=send_simple
-RECEIVERS=recv_simple
-OFFLINE=sockopt_loop sockopt_ttl sockopt_if
+RECEIVERS=recv_simple recv_membership
+OFFLINE=sockopt_loop sockopt_ttl sockopt_if sockopt_membership
all: $(SENDERS) $(RECEIVERS) $(OFFLINE)
diff --git a/test_tools/multicast/offline/sockopt_membership.c b/test_tools/multicast/offline/sockopt_membership.c
new file mode 100644
index 0000000..b8612f7
--- /dev/null
+++ b/test_tools/multicast/offline/sockopt_membership.c
@@ -0,0 +1,78 @@
+/*
+ * sockopt_membership.c - IP_ADD/DROP_MEMBERSHIP socket option test
+ * Copyright (C) 2012 Red Hat Inc.
+ *
+ * Author: Radek Pazdera (rpazdera(a)redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "sockopt_utils.h"
+
+
+void test_add_membership()
+{
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr.s_addr = 0x0100007f;
+ struct ip_mreqn mreqn;
+
+ test_setsockopt_error("IP_ADD_MEMBERSHIP Bad multicast address",
+ IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq), EINVAL);
+
+ test_setsockopt_error("IP_ADD_MEMBERSHIP Bad optlen",
+ IP_ADD_MEMBERSHIP, &mreq, 5, EINVAL);
+
+ mreqn.imr_multiaddr.s_addr = 0xdeadbeef;
+ mreqn.imr_address.s_addr = 0xffffffff;
+ mreqn.imr_ifindex = 500;
+ test_setsockopt_error("IP_ADD_MEMBERSHIP No device found",
+ IP_ADD_MEMBERSHIP, &mreqn, sizeof(mreqn), ENODEV);
+}
+
+void test_drop_membership()
+{
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr.s_addr = 0x0100007f;
+ mreq.imr_interface.s_addr = 0x0100007f;
+ struct ip_mreqn mreqn;
+
+ test_setsockopt_error("IP_DROP_MEMBERSHIP Bad optlen",
+ IP_DROP_MEMBERSHIP, &mreq, 5, EINVAL);
+ test_setsockopt_error("IP_DROP_MEMBERSHIP Bad multicast address",
+ IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq), EADDRNOTAVAIL);
+
+ mreq.imr_multiaddr.s_addr = 0xdeadbeef;
+ mreq.imr_interface.s_addr = 0x0100007f;
+ test_setsockopt_error("IP_DROP_MEMBERSHIP Not a member",
+ IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq), EADDRNOTAVAIL);
+
+ mreqn.imr_multiaddr.s_addr = 0xdeadbeef;
+ mreqn.imr_address.s_addr = 0xffffffff;
+ mreqn.imr_ifindex = 500;
+ test_setsockopt_error("IP_DROP_MEMBERSHIP No device found",
+ IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn), ENODEV);
+}
+
+int main()
+{
+ initialize();
+
+ test_add_membership();
+ test_drop_membership();
+
+ report_and_exit();
+ return 0;
+}
diff --git a/test_tools/multicast/server/recv_membership.c b/test_tools/multicast/server/recv_membership.c
new file mode 100644
index 0000000..018226b
--- /dev/null
+++ b/test_tools/multicast/server/recv_membership.c
@@ -0,0 +1,62 @@
+/*
+ * recv_membership.c - Join multicast group and leave it
+ * in the middle of communication
+ *
+ * Copyright (C) 2012 Red Hat Inc.
+ *
+ * Author: Radek Pazdera (rpazdera(a)redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#define RECEIVE
+#include "multicast_utils.h"
+
+int main(int argc, char** argv)
+{
+ struct parameters params;
+ parse_args(argc, argv, ¶ms);
+
+ int sockfd = init_in_socket(params.multiaddr, params.port);
+
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr = params.multiaddr;
+ mreq.imr_interface = params.interface;
+
+ int num_recv = 0;
+
+ if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ &(mreq), sizeof(mreq)) < 0) {
+ perror("setsockopt");
+ return EXIT_FAILURE;
+ }
+
+ num_recv = wait_for_data(sockfd, params.duration/2, 0);
+
+ printf("packets_received=%d\n", num_recv);
+
+ if (setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP,
+ &(mreq), sizeof(mreq)) < 0) {
+ perror("setsockopt");
+ return EXIT_FAILURE;
+ }
+
+ num_recv = wait_for_data(sockfd, params.duration/2, 0);
+
+ printf("packets_received_after_drop=%d\n", num_recv);
+
+ return EXIT_SUCCESS;
+}
--
1.7.7.6
11 years, 11 months
[PATCH] NetTestParse.py: Conversion of attributes
by Radek Pazdera
From: Radek Pazdera <rpazdera(a)redhat.com>
DOM parser returns unicode strings, but LNST works with
traditional strings. It's necessary to convert unicode strings
for consistency.
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
---
NetTest/NetTestParse.py | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/NetTest/NetTestParse.py b/NetTest/NetTestParse.py
index 10ff4fc..c1493b5 100644
--- a/NetTest/NetTestParse.py
+++ b/NetTest/NetTestParse.py
@@ -251,10 +251,10 @@ class NetTestParse:
num_attributes = node.attributes.length
while(i < num_attributes):
attr = node.attributes.item(i)
- attr.value = self._expand_string(attr.value, recipe_eval)
+ attr.value = self._expand_string(str(attr.value), recipe_eval)
i += 1
elif node.nodeType == node.TEXT_NODE:
- node.data = self._expand_string(node.data, recipe_eval)
+ node.data = self._expand_string(str(node.data), recipe_eval)
for child in node.childNodes:
self._expand(child, recipe_eval)
--
1.7.7.6
11 years, 11 months