From: Ondrej Lichtner olichtne@redhat.com
The following set of patches further improves network namespace support in LNST.
Most of the patch set implements support for veth devices. Since veth devices are always created in pairs they're represented a bit differently in the recipe XML - by encompasing two <veth> tags within a <veth_pair> tag.
The first patch of the set also takes care of remounting sysfs on namespace creation which makes it usable for configuration again.
Example recipe with veth devices: <lnstrecipe> <network> <host id="testmachine1"> <interfaces> <eth id="out_if" label="tnet"/> <veth_pair> <veth id="testifc1" netns="xyz"> <addresses> <address value="192.168.200.2/24"/> </addresses> </veth> <veth id="testifc2"/> </veth_pair> <bridge id="br"> <slaves> <slave id="out_if"/> <slave id="testifc2"/> </slaves> </bridge> </interfaces> </host>
<host id="testmachine2"> <interfaces> <eth id="testifc2" label="tnet"> <addresses> <address value="192.168.200.3/24"/> </addresses> </eth> </interfaces> </host> </network>
<task> <run host="testmachine1" module="IcmpPing" netns="xyz"> <options> <option name="addr" value="{ip(testmachine2,testifc2)}"/> <option name="count" value="40"/> <option name="interval" value="1"/> </options> </run> </task> </lnstrecipe>
Ondrej Lichtner (10): NetTestSlave: remount sysfs in new net namespace ConnectionHandler: check specific connections schema-recipe: add support for veth interfaces RecipeParser: support parsing veth interfaces NetTestSlave: extend communication options between namespaces NetTestSlave: make return_if_netns method recursive *ConfigDevice: split device creation and configuration *ConfigDevice: add VEth classes NetTestSlave: add methods {create, deconfigure}_if_pair Machine: Interface class veth support
lnst/Common/ConnectionHandler.py | 8 +- lnst/Controller/Machine.py | 39 +++++++-- lnst/Controller/NetTestController.py | 3 + lnst/Controller/RecipeParser.py | 16 +++- lnst/Slave/InterfaceManager.py | 57 ++++++++++++- lnst/Slave/NetConfigDevice.py | 108 ++++++++++++++---------- lnst/Slave/NetTestSlave.py | 155 +++++++++++++++++++++++++++++++++-- lnst/Slave/NmConfigDevice.py | 15 +++- schema-recipe.rng | 36 ++++++++ 9 files changed, 369 insertions(+), 68 deletions(-)
From: Ondrej Lichtner olichtne@redhat.com
Creating a new network namespace does not change the contents of the sysfs filesystem which makes it unusable for network configuration purposes. This patch fixes that by creating a new mount namespace and remounting the sysfs filesystem.
This also means that we don't need to use iproute2 to create Bonding interfaces anymore.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Slave/NetConfigDevice.py | 28 +++++----------------------- lnst/Slave/NetTestSlave.py | 14 +++++++++++++- 2 files changed, 18 insertions(+), 24 deletions(-)
diff --git a/lnst/Slave/NetConfigDevice.py b/lnst/Slave/NetConfigDevice.py index 338df67..86744db 100644 --- a/lnst/Slave/NetConfigDevice.py +++ b/lnst/Slave/NetConfigDevice.py @@ -14,8 +14,6 @@ jpirko@redhat.com (Jiri Pirko) import logging import re import sys -from platform import release -from distutils.version import LooseVersion from lnst.Common.ExecCmd import exec_cmd from lnst.Slave.NetConfigCommon import get_slaves, get_option, get_slave_option from lnst.Common.Utils import kmod_in_use, bool_it @@ -88,16 +86,9 @@ class NetConfigDeviceBond(NetConfigDeviceGeneric): _moduleparams = "max_bonds=0"
def _add_rm_bond(self, mark): - #3.10 works on rhel7, I didn't test which oldest version works... - if LooseVersion(release()) > LooseVersion('3.10'): - if mark == "+": - exec_cmd('ip link add %s type bond' % self._dev_config["name"]) - elif mark == "-": - exec_cmd('ip link del %s' % self._dev_config["name"]) - else: - bond_masters = "/sys/class/net/bonding_masters" - exec_cmd('echo "%s%s" > %s' % (mark, self._dev_config["name"], - bond_masters)) + bond_masters = "/sys/class/net/bonding_masters" + exec_cmd('echo "%s%s" > %s' % (mark, self._dev_config["name"], + bond_masters))
def _get_bond_dir(self): return "/sys/class/net/%s/bonding" % self._dev_config["name"] @@ -123,20 +114,11 @@ class NetConfigDeviceBond(NetConfigDeviceGeneric): slave_dev = self._if_manager.get_mapped_device(slave_id) slave_conf = slave_dev.get_conf_dict() slave_name = slave_dev.get_name() - bond_name = self._dev_config["name"] if mark == "+": slave_dev.down()
- #3.10 works on rhel7, I didn't test which oldest version works... - if LooseVersion(release()) > LooseVersion('3.10'): - if mark == "+": - exec_cmd('ip link set %s master %s' % (slave_name, - bond_name)) - elif mark == "-": - exec_cmd('ip link set %s nomaster' % (slave_name)) - else: - exec_cmd('echo "%s%s" > %s/slaves' % (mark, slave_name, - self._get_bond_dir())) + exec_cmd('echo "%s%s" > %s/slaves' % (mark, slave_name, + self._get_bond_dir()))
def configure(self): self._add_rm_bond("+") diff --git a/lnst/Slave/NetTestSlave.py b/lnst/Slave/NetTestSlave.py index 780cd9a..8db7b8f 100644 --- a/lnst/Slave/NetTestSlave.py +++ b/lnst/Slave/NetTestSlave.py @@ -402,9 +402,21 @@ class SlaveMethods: elif pid == 0: #create new network namespace libc_name = ctypes.util.find_library("c") - CLONE_NEWNET = 0x40000000 #from sched.h + #from sched.h + CLONE_NEWNET = 0x40000000 + CLONE_NEWNS = 0x00020000 + #based on ipnetns.c from the iproute2 project + MNT_DETACH = 0x00000002 + MS_SLAVE = 1<<19 + MS_REC = 16384 + libc = ctypes.CDLL(libc_name) libc.unshare(CLONE_NEWNET) + #based on ipnetns.c from the iproute2 project + libc.unshare(CLONE_NEWNS) + libc.mount("", "/", "none", MS_SLAVE | MS_REC, 0) + libc.umount2("/sys", MNT_DETACH) + libc.mount(netns, "/sys", "sysfs", 0, 0)
#set ctl socket to pipe to main netns self._server_handler.close_s_sock()
From: Ondrej Lichtner olichtne@redhat.com
This patch extends the ConnectionHandler class with functionality to check specific connections for new messages instead of always checking all of them.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Common/ConnectionHandler.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/lnst/Common/ConnectionHandler.py b/lnst/Common/ConnectionHandler.py index 042e78a..e8a6976 100644 --- a/lnst/Common/ConnectionHandler.py +++ b/lnst/Common/ConnectionHandler.py @@ -70,9 +70,15 @@ class ConnectionHandler(object): self._connections = {}
def check_connections(self): + return self.check_connections_by_id(self._connections.keys()) + + def check_connections_by_id(self, connection_ids): + connections = [] + for con_id in connection_ids: + connections.append(self._connections[con_id]) requests = [] try: - rl, wl, xl = select.select(self._connections.values(), [], [], 0) + rl, wl, xl = select.select(connections, [], [], 0) except select.error: return [] for f in rl:
From: Ondrej Lichtner olichtne@redhat.com
This patch adds recipe XML support for veth interfaces. Since veth devices are created in pairs, XML specification reflects this by encapsulating two <veth> tags in a single <veth_pair> tag. Both of these tags are mandatory. At the moment there are no <options> supported - you can specify them, but they will be ignored.
Example interface specification would look something like this:
<interfaces> <eth id="out_if" label="tnet"/> <veth_pair> <veth id="testifc1" netns="xyz"> <addresses> <address value="192.168.200.2/24"/> </addresses> </veth> <veth id="testifc2"/> </veth_pair> <bridge id="br"> <slaves> <slave id="out_if"/> <slave id="testifc2"/> </slaves> </bridge> </interfaces>
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- schema-recipe.rng | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+)
diff --git a/schema-recipe.rng b/schema-recipe.rng index 6795f72..f13f36a 100644 --- a/schema-recipe.rng +++ b/schema-recipe.rng @@ -135,6 +135,7 @@ <ref name="macvlan"/> <ref name="team"/> <ref name="ovs_bridge"/> + <ref name="veth_pair"/> </choice> </zeroOrMore> </element> @@ -328,6 +329,41 @@ </interleave> </define>
+ <define name="veth_pair"> + <element name="veth_pair"> + <optional> + <ref name="define"/> + </optional> + <ref name="veth"/> + <optional> + <ref name="define"/> + </optional> + <ref name="veth"/> + </element> + </define> + + <define name="veth"> + <element name="veth"> + <attribute name="id"/> + <optional> + <attribute name="netns"/> + </optional> + <interleave> + <optional> + <ref name="define"/> + </optional> + + <zeroOrMore> + <ref name="options"/> + </zeroOrMore> + + <optional> + <ref name="addresses"/> + </optional> + </interleave> + </element> + </define> + <define name="options"> <element name="options"> <interleave>
From: Ondrej Lichtner olichtne@redhat.com
The _process_interface method now returns a list of parsed interfaces. This is because when parsing the veth_pair tag the method parses both child veth tags and returns 2 interfaces at once. Both interfaces contain a reference (by id) to their peer.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Controller/RecipeParser.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/lnst/Controller/RecipeParser.py b/lnst/Controller/RecipeParser.py index 76da1c6..3960c97 100644 --- a/lnst/Controller/RecipeParser.py +++ b/lnst/Controller/RecipeParser.py @@ -66,7 +66,7 @@ class RecipeParser(XmlParser): machine["interfaces"] = XmlCollection(interfaces_tag) for interface_tag in interfaces_tag: interface = self._process_interface(interface_tag) - machine["interfaces"].append(interface) + machine["interfaces"].extend(interface)
return machine
@@ -83,9 +83,19 @@ class RecipeParser(XmlParser):
def _process_interface(self, iface_tag): iface = XmlData(iface_tag) - iface["id"] = self._get_attribute(iface_tag, "id") iface["type"] = iface_tag.tag
+ if iface["type"] == "veth_pair": + iface = self._process_interface(iface_tag[0])[0] + iface2 = self._process_interface(iface_tag[1])[0] + + iface["peer"] = iface2["id"] + iface2["peer"] = iface["id"] + + return [iface, iface2] + + iface["id"] = self._get_attribute(iface_tag, "id") + iface["netns"] = None if self._has_attribute(iface_tag, "netns"): iface["netns"] = self._get_attribute(iface_tag, "netns") @@ -222,7 +232,7 @@ class RecipeParser(XmlParser):
bond_slaves.append(slave_id)
- return iface + return [iface]
def _proces_options(self, opts_tag): options = XmlCollection(opts_tag)
From: Ondrej Lichtner olichtne@redhat.com
The ServerHandler class now supports retrieving messages from a specified connection. And the NetTestSlave class now supports waiting for a response from a specified connection using the ServerHandler method.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Slave/NetTestSlave.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-)
diff --git a/lnst/Slave/NetTestSlave.py b/lnst/Slave/NetTestSlave.py index 8db7b8f..e7d620c 100644 --- a/lnst/Slave/NetTestSlave.py +++ b/lnst/Slave/NetTestSlave.py @@ -46,13 +46,14 @@ class SlaveMethods: Exported xmlrpc methods ''' def __init__(self, command_context, log_ctl, if_manager, net_namespaces, - server_handler): + server_handler, slave_server): self._packet_captures = {} self._if_manager = if_manager self._command_context = command_context self._log_ctl = log_ctl self._net_namespaces = net_namespaces self._server_handler = server_handler + self._slave_server = slave_server
self._capture_files = {} self._copy_targets = {} @@ -541,6 +542,14 @@ class ServerHandler(object): self._c_socket = None return messages
+ def get_messages_from_con(self, con_id): + if self._connection_handler.get_connection(con_id) != None: + return self._connection_handler.check_connections_by_id([con_id]) + elif self._netns_con_handler.get_connection(con_id) != None: + return self._netns_con_handler.check_connections_by_id([con_id]) + else: + raise Exception("Unknown connection id '%s'." % con_id) + def send_data_to_ctl(self, data): if self._c_socket != None: if self._netns != None: @@ -598,7 +607,7 @@ class NetTestSlave:
self._methods = SlaveMethods(self._cmd_context, log_ctl, self._if_manager, self._net_namespaces, - self._server_handler) + self._server_handler, self)
self.register_die_signal(signal.SIGHUP) self.register_die_signal(signal.SIGINT) @@ -630,6 +639,20 @@ class NetTestSlave:
self._methods.machine_cleanup()
+ def wait_for_result(self, id): + result = None + while result == None: + msgs = self._server_handler.get_messages_from_con(id) + for msg in msgs: + if msg[1]["type"] == "result": + result = msg[1] + elif msg[1]["type"] == "from_netns" and\ + msg[1]["data"]["type"] == "result": + result = msg[1]["data"] + else: + self._process_msg(msg[1]) + return result + def _process_msg(self, msg): if msg["type"] == "command": method = getattr(self._methods, msg["method_name"], None)
From: Ondrej Lichtner olichtne@redhat.com
This patch makes the 'return_if_netns' slave method recursive - the controller always calls it in the root namespace which will reroute the method to the specific namespace that the device was moved into. In addition the InterfaceManager class will not remove Device objects if they're moved to a namespace (_netns attribute != None) or if they have a configuration object associated with them. This works around a bug on older kernels that generate RTM_DELLINK messages for slave devices when removing the master device (e.g. bridge).
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Controller/Machine.py | 3 +-- lnst/Slave/InterfaceManager.py | 3 +-- lnst/Slave/NetTestSlave.py | 20 ++++++++++++++++---- 3 files changed, 18 insertions(+), 8 deletions(-)
diff --git a/lnst/Controller/Machine.py b/lnst/Controller/Machine.py index 94d4fbd..7bacad1 100644 --- a/lnst/Controller/Machine.py +++ b/lnst/Controller/Machine.py @@ -621,8 +621,7 @@ class Interface(object): 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()) + self._machine._rpc_call_to("return_if_netns", self.get_id()) else: self._machine._rpc_call("deconfigure_interface", self.get_id()) self._configured = False diff --git a/lnst/Slave/InterfaceManager.py b/lnst/Slave/InterfaceManager.py index 78cc46b..16eba2d 100644 --- a/lnst/Slave/InterfaceManager.py +++ b/lnst/Slave/InterfaceManager.py @@ -101,7 +101,7 @@ class InterfaceManager(object): elif msg['header']['type'] == RTM_DELLINK: if msg['index'] in self._devices: dev = self._devices[msg['index']] - if dev.get_netns() != None: + if dev.get_netns() == None and dev.get_conf_dict() == None: dev.del_link() del self._devices[msg['index']] else: @@ -230,7 +230,6 @@ class Device(object): self._state = nl_msg.get_attr("IFLA_OPERSTATE") self._ip = None #TODO self.set_master(nl_msg.get_attr("IFLA_MASTER"), primary=True) - self._netns = None
link = nl_msg.get_attr("IFLA_LINK") if link != None: diff --git a/lnst/Slave/NetTestSlave.py b/lnst/Slave/NetTestSlave.py index e7d620c..d331818 100644 --- a/lnst/Slave/NetTestSlave.py +++ b/lnst/Slave/NetTestSlave.py @@ -468,10 +468,22 @@ class SlaveMethods:
def return_if_netns(self, if_id): device = self._if_manager.get_mapped_device(if_id) - dev_name = device.get_name() - ppid = os.getppid() - exec_cmd("ip link set %s netns %d" % (dev_name, ppid)) - return True + if device.get_netns() == None: + dev_name = device.get_name() + ppid = os.getppid() + exec_cmd("ip link set %s netns %d" % (dev_name, ppid)) + return True + else: + netns = device.get_netns() + msg = {"type": "command", "method_name": "return_if_netns", + "args": [if_id]} + self._server_handler.send_data_to_netns(netns, msg) + result = self._slave_server.wait_for_result(netns) + if result["result"] != True: + raise Exception("Return from netns failed.") + + device.set_netns(None) + return True
class ServerHandler(object): def __init__(self, addr):
From: Ondrej Lichtner olichtne@redhat.com
This patch splits the device creation (destruction) part of configure (deconfigure) method of ConfigDevice objects into it's own separate method create (destroy). This split is usefull mostly for configuring veth devices, however to keep the code consistent I changed all other device types as well.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Slave/InterfaceManager.py | 18 ++++++++++++- lnst/Slave/NetConfigDevice.py | 58 +++++++++++++++++++++++++++--------------- lnst/Slave/NetTestSlave.py | 5 +++- lnst/Slave/NmConfigDevice.py | 6 +++++ 4 files changed, 64 insertions(+), 23 deletions(-)
diff --git a/lnst/Slave/InterfaceManager.py b/lnst/Slave/InterfaceManager.py index 16eba2d..3695fe2 100644 --- a/lnst/Slave/InterfaceManager.py +++ b/lnst/Slave/InterfaceManager.py @@ -151,7 +151,7 @@ class InterfaceManager(object):
device = Device(self) device.set_configuration(config) - device.configure() + device.create()
self._tmp_mapping[if_id] = device return config["name"] @@ -198,6 +198,7 @@ class Device(object): def __init__(self, if_manager): self._initialized = False self._configured = False + self._created = False
self._if_index = None self._hwaddr = None @@ -320,6 +321,7 @@ class Device(object): if self._conf != None: self.down() self.deconfigure() + self.destroy() self._conf = None self._conf_dict = None
@@ -354,6 +356,20 @@ class Device(object): if if_index in self._slaves: self._slaves.remove(if_index)
+ def create(self): + if self._conf != None and not self._created: + self._conf.create() + self._created = True + return True + return False + + def destroy(self): + if self._conf != None and self._created: + self._conf.destroy() + self._created = False + return True + return False + def configure(self): if self._conf != None and not self._configured: self._conf.configure() diff --git a/lnst/Slave/NetConfigDevice.py b/lnst/Slave/NetConfigDevice.py index 86744db..d64c471 100644 --- a/lnst/Slave/NetConfigDevice.py +++ b/lnst/Slave/NetConfigDevice.py @@ -50,6 +50,12 @@ class NetConfigDeviceGeneric(object): def slave_del(self, slave_id): pass
+ def create(self): + pass + + def destroy(self): + pass + def up(self): config = self._dev_config if "addresses" in config: @@ -120,14 +126,18 @@ class NetConfigDeviceBond(NetConfigDeviceGeneric): exec_cmd('echo "%s%s" > %s/slaves' % (mark, slave_name, self._get_bond_dir()))
- def configure(self): + def create(self): self._add_rm_bond("+") + + def destroy(self): + self._add_rm_bond("-") + + def configure(self): self._setup_options() self._add_rm_slaves("+")
def deconfigure(self): self._add_rm_slaves("-") - self._add_rm_bond("-")
class NetConfigDeviceBridge(NetConfigDeviceGeneric): _modulename = "bridge" @@ -144,13 +154,17 @@ class NetConfigDeviceBridge(NetConfigDeviceGeneric): for slave_id in get_slaves(self._dev_config): self._add_rm_port(prefix, slave_id)
- def configure(self): + def create(self): self._add_rm_bridge("add") + + def destroy(self): + self._add_rm_bridge("del") + + def configure(self): self._add_rm_ports("add")
def deconfigure(self): self._add_rm_ports("del") - self._add_rm_bridge("del")
def slave_add(self, slave_id): self._add_rm_port("add", slave_id) @@ -161,7 +175,7 @@ class NetConfigDeviceBridge(NetConfigDeviceGeneric): class NetConfigDeviceMacvlan(NetConfigDeviceGeneric): _modulename = "macvlan"
- def configure(self): + def create(self): config = self._dev_config; realdev_id = config["slaves"][0] realdev_name = self._if_manager.get_mapped_device(realdev_id).get_name() @@ -175,7 +189,7 @@ class NetConfigDeviceMacvlan(NetConfigDeviceGeneric): exec_cmd("ip link add link %s %s%s type macvlan" % (realdev_name, dev_name, hwaddr))
- def deconfigure(self): + def destroy(self): dev_name = self._dev_config["name"] exec_cmd("ip link del %s" % dev_name)
@@ -198,7 +212,7 @@ class NetConfigDeviceVlan(NetConfigDeviceGeneric): vlan_tci = int(get_option(config, "vlan_tci")) return dev_name, realdev_name, vlan_tci
- def configure(self): + def create(self): dev_name, realdev_name, vlan_tci = self._get_vlan_info() if self._check_ip_link_add(): exec_cmd("ip link add link %s %s type vlan id %d" @@ -210,7 +224,7 @@ class NetConfigDeviceVlan(NetConfigDeviceGeneric): raise Exception("Bad vlan device name") exec_cmd("vconfig add %s %d" % (realdev_name, vlan_tci))
- def deconfigure(self): + def destroy(self): dev_name = self._get_vlan_info()[0] if self._check_ip_link_add(): exec_cmd("ip link del %s" % dev_name) @@ -252,9 +266,7 @@ class NetConfigDeviceTeam(NetConfigDeviceGeneric): port_dev = self._if_manager.get_mapped_device(slave_id) port_dev.up()
- def configure(self): - self._ports_down() - + def create(self): teamd_config = get_option(self._dev_config, "teamd_config") teamd_config = prepare_json_str(teamd_config)
@@ -263,6 +275,13 @@ class NetConfigDeviceTeam(NetConfigDeviceGeneric): dbus_option = " -D" if self._should_enable_dbus() else "" exec_cmd("teamd -r -d -c "%s" -t %s %s" % (teamd_config, dev_name, dbus_option))
+ def destroy(self): + dev_name = self._dev_config["name"] + exec_cmd("teamd -k -t %s" % dev_name) + + def configure(self): + self._ports_down() + for slave_id in get_slaves(self._dev_config): self.slave_add(slave_id) self._ports_up() @@ -271,10 +290,6 @@ class NetConfigDeviceTeam(NetConfigDeviceGeneric): for slave_id in get_slaves(self._dev_config): self.slave_del(slave_id)
- dev_name = self._dev_config["name"] - - exec_cmd("teamd -k -t %s" % dev_name) - def slave_add(self, slave_id): dev_name = self._dev_config["name"] port_dev = self._if_manager.get_mapped_device(slave_id) @@ -359,25 +374,26 @@ class NetConfigDeviceOvsBridge(NetConfigDeviceGeneric): for bond_id, bond in bonds.iteritems(): exec_cmd("ovs-vsctl del-port %s %s" % (br_name, bond_id))
- def configure(self): + def create(self): dev_cfg = self._dev_config - br_name = dev_cfg["name"] exec_cmd("ovs-vsctl add-br %s" % br_name)
+ def destroy(self): + dev_cfg = self._dev_config + br_name = dev_cfg["name"] + exec_cmd("ovs-vsctl del-br %s" % br_name) + + def configure(self): self._add_ports()
self._add_bonds()
def deconfigure(self): - dev_cfg = self._dev_config - self._del_bonds()
self._del_ports()
- br_name = dev_cfg["name"] - exec_cmd("ovs-vsctl del-br %s" % br_name)
type_class_mapping = { "eth": NetConfigDeviceEth, diff --git a/lnst/Slave/NetTestSlave.py b/lnst/Slave/NetTestSlave.py index d331818..ca714e2 100644 --- a/lnst/Slave/NetTestSlave.py +++ b/lnst/Slave/NetTestSlave.py @@ -161,7 +161,10 @@ class SlaveMethods: return True
def create_soft_interface(self, if_id, config): - return self._if_manager.create_device_from_config(if_id, config) + dev_name = self._if_manager.create_device_from_config(if_id, config) + dev = self._if_manager.get_mapped_device(if_id) + dev.configure() + return dev_name
def deconfigure_interface(self, if_id): device = self._if_manager.get_mapped_device(if_id) diff --git a/lnst/Slave/NmConfigDevice.py b/lnst/Slave/NmConfigDevice.py index e07c0ba..718bd9f 100644 --- a/lnst/Slave/NmConfigDevice.py +++ b/lnst/Slave/NmConfigDevice.py @@ -105,6 +105,12 @@ class NmConfigDeviceGeneric(object): def deconfigure(self): pass
+ def create(self): + pass + + def destroy(self): + pass + def slave_add(self, slave_id): pass
From: Ondrej Lichtner olichtne@redhat.com
This patch adds the classes NetConfigDeviceVEth and NmConfigDeviceVEth. That represent configuration of veth type devices. Since NM doesn't support veth devices, the NmConfigDeviceVEth class just to defines the is_nm_managed method.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Slave/NetConfigDevice.py | 22 +++++++++++++++++++++- lnst/Slave/NmConfigDevice.py | 9 ++++++++- 2 files changed, 29 insertions(+), 2 deletions(-)
diff --git a/lnst/Slave/NetConfigDevice.py b/lnst/Slave/NetConfigDevice.py index d64c471..2a15a82 100644 --- a/lnst/Slave/NetConfigDevice.py +++ b/lnst/Slave/NetConfigDevice.py @@ -394,6 +394,25 @@ class NetConfigDeviceOvsBridge(NetConfigDeviceGeneric):
self._del_ports()
+class NetConfigDeviceVEth(NetConfigDeviceGeneric): + _modulename = "" + _moduleload = False + + def create(self): + conf = self._dev_config + exec_cmd("ip link add %s type veth peer name %s" % (conf["name"], + conf["peer_name"])) + + def destroy(self): + conf = self._dev_config + exec_cmd("ip link del %s" % conf["name"]) + + def configure(self): + #no configuration options supported at the moment + return True + + def deconfigure(self): + return True
type_class_mapping = { "eth": NetConfigDeviceEth, @@ -402,7 +421,8 @@ type_class_mapping = { "macvlan": NetConfigDeviceMacvlan, "vlan": NetConfigDeviceVlan, "team": NetConfigDeviceTeam, - "ovs_bridge": NetConfigDeviceOvsBridge + "ovs_bridge": NetConfigDeviceOvsBridge, + "veth": NetConfigDeviceVEth }
def NetConfigDevice(dev_config, if_manager): diff --git a/lnst/Slave/NmConfigDevice.py b/lnst/Slave/NmConfigDevice.py index 718bd9f..37251e9 100644 --- a/lnst/Slave/NmConfigDevice.py +++ b/lnst/Slave/NmConfigDevice.py @@ -805,6 +805,12 @@ class NmConfigDeviceOvsBridge(NmConfigDeviceGeneric):
return managed
+class NmConfigDeviceVEth(NmConfigDeviceGeneric): + #Not supported by NetworkManager + @classmethod + def is_nm_managed(cls, dev_config, if_manager): + return False + type_class_mapping = { "eth": NmConfigDeviceEth, "bond": NmConfigDeviceBond, @@ -812,7 +818,8 @@ type_class_mapping = { "macvlan": NmConfigDeviceMacvlan, "vlan": NmConfigDeviceVlan, "team": NmConfigDeviceTeam, - "ovs_bridge": NmConfigDeviceOvsBridge + "ovs_bridge": NmConfigDeviceOvsBridge, + "veth": NmConfigDeviceVEth }
def is_nm_managed(dev_config, if_manager):
From: Ondrej Lichtner olichtne@redhat.com
These two new methods are used to create paired interfaces, such as veth. Since these devices are created together LNST representation of the devices needs to reflect that. After device creation the method also takes care of moving the devices to the correct network namespaces.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Slave/InterfaceManager.py | 36 +++++++++++++++++ lnst/Slave/NetTestSlave.py | 89 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+)
diff --git a/lnst/Slave/InterfaceManager.py b/lnst/Slave/InterfaceManager.py index 3695fe2..e30b52a 100644 --- a/lnst/Slave/InterfaceManager.py +++ b/lnst/Slave/InterfaceManager.py @@ -156,6 +156,24 @@ class InterfaceManager(object): self._tmp_mapping[if_id] = device return config["name"]
+ def create_device_pair(self, if_id1, config1, if_id2, config2): + name1, name2 = self.assign_name(config1) + config1["name"] = name1 + config2["name"] = name2 + config1["peer_name"] = name2 + config2["peer_name"] = name1 + + device1 = Device(self) + device2 = Device(self) + + device1.set_configuration(config1) + device2.set_configuration(config2) + device1.create() + + self._tmp_mapping[if_id1] = device1 + self._tmp_mapping[if_id2] = device2 + return name1, name2 + def _is_name_used(self, name): for device in self._devices.itervalues(): if name == device.get_name(): @@ -168,6 +186,16 @@ class InterfaceManager(object): index += 1 return prefix + str(index)
+ def _assign_name_pair(self, prefix): + index1 = 0 + index2 = 0 + while (self._is_name_used(prefix + str(index1))): + index1 += 1 + index2 = index1 + 1 + while (self._is_name_used(prefix + str(index2))): + index2 += 1 + return prefix + str(index1), prefix + str(index2) + def assign_name(self, config): if "name" in config: return config["name"] @@ -193,6 +221,10 @@ class InterfaceManager(object): vlan_tci = get_option(config, "vlan_tci") prefix = "%s.%s_" % (netdev_name, vlan_tci) return self._assign_name_generic(prefix) + elif dev_type == "veth": + return self._assign_name_pair("veth") + else: + return self._assign_name_generic("dev")
class Device(object): def __init__(self, if_manager): @@ -306,6 +338,10 @@ class Device(object): def get_configuration(self): return self._conf
+ def del_configuration(self): + self._conf = None + self._conf_dict = None + def clear_configuration(self): if self._master["primary"]: primary_id = self._master["primary"] diff --git a/lnst/Slave/NetTestSlave.py b/lnst/Slave/NetTestSlave.py index ca714e2..d2f7834 100644 --- a/lnst/Slave/NetTestSlave.py +++ b/lnst/Slave/NetTestSlave.py @@ -166,6 +166,95 @@ class SlaveMethods: dev.configure() return dev_name
+ def create_if_pair(self, if_id1, config1, if_id2, config2): + dev_names = self._if_manager.create_device_pair(if_id1, config1, + if_id2, config2) + dev1 = self._if_manager.get_mapped_device(if_id1) + dev2 = self._if_manager.get_mapped_device(if_id2) + + while dev1.get_if_index() == None and dev2.get_if_index() == None: + msgs = self._server_handler.get_messages_from_con('netlink') + for msg in msgs: + self._if_manager.handle_netlink_msgs(msg[1]["data"]) + + if config1["netns"] != None: + hwaddr = dev1.get_hwaddr() + self.set_if_netns(if_id1, config1["netns"]) + + msg = {"type": "command", "method_name": "map_if_by_hwaddr", + "args": [if_id1, hwaddr]} + self._server_handler.send_data_to_netns(config1["netns"], msg) + result = self._slave_server.wait_for_result(config1["netns"]) + if len(result["result"]) != 1: + raise Exception("Mapping failed.") + + msg = {"type": "command", "method_name": "configure_interface", + "args": [if_id1, config1]} + self._server_handler.send_data_to_netns(config1["netns"], msg) + result = self._slave_server.wait_for_result(config1["netns"]) + if result["result"] != True: + raise Exception("Configuration failed.") + else: + dev1.configure() + if config2["netns"] != None: + hwaddr = dev2.get_hwaddr() + self.set_if_netns(if_id2, config2["netns"]) + + msg = {"type": "command", "method_name": "map_if_by_hwaddr", + "args": [if_id2, hwaddr]} + self._server_handler.send_data_to_netns(config2["netns"], msg) + result = self._slave_server.wait_for_result(config2["netns"]) + if len(result["result"]) != 1: + raise Exception("Mapping failed.") + + msg = {"type": "command", "method_name": "configure_interface", + "args": [if_id2, config2]} + self._server_handler.send_data_to_netns(config2["netns"], msg) + result = self._slave_server.wait_for_result(config2["netns"]) + if result["result"] != True: + raise Exception("Configuration failed.") + else: + dev2.configure() + return dev_names + + def deconfigure_if_pair(self, if_id1, if_id2): + dev1 = self._if_manager.get_mapped_device(if_id1) + dev2 = self._if_manager.get_mapped_device(if_id2) + + if dev1.get_netns() == None: + dev1.deconfigure() + else: + netns = dev1.get_netns() + + msg = {"type": "command", "method_name": "deconfigure_interface", + "args": [if_id1]} + self._server_handler.send_data_to_netns(netns, msg) + result = self._slave_server.wait_for_result(netns) + if result["result"] != True: + raise Exception("Deconfiguration failed.") + + self.return_if_netns(if_id1) + + if dev2.get_netns() == None: + dev2.deconfigure() + else: + netns = dev2.get_netns() + + msg = {"type": "command", "method_name": "deconfigure_interface", + "args": [if_id2]} + self._server_handler.send_data_to_netns(netns, msg) + result = self._slave_server.wait_for_result(netns) + if result["result"] != True: + raise Exception("Deconfiguration failed.") + + self.return_if_netns(if_id2) + + dev1.destroy() + dev2.destroy() + dev1.del_configuration() + dev2.del_configuration() + return True + def deconfigure_interface(self, if_id): device = self._if_manager.get_mapped_device(if_id) device.clear_configuration()
From: Ondrej Lichtner olichtne@redhat.com
This patch adds support for configuring veth interfaces from the Interface class and its subclass SoftInterface. The most important changes are in the configure and deconfigure methods.
When configuring a veth interface we call a new slave method 'create_if_pair' that creates and configures both of the paired interfaces. The first of the configured interfaces also marks the second one as configured so it skiped.
Deconfiguration follows a similar pattern by calling the 'deconfigure_if_pair' method.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com --- lnst/Controller/Machine.py | 36 +++++++++++++++++++++++++++++++----- lnst/Controller/NetTestController.py | 3 +++ 2 files changed, 34 insertions(+), 5 deletions(-)
diff --git a/lnst/Controller/Machine.py b/lnst/Controller/Machine.py index 7bacad1..eefb3aa 100644 --- a/lnst/Controller/Machine.py +++ b/lnst/Controller/Machine.py @@ -458,6 +458,7 @@ class Interface(object): self._ovs_conf = None
self._netns = None + self._peer = None
def get_id(self): return self._id @@ -539,6 +540,12 @@ class Interface(object): def get_netns(self): return self._netns
+ def set_peer(self, peer): + self._peer = peer + + def get_peer(self): + return self._peer + def get_prefix(self, num): try: return self._addresses[num].split('/')[1] @@ -551,7 +558,8 @@ class Interface(object): "options": self._options, "slave_options": self._slave_options, "master": None, "other_masters": [], - "ovs_conf": self._ovs_conf, "netns": self._netns} + "ovs_conf": self._ovs_conf, "netns": self._netns, + "peer": self._peer}
if self._master["primary"] != None: config["master"] = self._master["primary"].get_id() @@ -736,14 +744,23 @@ class SoftInterface(Interface):
def configure(self): if self._configured: - msg = "Unable to configure interface %s on machine %s. " \ - "It has been configured already." % (self.get_id(), - self._machine.get_id()) - raise MachineError(msg) + return
logging.info("Configuring interface %s on machine %s", self.get_id(), self._machine.get_id())
+ if self._type == "veth": + peer_if = self._machine.get_interface(self._peer) + peer_config = peer_if._get_config() + dev_name, peer_name = self._machine._rpc_call("create_if_pair", + self._id, self._get_config(), + self._peer, peer_config) + self.set_devname(dev_name) + peer_if.set_devname(peer_name) + self._configured = True + peer_if._configured = True + return + if self._netns != None: dev_name = self._machine._rpc_call_to_netns(self._netns, "create_soft_interface", self._id, self._get_config()) @@ -757,6 +774,15 @@ class SoftInterface(Interface): if not self._configured: return
+ if self._type == "veth": + peer_if = self._machine.get_interface(self._peer) + + self._machine._rpc_call("deconfigure_if_pair", self._id, self._peer) + + self._configured = False + peer_if._configured = False + return + if self._netns != None: self._machine._rpc_call_to_netns(self._netns, "deconfigure_interface", self.get_id()) diff --git a/lnst/Controller/NetTestController.py b/lnst/Controller/NetTestController.py index f62ca45..f3cdc60 100644 --- a/lnst/Controller/NetTestController.py +++ b/lnst/Controller/NetTestController.py @@ -310,6 +310,9 @@ class NetTestController: if iface_xml_data["netns"] != None: iface.set_netns(iface_xml_data["netns"])
+ if "peer" in iface_xml_data: + iface.set_peer(iface_xml_data["peer"]) + def _prepare_tasks(self): self._tasks = [] for task_data in self._recipe["tasks"]:
Patchset applied. Thanks!
Thu, Aug 21, 2014 at 02:25:25PM CEST, olichtne@redhat.com wrote:
From: Ondrej Lichtner olichtne@redhat.com
The following set of patches further improves network namespace support in LNST.
Most of the patch set implements support for veth devices. Since veth devices are always created in pairs they're represented a bit differently in the recipe XML - by encompasing two <veth> tags within a <veth_pair> tag.
The first patch of the set also takes care of remounting sysfs on namespace creation which makes it usable for configuration again.
Example recipe with veth devices:
<lnstrecipe> <network> <host id="testmachine1"> <interfaces> <eth id="out_if" label="tnet"/> <veth_pair> <veth id="testifc1" netns="xyz"> <addresses> <address value="192.168.200.2/24"/> </addresses> </veth> <veth id="testifc2"/> </veth_pair> <bridge id="br"> <slaves> <slave id="out_if"/> <slave id="testifc2"/> </slaves> </bridge> </interfaces> </host>
<host id="testmachine2"> <interfaces> <eth id="testifc2" label="tnet"> <addresses> <address value="192.168.200.3/24"/> </addresses> </eth> </interfaces> </host></network>
<task> <run host="testmachine1" module="IcmpPing" netns="xyz"> <options> <option name="addr" value="{ip(testmachine2,testifc2)}"/> <option name="count" value="40"/> <option name="interval" value="1"/> </options> </run> </task> </lnstrecipe>
Ondrej Lichtner (10): NetTestSlave: remount sysfs in new net namespace ConnectionHandler: check specific connections schema-recipe: add support for veth interfaces RecipeParser: support parsing veth interfaces NetTestSlave: extend communication options between namespaces NetTestSlave: make return_if_netns method recursive *ConfigDevice: split device creation and configuration *ConfigDevice: add VEth classes NetTestSlave: add methods {create, deconfigure}_if_pair Machine: Interface class veth support
lnst/Common/ConnectionHandler.py | 8 +- lnst/Controller/Machine.py | 39 +++++++-- lnst/Controller/NetTestController.py | 3 + lnst/Controller/RecipeParser.py | 16 +++- lnst/Slave/InterfaceManager.py | 57 ++++++++++++- lnst/Slave/NetConfigDevice.py | 108 ++++++++++++++---------- lnst/Slave/NetTestSlave.py | 155 +++++++++++++++++++++++++++++++++-- lnst/Slave/NmConfigDevice.py | 15 +++- schema-recipe.rng | 36 ++++++++ 9 files changed, 369 insertions(+), 68 deletions(-)
-- 1.9.3
LNST-developers mailing list LNST-developers@lists.fedorahosted.org https://lists.fedorahosted.org/mailman/listinfo/lnst-developers
lnst-developers@lists.fedorahosted.org