[lnst: 9/9] network namespace support for LNST
by Jiří Pírko
commit bbce651266c2d85419ac000248d474a5647ffaef
Merge: e6a5dd0 fea2cea
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Thu Aug 14 11:59:57 2014 +0200
network namespace support for LNST
The following set of patches introduces network namespace support to LNST. This
required some changes to the way the slave server works. Currently there will
be a single "master" process that acts as the main server and manages the root
network namespace (or the namespace that lnst-slave was launched in). This is
the process that the controller connects to and communicates with.
When the controller requests the creation of a new network namespace the server
forks itself and using the unshare syscall creates a new namespace. This new
process will now act as the lnst-slave server that manages a different
namespace (configuration, running commands and cleanup). The new process only
communicates with it's parent through a pipe object, and as far as network
communication is concerned this parent is it's connected controller. At this
point the parent process starts acting like a router resending messages from
the network namespaces to the controller and vice versa.
The configuration itself works in this order:
1. namespace creation
2. move interfaces to their target namespaces
3. configure interfaces (create soft interfaces in their target namespace)
When it comes to recipe XML format, the only thing that changed is that you can
now use the attribute "netns" for devices, and for some commands (where it
makes sense).
Next it will probably be a good idea to add support for veth type devices,
signal commands probably shouldn't require to use the netns attribute, netns
support for python taks.
To finish off I'll add an example recipe:
<lnstrecipe>
<network>
<host id="1">
<interfaces>
<eth id="t1" netns="xyz" label="ttnet"/>
<eth id="t2" netns="xyz" label="ttnet"/>
<bond id="t3" netns="xyz">
<addresses>
<address value="192.168.100.240/24"/>
</addresses>
<slaves>
<slave id="t1"/>
<slave id="t2"/>
</slaves>
</bond>
</interfaces>
</host>
<host id="2">
<interfaces>
<eth id="t1" label="ttnet">
<addresses>
<address value="192.168.100.215/24"/>
</addresses>
</eth>
</interfaces>
</host>
</network>
<task>
<run host="1" command="ip a"/>
<run host="1" command="ip a" netns="xyz"/>
<run host="1" module="IcmpPing" netns="xyz">
<options>
<option name="addr" value="{ip(2,t1)}"/>
<option name="count" value="40"/>
<option name="interval" value="1"/>
</options>
</run>
</task>
</lnstrecipe>
lnst/Common/ConnectionHandler.py | 2 +-
lnst/Controller/Machine.py | 127 ++++++++++++++++++++++---
lnst/Controller/NetTestController.py | 19 ++++-
lnst/Controller/RecipeParser.py | 12 +++
lnst/Controller/XmlProcessing.py | 3 +-
lnst/Slave/InterfaceManager.py | 60 +++++++++---
lnst/Slave/NetConfigDevice.py | 31 +++++-
lnst/Slave/NetTestSlave.py | 175 +++++++++++++++++++++++++++++++++-
lnst/Slave/NmConfigDevice.py | 10 ++-
schema-recipe.rng | 21 ++++
10 files changed, 420 insertions(+), 40 deletions(-)
---
9 years, 8 months
[lnst: 8/9] controller: network namespace support
by Jiří Pírko
commit fea2cea9df47d7a2bd5656d28555e430d4256e11
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Wed Aug 13 18:34:57 2014 +0200
controller: network namespace support
This patch adds the rest of the controller support for network
namespaces.
Machine:
* The most important addition is the _rpc_call_to_netns method of the
Machine class. This method ensures that whent the slave recieves the
message it will route it's contents to the specified network namespace.
* Packet capture was modified so that we also capture on devices that
are in different namespaces.
* To add/remove network namespaces we use the add_netns and del_netns
methods of the Machine class.
Interface:
* If the interface is supposed to be in a non-root network namespace the
configure method first moves the interface to the correct namespace
and then configures the interface in the new interface.
Deconfiguration is the inverse process - first we deconfigure the
interface and then we return the interface to the previous namespace.
* SoftInterface configuration creates the interfaces in the target
namespace since it might be impossible to move them to a different
namespace once they're created.
NetTestController:
* The _prepare_network method finds all required network namespaces
(based on interface configuration) and takes care of creating these
devices before configuring the interfaces. It also disables the use of
network manager on the slave machine since NM doesn't support network
namespaces.
* In addition to that it takes care of extracting the recipe information
about network namespaces and storing it in the Interface object, and
command dictionary.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Controller/Machine.py | 118 ++++++++++++++++++++++++++++++----
lnst/Controller/NetTestController.py | 19 +++++-
2 files changed, 124 insertions(+), 13 deletions(-)
---
diff --git a/lnst/Controller/Machine.py b/lnst/Controller/Machine.py
index 7ef3837..94d4fbd 100644
--- a/lnst/Controller/Machine.py
+++ b/lnst/Controller/Machine.py
@@ -71,6 +71,7 @@ class Machine(object):
self._mac_pool = None
self._interfaces = []
+ self._namespaces = []
def _add_interface(self, if_id, if_type, cls):
if if_id != None:
@@ -148,6 +149,15 @@ class Machine(object):
return result
+ def _rpc_call_to_netns(self, netns, method_name, *args):
+ data = {"type": "command", "method_name": method_name, "args": args}
+ msg = {"type": "to_netns", "netns": netns, "data": data}
+
+ self._msg_dispatcher.send_message(self._id, msg)
+ result = self._msg_dispatcher.wait_for_result(self._id)
+
+ return result
+
def configure(self, recipe_name):
""" Prepare the machine
@@ -198,6 +208,8 @@ class Machine(object):
ordered_ifaces = self.get_ordered_interfaces()
try:
self._rpc_call("kill_cmds")
+ for netns in self._namespaces:
+ self._rpc_call_to_netns(netns, "kill_cmds")
if deconfigure:
ordered_ifaces.reverse()
@@ -206,6 +218,8 @@ class Machine(object):
for iface in ordered_ifaces:
iface.cleanup()
+ self.del_namespaces()
+
self._rpc_call("bye")
except:
#cleanup is only meaningful on dynamic interfaces, and should
@@ -217,6 +231,7 @@ class Machine(object):
iface.cleanup()
raise
finally:
+ self.restore_nm_option()
self._msg_dispatcher.disconnect_slave(self.get_id())
self._configured = False
@@ -239,7 +254,11 @@ class Machine(object):
signal.alarm(DEFAULT_TIMEOUT)
try:
- cmd_res = self._rpc_call("run_command", command)
+ if 'netns' in command and command['netns'] != None:
+ netns = command["netns"]
+ cmd_res = self._rpc_call_to_netns(netns, "run_command", command)
+ else:
+ cmd_res = self._rpc_call("run_command", command)
except MachineError as exc:
if "bg_id" in command:
cmd_res = self._rpc_call("kill_command", command["bg_id"])
@@ -281,7 +300,10 @@ class Machine(object):
self._mac_pool = mac_pool
def restore_system_config(self):
- return self._rpc_call("restore_system_config")
+ self._rpc_call("restore_system_config")
+ for netns in self._namespaces:
+ self._rpc_call_to_netns(netns, "restore_system_config")
+ return True
def set_network_bridges(self, bridges):
self._network_bridges = bridges
@@ -299,10 +321,28 @@ class Machine(object):
return self._domain_ctl
def start_packet_capture(self):
- return self._rpc_call("start_packet_capture", "")
+ namespaces = set()
+ for iface in self._interfaces:
+ namespaces.add(iface.get_netns())
+
+ tmp = {}
+ for netns in namespaces:
+ if netns == None:
+ tmp.update(self._rpc_call("start_packet_capture", ""))
+ else:
+ tmp.update(self._rpc_call_to_netns(netns, "start_packet_capture", ""))
+ return tmp
def stop_packet_capture(self):
- self._rpc_call("stop_packet_capture")
+ namespaces = set()
+ for iface in self._interfaces:
+ namespaces.add(iface.get_netns())
+
+ for netns in namespaces:
+ if netns == None:
+ self._rpc_call("stop_packet_capture")
+ else:
+ self._rpc_call_to_netns(netns, "stop_packet_capture")
def copy_file_to_machine(self, local_path, remote_path=None):
remote_path = self._rpc_call("start_copy_to", remote_path)
@@ -378,6 +418,19 @@ class Machine(object):
return "[Machine hostname(%s) libvirt_domain(%s) interfaces(%d)]" % \
(self._hostname, self._libvirt_domain, len(self._interfaces))
+ def add_netns(self, netns):
+ self._namespaces.append(netns)
+ return self._rpc_call("add_namespace", netns)
+
+ def del_netns(self, netns):
+ return self._rpc_call("del_namespace", netns)
+
+ def del_namespaces(self):
+ for netns in self._namespaces:
+ self.del_netns(netns)
+ self._namespaces = []
+ return True
+
class Interface(object):
""" Abstraction of a test network interface on a slave machine
@@ -404,6 +457,8 @@ class Interface(object):
self._ovs_conf = None
+ self._netns = None
+
def get_id(self):
return self._id
@@ -478,6 +533,12 @@ class Interface(object):
def get_ovs_conf(self):
return self._ovs_conf
+ def set_netns(self, netns):
+ self._netns = netns
+
+ def get_netns(self):
+ return self._netns
+
def get_prefix(self, num):
try:
return self._addresses[num].split('/')[1]
@@ -490,7 +551,7 @@ class Interface(object):
"options": self._options,
"slave_options": self._slave_options,
"master": None, "other_masters": [],
- "ovs_conf": self._ovs_conf}
+ "ovs_conf": self._ovs_conf, "netns": self._netns}
if self._master["primary"] != None:
config["master"] = self._master["primary"].get_id()
@@ -501,10 +562,18 @@ class Interface(object):
return config
def up(self):
- self._machine._rpc_call("set_device_up", self._id)
+ netns = self._netns
+ if netns != None:
+ self._machine._rpc_call_to_netns(netns, "set_device_up", self._id)
+ else:
+ self._machine._rpc_call("set_device_up", self._id)
def down(self):
- self._machine._rpc_call("set_device_down", self._id)
+ netns = self._netns
+ if netns != None:
+ self._machine._rpc_call_to_netns(netns, "set_device_down", self._id)
+ else:
+ self._machine._rpc_call("set_device_down", self._id)
def initialize(self):
phys_devs = self._machine._rpc_call("map_if_by_hwaddr",
@@ -536,15 +605,26 @@ class Interface(object):
logging.info("Configuring interface %s on machine %s", self.get_id(),
self._machine.get_id())
- self._machine._rpc_call("configure_interface", self.get_id(),
- self._get_config())
+ if self._netns != None:
+ self._machine._rpc_call("set_if_netns", self.get_id(), self._netns)
+ self._machine._rpc_call_to_netns(self._netns, "configure_interface",
+ self.get_id(), self._get_config())
+ else:
+ self._machine._rpc_call("configure_interface", self.get_id(),
+ self._get_config())
self._configured = True
def deconfigure(self):
if not self._configured:
return
- self._machine._rpc_call("deconfigure_interface", self.get_id())
+ if self._netns != None:
+ self._machine._rpc_call_to_netns(self._netns,
+ "deconfigure_interface", self.get_id())
+ self._machine._rpc_call_to_netns(self._netns,
+ "return_if_netns", self.get_id())
+ else:
+ self._machine._rpc_call("deconfigure_interface", self.get_id())
self._configured = False
class StaticInterface(Interface):
@@ -665,11 +745,25 @@ class SoftInterface(Interface):
logging.info("Configuring interface %s on machine %s", self.get_id(),
self._machine.get_id())
- dev_name = self._machine._rpc_call("create_soft_interface", self._id,
- self._get_config())
+ if self._netns != None:
+ dev_name = self._machine._rpc_call_to_netns(self._netns,
+ "create_soft_interface", self._id, self._get_config())
+ else:
+ dev_name = self._machine._rpc_call("create_soft_interface",
+ self._id, self._get_config())
self.set_devname(dev_name)
self._configured = True
+ def deconfigure(self):
+ if not self._configured:
+ return
+
+ if self._netns != None:
+ self._machine._rpc_call_to_netns(self._netns,
+ "deconfigure_interface", self.get_id())
+ else:
+ self._machine._rpc_call("deconfigure_interface", self.get_id())
+ self._configured = False
class UnusedInterface(Interface):
""" Unused interface for this test
diff --git a/lnst/Controller/NetTestController.py b/lnst/Controller/NetTestController.py
index 890bba7..f62ca45 100644
--- a/lnst/Controller/NetTestController.py
+++ b/lnst/Controller/NetTestController.py
@@ -178,10 +178,21 @@ class NetTestController:
for machine_xml_data in recipe["machines"]:
m_id = machine_xml_data["id"]
+ m = machines[m_id]
+ namespaces = set()
for iface_xml_data in machine_xml_data["interfaces"]:
self._prepare_interface(m_id, iface_xml_data)
- ifaces = machines[m_id].get_ordered_interfaces()
+ if iface_xml_data["netns"] != None:
+ namespaces.add(iface_xml_data["netns"])
+
+ if len(namespaces) > 0:
+ m.disable_nm()
+
+ ifaces = m.get_ordered_interfaces()
+ for netns in namespaces:
+ m.add_netns(netns)
+
for iface in ifaces:
iface.configure()
for iface in ifaces:
@@ -296,6 +307,9 @@ class NetTestController:
if "ovs_conf" in iface_xml_data:
iface.set_ovs_conf(iface_xml_data["ovs_conf"])
+ if iface_xml_data["netns"] != None:
+ iface.set_netns(iface_xml_data["netns"])
+
def _prepare_tasks(self):
self._tasks = []
for task_data in self._recipe["tasks"]:
@@ -354,6 +368,9 @@ class NetTestController:
msg = "Invalid host id '%s'." % cmd["host"]
raise RecipeError(msg, cmd_data)
+ if "netns" in cmd_data:
+ cmd["netns"] = cmd_data["netns"]
+
if "expect" in cmd_data:
expect = cmd_data["expect"]
if expect not in ["pass", "fail"]:
9 years, 8 months
[lnst: 7/9] recipe: network namespace support
by Jiří Pírko
commit baefa67cb37bfba69a8c57ee141f908afd2606d3
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Wed Aug 13 18:34:56 2014 +0200
recipe: network namespace support
This patch extends the recipe XML files to support network namespaces.
This is a pretty simple change:
* all device elemenets (eth, bond, bridge, ...) now support the "netns"
attribute which can be an arbitrary string
* command elements config, run, kill, intr and wait now also support the
"netns" attribute
At the moment there are no checks whether or not the value of the
attribute makes sense. The controller will create the namespaces based
on the device configuration so if a command uses a different namespace
it will fail. The same goes for background commands - the corresponding
signal command needs to be run in the same namespace or it will fail.
This probably can be improved in the future.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Controller/RecipeParser.py | 12 ++++++++++++
schema-recipe.rng | 21 +++++++++++++++++++++
2 files changed, 33 insertions(+), 0 deletions(-)
---
diff --git a/lnst/Controller/RecipeParser.py b/lnst/Controller/RecipeParser.py
index df0b47a..76da1c6 100644
--- a/lnst/Controller/RecipeParser.py
+++ b/lnst/Controller/RecipeParser.py
@@ -86,6 +86,10 @@ class RecipeParser(XmlParser):
iface["id"] = self._get_attribute(iface_tag, "id")
iface["type"] = iface_tag.tag
+ iface["netns"] = None
+ if self._has_attribute(iface_tag, "netns"):
+ iface["netns"] = self._get_attribute(iface_tag, "netns")
+
# params
params_tag = iface_tag.find("params")
params = self._process_params(params_tag)
@@ -283,6 +287,10 @@ class RecipeParser(XmlParser):
cmd = XmlData(cmd_tag)
cmd["host"] = self._get_attribute(cmd_tag, "host")
+ cmd["netns"] = None
+ if self._has_attribute(cmd_tag, "netns"):
+ cmd["netns"] = self._get_attribute(cmd_tag, "netns")
+
has_module = self._has_attribute(cmd_tag, "module")
has_command = self._has_attribute(cmd_tag, "command")
has_from = self._has_attribute(cmd_tag, "from")
@@ -323,6 +331,10 @@ class RecipeParser(XmlParser):
cmd["type"] = "config"
cmd["host"] = self._get_attribute(cmd_tag, "host")
+ cmd["netns"] = None
+ if self._has_attribute(cmd_tag, "netns"):
+ cmd["netns"] = self._get_attribute(cmd_tag, "netns")
+
if self._has_attribute(cmd_tag, "persistent"):
cmd["persistent"] = self._get_attribute(cmd_tag, "persistent")
diff --git a/schema-recipe.rng b/schema-recipe.rng
index b623a99..6795f72 100644
--- a/schema-recipe.rng
+++ b/schema-recipe.rng
@@ -146,6 +146,9 @@
<element name="eth">
<attribute name="id"/>
<attribute name="label"/>
+ <optional>
+ <attribute name="netns"/>
+ </optional>
<interleave>
<optional>
<ref name="define"/>
@@ -179,6 +182,9 @@
<define name="ovs_bridge">
<element name="ovs_bridge">
<attribute name="id"/>
+ <optional>
+ <attribute name="netns"/>
+ </optional>
<interleave>
<optional>
@@ -275,6 +281,9 @@
</define>
<define name="softdevice">
+ <optional>
+ <attribute name="netns"/>
+ </optional>
<interleave>
<optional>
<ref name="define"/>
@@ -383,6 +392,10 @@
<attribute name="host"/>
<optional>
+ <attribute name="netns"/>
+ </optional>
+
+ <optional>
<attribute name="option"/>
<attribute name="value"/>
</optional>
@@ -421,6 +434,10 @@
<element name="run">
<attribute name="host"/>
+ <optional>
+ <attribute name="netns"/>
+ </optional>
+
<choice>
<attribute name="module"/>
<ref name="run_command"/>
@@ -483,6 +500,10 @@
<define name="signal_command">
<attribute name="host"/>
<attribute name="bg_id"/>
+
+ <optional>
+ <attribute name="netns"/>
+ </optional>
</define>
<define name="ctl_wait">
9 years, 8 months