Hi,
This patchset add switchdev recipes for layer 2 multicast. The pacthset starts introducing support with named namespaces. Using named namespace allow easy debugging, a user can simply execute "ip netns exec <ns> bash" and see what is been configured inside the namespace.
The link_stat() command, or "ip -s link" command is used to understand how many packets were send and receive over the interface. It's been used in the Multicast recipes to check selective traffic only for the joined networks. In addition a second patch automatically adds "ip -s link" on all interfaces and debug display the information. It's also useful for debugging connection and understand where the faults are.
The "set_addresses" is used to on the fly assign IP address without "resetting" interface (which kills veth paris).
I'm preparing a follow-up patch which will add Layer 3 routing support.
Elad Raz (10): NetTestSlave: Adding named namespace to netns InterfaceAPI: Adding get_netns() InterfaceAPI: Adding link_stat() command InterfaceAPI: Adding set_addresses() command InterfaceAPI: Adding slave route support Controller: Dump interface statistics at exit switchdev: TestLib: Adding support for netns switchdev: TestLib: changing _generate_default_desc() to be multi-iface switchdev: TestLib: Adding iperf_mc() command recipes: switchdev: Adding multicast recipe
lnst/Controller/Machine.py | 34 +++++++++ lnst/Controller/Task.py | 15 ++++ lnst/Slave/InterfaceManager.py | 38 ++++++++++ lnst/Slave/NetConfigDevice.py | 3 + lnst/Slave/NetTestSlave.py | 62 +++++++++++++++- recipes/switchdev/TestLib.py | 128 +++++++++++++++++++++++++++++--- recipes/switchdev/l2-020-bridge_mdb.py | 50 +++++++++++++ recipes/switchdev/l2-020-bridge_mdb.xml | 70 +++++++++++++++++ 8 files changed, 385 insertions(+), 15 deletions(-) create mode 100644 recipes/switchdev/l2-020-bridge_mdb.py create mode 100644 recipes/switchdev/l2-020-bridge_mdb.xml
When LNST creates a namespace it will mount a named namespace in order to ease debuggin feature (e.g. use "ip netns exec <named> ...)
Signed-off-by: Elad Raz eladr@mellanox.com Signed-off-by: Jiri Pirko jiri@mellanox.com --- lnst/Slave/NetTestSlave.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-)
diff --git a/lnst/Slave/NetTestSlave.py b/lnst/Slave/NetTestSlave.py index 3b8bf79..a79ad60 100644 --- a/lnst/Slave/NetTestSlave.py +++ b/lnst/Slave/NetTestSlave.py @@ -13,7 +13,7 @@ jpirko@redhat.com (Jiri Pirko)
import signal import logging -import os +import os, stat import sys, traceback import datetime import socket @@ -609,12 +609,25 @@ class SlaveMethods: CLONE_NEWNS = 0x00020000 #based on ipnetns.c from the iproute2 project MNT_DETACH = 0x00000002 + MS_BIND = 4096 MS_SLAVE = 1<<19 MS_REC = 16384 - libc = ctypes.CDLL(libc_name) - libc.unshare(CLONE_NEWNET) + #based on ipnetns.c from the iproute2 project + #bind to named namespace + netns_path = "/var/run/netns/" + if not os.path.exists(netns_path): + os.mkdir(netns_path, stat.S_IRWXU | stat.S_IRGRP | + stat.S_IXGRP | stat.S_IROTH | + stat.S_IXOTH) + netns_path = netns_path + netns + f = os.open(netns_path, os.O_RDONLY | os.O_CREAT | os.O_EXCL, 0) + os.close(f) + libc.unshare(CLONE_NEWNET) + libc.mount("/proc/self/ns/net", netns_path, "none", MS_BIND, 0) + + #map network sysfs to new net libc.unshare(CLONE_NEWNS) libc.mount("", "/", "none", MS_SLAVE | MS_REC, 0) libc.umount2("/sys", MNT_DETACH) @@ -649,6 +662,18 @@ class SlaveMethods: logging.debug("Network namespace %s doesn't exist." % netns) return False else: + MNT_DETACH = 0x00000002 + libc_name = ctypes.util.find_library("c") + libc = ctypes.CDLL(libc_name) + netns_path = "/var/run/netns/" + netns + + # Remove named namespace + try: + libc.umount2(netns_path, MNT_DETACH) + os.unlink(netns_path) + except: + logging.warning("Unable to remove named namespace %s." % netns_path) + netns_pid = self._net_namespaces[netns]["pid"] os.kill(netns_pid, signal.SIGUSR1) os.waitpid(netns_pid, 0)
Signed-off-by: Elad Raz eladr@mellanox.com Signed-off-by: Jiri Pirko jiri@mellanox.com --- lnst/Controller/Task.py | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/lnst/Controller/Task.py b/lnst/Controller/Task.py index 54c9d3f..1a58b91 100644 --- a/lnst/Controller/Task.py +++ b/lnst/Controller/Task.py @@ -527,6 +527,9 @@ class InterfaceAPI(object): def get_host(self): return self._host
+ def get_netns(self): + return self._if.get_netns() + def reset(self, ip=None, netns=None): self._if.down() self._if.deconfigure()
Implemeting new rpc-command "link_stat" and implement it in both Slave/InterfaceManager and in Controller/Interface.
The link_stat invoke a call to "ip -s link show <iface>" and parse the statistics into a dictionary.
Signed-off-by: Elad Raz eladr@mellanox.com Signed-off-by: Jiri Pirko jiri@mellanox.com --- lnst/Controller/Machine.py | 5 +++++ lnst/Controller/Task.py | 3 +++ lnst/Slave/InterfaceManager.py | 26 ++++++++++++++++++++++++++ lnst/Slave/NetTestSlave.py | 7 +++++++ 4 files changed, 41 insertions(+)
diff --git a/lnst/Controller/Machine.py b/lnst/Controller/Machine.py index 5d8db7d..3c3bc1f 100644 --- a/lnst/Controller/Machine.py +++ b/lnst/Controller/Machine.py @@ -685,6 +685,11 @@ class Interface(object): self._mtu = mtu return self._mtu
+ def link_stats(self): + stats = self._machine._rpc_call_x(self._netns, "link_stats", + self._id) + return stats + def update_from_slave(self): if_data = self._machine._rpc_call_x(self._netns, "get_if_data", self._id) diff --git a/lnst/Controller/Task.py b/lnst/Controller/Task.py index 1a58b91..fb5966f 100644 --- a/lnst/Controller/Task.py +++ b/lnst/Controller/Task.py @@ -518,6 +518,9 @@ class InterfaceAPI(object): def set_mtu(self, mtu): return self._if.set_mtu(mtu)
+ def link_stats(self): + return self._if.link_stats() + def set_link_up(self): return self._if.set_link_up()
diff --git a/lnst/Slave/InterfaceManager.py b/lnst/Slave/InterfaceManager.py index edeb5bc..ebb76c2 100644 --- a/lnst/Slave/InterfaceManager.py +++ b/lnst/Slave/InterfaceManager.py @@ -521,6 +521,32 @@ class Device(object): def link_down(self): exec_cmd("ip link set %s down" % self._name)
+ def link_stats(self): + stats = {"devname": self._name, + "hwaddr": self._hwaddr} + out, _ = exec_cmd("ip -s link show %s" % self._name) + lines = iter(out.split("\n")) + for line in lines: + if (len(line.split()) == 0): + continue + if (line.split()[0] == "RX:"): + rx_stats = map(int, lines.next().split()) + stats.update({"rx_bytes" : rx_stats[0], + "rx_packets": rx_stats[1], + "rx_errors" : rx_stats[2], + "rx_dropped": rx_stats[3], + "rx_overrun": rx_stats[4], + "rx_mcast" : rx_stats[5]}) + if (line.split()[0] == "TX:"): + tx_stats = map(int, lines.next().split()) + stats.update({"tx_bytes" : tx_stats[0], + "tx_packets": tx_stats[1], + "tx_errors" : tx_stats[2], + "tx_dropped": tx_stats[3], + "tx_carrier": tx_stats[4], + "tx_collsns": tx_stats[5]}) + return stats + def set_netns(self, netns): self._netns = netns return diff --git a/lnst/Slave/NetTestSlave.py b/lnst/Slave/NetTestSlave.py index a79ad60..127542a 100644 --- a/lnst/Slave/NetTestSlave.py +++ b/lnst/Slave/NetTestSlave.py @@ -186,6 +186,13 @@ class SlaveMethods: return None return dev.get_if_data()
+ def link_stats(self, if_id): + dev = self._if_manager.get_mapped_device(if_id) + if dev is None: + logging.error("Device with id '%s' not found." % if_id) + return None + return dev.link_stats() + def set_device_up(self, if_id): dev = self._if_manager.get_mapped_device(if_id) dev.up()
The set_address() command changes at run-time an interface IP addresses. The command flushes all previous addresses and set new ones.
Signed-off-by: Elad Raz eladr@mellanox.com Signed-off-by: Jiri Pirko jiri@mellanox.com --- lnst/Controller/Machine.py | 5 +++++ lnst/Controller/Task.py | 3 +++ lnst/Slave/InterfaceManager.py | 6 ++++++ lnst/Slave/NetConfigDevice.py | 3 +++ lnst/Slave/NetTestSlave.py | 8 ++++++++ 5 files changed, 25 insertions(+)
diff --git a/lnst/Controller/Machine.py b/lnst/Controller/Machine.py index 3c3bc1f..8b9fa0f 100644 --- a/lnst/Controller/Machine.py +++ b/lnst/Controller/Machine.py @@ -690,6 +690,11 @@ class Interface(object): self._id) return stats
+ def set_addresses(self, ips): + self._addresses = ips + self._machine._rpc_call_x(self._netns, "set_addresses", + self._id, ips) + def update_from_slave(self): if_data = self._machine._rpc_call_x(self._netns, "get_if_data", self._id) diff --git a/lnst/Controller/Task.py b/lnst/Controller/Task.py index fb5966f..b1a17be 100644 --- a/lnst/Controller/Task.py +++ b/lnst/Controller/Task.py @@ -546,6 +546,9 @@ class InterfaceAPI(object): self._if.configure() self._if.up()
+ def set_addresses(self, ips): + self._if.set_addresses(ips) + def destroy(self): self._host._remove_iface(self)
diff --git a/lnst/Slave/InterfaceManager.py b/lnst/Slave/InterfaceManager.py index ebb76c2..6ddbe52 100644 --- a/lnst/Slave/InterfaceManager.py +++ b/lnst/Slave/InterfaceManager.py @@ -547,6 +547,12 @@ class Device(object): "tx_collsns": tx_stats[5]}) return stats
+ def set_addresses(self, ips): + self._conf.set_addresses(ips) + exec_cmd("ip addr flush %s" % self._name) + for address in ips: + exec_cmd("ip addr add %s dev %s" % (address, self._name)) + def set_netns(self, netns): self._netns = netns return diff --git a/lnst/Slave/NetConfigDevice.py b/lnst/Slave/NetConfigDevice.py index dd053a6..8290772 100644 --- a/lnst/Slave/NetConfigDevice.py +++ b/lnst/Slave/NetConfigDevice.py @@ -68,6 +68,9 @@ class NetConfigDeviceGeneric(object): die_on_err=False) exec_cmd("ip link set %s down" % config["name"])
+ def set_addresses(self, ips): + self._dev_config["addresses"] = ips + @classmethod def type_init(self): if self._modulename and self._moduleload: diff --git a/lnst/Slave/NetTestSlave.py b/lnst/Slave/NetTestSlave.py index 127542a..5eeb30c 100644 --- a/lnst/Slave/NetTestSlave.py +++ b/lnst/Slave/NetTestSlave.py @@ -193,6 +193,14 @@ class SlaveMethods: return None return dev.link_stats()
+ def set_addresses(self, if_id, ips): + dev = self._if_manager.get_mapped_device(if_id) + if dev is None: + logging.error("Device with id '%s' not found." % if_id) + return False + dev.set_addresses(ips) + return True + def set_device_up(self, if_id): dev = self._if_manager.get_mapped_device(if_id) dev.up()
Adding add_route() and del_route APIs allowing inserting and removing routes from the Slave's routing-table.
This patch supports enable_multicast() and disable_multicast() APIs which add local routes for all MC groups to go via a device. This will allow a device to send Multicast traffic and IGMP join requests. The disable_multicast() remote the local routes.
This patch will be follow with "Contoller: Adding route management" patch which will remember new routes and destory them at "deconfigure" stage.
Signed-off-by: Elad Raz eladr@mellanox.com Signed-off-by: Jiri Pirko jiri@mellanox.com --- lnst/Controller/Machine.py | 8 ++++++++ lnst/Controller/Task.py | 6 ++++++ lnst/Slave/InterfaceManager.py | 6 ++++++ lnst/Slave/NetTestSlave.py | 16 ++++++++++++++++ 4 files changed, 36 insertions(+)
diff --git a/lnst/Controller/Machine.py b/lnst/Controller/Machine.py index 8b9fa0f..5363d1c 100644 --- a/lnst/Controller/Machine.py +++ b/lnst/Controller/Machine.py @@ -695,6 +695,14 @@ class Interface(object): self._machine._rpc_call_x(self._netns, "set_addresses", self._id, ips)
+ def add_route(self, dest): + self._machine._rpc_call_x(self._netns, "add_route", + self._id, dest) + + def del_route(self, dest): + self._machine._rpc_call_x(self._netns, "del_route", + self._id, dest) + def update_from_slave(self): if_data = self._machine._rpc_call_x(self._netns, "get_if_data", self._id) diff --git a/lnst/Controller/Task.py b/lnst/Controller/Task.py index b1a17be..68be1bf 100644 --- a/lnst/Controller/Task.py +++ b/lnst/Controller/Task.py @@ -549,6 +549,12 @@ class InterfaceAPI(object): def set_addresses(self, ips): self._if.set_addresses(ips)
+ def enable_multicast(self): + self._if.add_route("224.0.0.0/4") + + def disable_multicast(self): + self._if.del_route("224.0.0.0/4") + def destroy(self): self._host._remove_iface(self)
diff --git a/lnst/Slave/InterfaceManager.py b/lnst/Slave/InterfaceManager.py index 6ddbe52..c4b9e59 100644 --- a/lnst/Slave/InterfaceManager.py +++ b/lnst/Slave/InterfaceManager.py @@ -553,6 +553,12 @@ class Device(object): for address in ips: exec_cmd("ip addr add %s dev %s" % (address, self._name))
+ def add_route(self, dest): + exec_cmd("ip route add %s dev %s" % (dest, self._name)) + + def del_route(self, dest): + exec_cmd("ip route del %s dev %s" % (dest, self._name)) + def set_netns(self, netns): self._netns = netns return diff --git a/lnst/Slave/NetTestSlave.py b/lnst/Slave/NetTestSlave.py index 5eeb30c..bcc7c3c 100644 --- a/lnst/Slave/NetTestSlave.py +++ b/lnst/Slave/NetTestSlave.py @@ -201,6 +201,22 @@ class SlaveMethods: dev.set_addresses(ips) return True
+ def add_route(self, if_id, dest): + dev = self._if_manager.get_mapped_device(if_id) + if dev is None: + logging.error("Device with id '%s' not found." % if_id) + return False + dev.add_route(dest) + return True + + def del_route(self, if_id, dest): + dev = self._if_manager.get_mapped_device(if_id) + if dev is None: + logging.error("Device with id '%s' not found." % if_id) + return False + dev.del_route(dest) + return True + def set_device_up(self, if_id): dev = self._if_manager.get_mapped_device(if_id) dev.up()
Once all recipes finish execution, get's interface statistics. This feature allows anaylze test execution and find faults easily.
Signed-off-by: Elad Raz eladr@mellanox.com Signed-off-by: Jiri Pirko jiri@mellanox.com --- lnst/Controller/Machine.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
diff --git a/lnst/Controller/Machine.py b/lnst/Controller/Machine.py index 5363d1c..ebdd17a 100644 --- a/lnst/Controller/Machine.py +++ b/lnst/Controller/Machine.py @@ -256,6 +256,19 @@ class Machine(object):
ordered_ifaces = self.get_ordered_interfaces() try: + #dump statistics + for iface in self._interfaces: + # Getting stats only from real interfaces + if isinstance(iface, UnusedInterface): + continue + stats = iface.link_stats() + logging.debug("%s:%s:%s: RX:\t bytes: %d\t packets: %d\t dropped: %d" % + (iface.get_netns(), iface.get_host(), iface.get_id(), + stats["rx_bytes"], stats["rx_packets"], stats["rx_dropped"])) + logging.debug("%s:%s:%s: TX:\t bytes: %d\t packets: %d\t dropped: %d" % + (iface.get_netns(), iface.get_host(), iface.get_id(), + stats["tx_bytes"], stats["tx_packets"], stats["tx_dropped"])) + self._rpc_call("kill_cmds") for netns in self._namespaces: self._rpc_call_to_netns(netns, "kill_cmds") @@ -656,6 +669,9 @@ class Interface(object): def get_netns(self): return self._netns
+ def get_host(self): + return self._machine.get_id() + def set_peer(self, peer): self._peer = peer
Executing tests within netns context
Signed-off-by: Elad Raz eladr@mellanox.com Signed-off-by: Jiri Pirko jiri@mellanox.com --- recipes/switchdev/TestLib.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/recipes/switchdev/TestLib.py b/recipes/switchdev/TestLib.py index 68333d8..c299974 100644 --- a/recipes/switchdev/TestLib.py +++ b/recipes/switchdev/TestLib.py @@ -55,7 +55,7 @@ class TestLib: else: if1.set_link_down()
- m2.run(linkneg_mod, desc=desc) + m2.run(linkneg_mod, desc=desc, netns=if2.get_netns())
def ping_simple(self, if1, if2, fail_expected=False, desc=None, limit_rate=90): @@ -85,10 +85,10 @@ class TestLib: "limit_rate": limit_rate})
if self._ipv in [ 'ipv6', 'both' ]: - m1.run(ping_mod6, fail_expected=fail_expected, desc=desc) + m1.run(ping_mod6, fail_expected=fail_expected, desc=desc, netns=if1.get_netns())
if self._ipv in [ 'ipv4', 'both' ]: - m1.run(ping_mod, fail_expected=fail_expected, desc=desc) + m1.run(ping_mod, fail_expected=fail_expected, desc=desc, netns=if1.get_netns())
def _get_netperf_srv_mod(self, if1, is_ipv6): if is_ipv6: @@ -134,12 +134,12 @@ class TestLib: duration = self._netperf_duration num_parallel = self._netperf_num_parallel
- server_proc = m1.run(self._get_netperf_srv_mod(if1, is_ipv6), bg=True) + server_proc = m1.run(self._get_netperf_srv_mod(if1, is_ipv6), bg=True, netns=if1.get_netns()) self._ctl.wait(2) netperf_cli_mod = self._get_netperf_cli_mod(if1, if2, testname, duration, num_parallel, is_ipv6) - m2.run(netperf_cli_mod, timeout=duration + 10, desc=desc) + m2.run(netperf_cli_mod, timeout=duration + 10, desc=desc, netns=if2.get_netns()) server_proc.intr()
def _netperf(self, if1, if2, testname, desc): @@ -172,7 +172,7 @@ class TestLib: "netdev_name": if1.get_devname(), "pktgen_option": pktgen_option})
- m1.run(pktgen_mod, desc=desc) + m1.run(pktgen_mod, desc=desc, netns=if1.get_netns())
def custom(self, m1, desc, err_msg=None): m1.sync_resources(modules=["Custom"])
Changing _generate_default_desc() API to produce description for multi- interfaces.
Signed-off-by: Elad Raz eladr@mellanox.com Signed-off-by: Jiri Pirko jiri@mellanox.com --- recipes/switchdev/TestLib.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/recipes/switchdev/TestLib.py b/recipes/switchdev/TestLib.py index c299974..acd9509 100644 --- a/recipes/switchdev/TestLib.py +++ b/recipes/switchdev/TestLib.py @@ -21,13 +21,17 @@ class TestLib: if "netperf_num_parallel" in aliases: self._netperf_num_parallel = int(aliases["netperf_num_parallel"])
- def _generate_default_desc(self, if1, if2): - return "from %s->%s to %s->%s" % (if1.get_host().get_id(), if1.get_id(), - if2.get_host().get_id(), if2.get_id()) + def _generate_default_desc(self, if1, ifs): + ret = "from %s->%s to " % (if1.get_host().get_id(), if1.get_id()) + for i in ifs: + ret += "%s->%s" % (i.get_host().get_id(), i.get_id()) + if i != ifs[-1]: + ret += ", " + return ret
def linkneg(self, if1, if2, state, speed=0, timeout=5, desc=None): if not desc: - desc = self._generate_default_desc(if1, if2) + desc = self._generate_default_desc(if1, [if2])
m2 = if2.get_host() m2.sync_resources(modules=["LinkNeg"]) @@ -60,7 +64,7 @@ class TestLib: def ping_simple(self, if1, if2, fail_expected=False, desc=None, limit_rate=90): if not desc: - desc = self._generate_default_desc(if1, if2) + desc = self._generate_default_desc(if1, [if2])
if1.set_mtu(self._mtu) if2.set_mtu(self._mtu) @@ -123,7 +127,7 @@ class TestLib:
def _run_netperf(self, if1, if2, testname, is_ipv6, desc): if not desc: - desc = self._generate_default_desc(if1, if2) + desc = self._generate_default_desc(if1, [if2])
m1 = if1.get_host() m2 = if2.get_host()
The iperf-mc() API execute UDP servers to multicast group (Sends IGMP joins) and send traffic to them. The function check that all hosts recieve the MC traffic and hosts which are bridged but didn't send IGMP join don't get anything.
def iperf_mc(self, sender, listeners, bridged, mc_group)
Where: * sender is the interface that sends multicast packets to mc_group * listeners - A list of interfaces that listens (iperf -s -B) to the MC group * bridged - A list of interfaces which are bridged but aren't listens to that specifc MC list.
Examples can be found in recipes/switchdev/l2-020-bridge_mdb.py file.
Signed-off-by: Elad Raz eladr@mellanox.com Signed-off-by: Jiri Pirko jiri@mellanox.com --- recipes/switchdev/TestLib.py | 100 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+)
diff --git a/recipes/switchdev/TestLib.py b/recipes/switchdev/TestLib.py index acd9509..152e76e 100644 --- a/recipes/switchdev/TestLib.py +++ b/recipes/switchdev/TestLib.py @@ -10,6 +10,7 @@ idosch@mellanox.com (Ido Schimmel) """
from time import sleep +import logging
class TestLib: def __init__(self, ctl, aliases): @@ -20,6 +21,18 @@ class TestLib: self._netperf_duration = int(aliases["netperf_duration"]) if "netperf_num_parallel" in aliases: self._netperf_num_parallel = int(aliases["netperf_num_parallel"]) + if "mc_low_thershold" in aliases: + self._mc_low_thershold = int(aliases["mc_low_thershold"]) + else: + self._mc_low_thershold = 1000 + if "mc_high_thershold" in aliases: + self._mc_high_thershold = int(aliases["mc_high_thershold"]) + else: + self._mc_high_thershold = 10000000 + if "mc_speed" in aliases: + self._mc_speed = int(aliases["mc_speed"]) + else: + self._mc_speed = 1000
def _generate_default_desc(self, if1, ifs): ret = "from %s->%s to " % (if1.get_host().get_id(), if1.get_id()) @@ -162,6 +175,93 @@ class TestLib: def netperf_udp(self, if1, if2, desc=None): self._netperf(if1, if2, "UDP_STREAM", desc)
+ def _get_iperf_srv_mod(self, mc_group): + modules_options = { + "role" : "server", + "bind" : mc_group, + "iperf_opts" : "-u" + } + return self._ctl.get_module("Iperf", options = modules_options) + + def _get_iperf_cli_mod(self, mc_group, duration, speed): + modules_options = { + "role" : "client", + "iperf_server" : mc_group, + "duration" : duration, + "iperf_opts" : "-u -b " + str(speed) + "mb" + } + return self._ctl.get_module("Iperf", options = modules_options) + + def iperf_mc(self, sender, listeners, bridged, mc_group, desc=None): + if not desc: + desc = self._generate_default_desc(sender, listeners) + + sender.set_mtu(self._mtu) + sender.enable_multicast() + map(lambda i:i.enable_multicast(), listeners + bridged) + map(lambda i:i.set_mtu(self._mtu), listeners + bridged) + + sender_host = sender.get_host() + listeners_host = map(lambda i:i.get_host(), listeners) + bridged_host = map(lambda i:i.get_host(), bridged) + + map(lambda i:i.sync_resources(modules=["Iperf"]), + listeners_host + bridged_host) + + duration = self._netperf_duration + speed = self._mc_speed + + # read link-stats + sender_stats = sender.link_stats() + listeners_stats = map(lambda i:i.link_stats(), listeners) + bridged_stats = map(lambda i:i.link_stats(), bridged) + + # Run iperf server for all listeners + srv_m = self._get_iperf_srv_mod(mc_group) + s_procs = map(lambda i:i[0].run(srv_m, bg=True, netns=i[1].get_netns()), + zip(listeners_host, listeners)) + self._ctl.wait(2) + + # An send traffic to all listeners but bridged + cli_m = self._get_iperf_cli_mod(mc_group, duration, speed) + sender_host.run(cli_m, timeout=duration + 10, desc=desc, + netns=sender.get_netns()) + map(lambda i:i.intr(), s_procs) + map(lambda i:i.disable_multicast(), listeners + bridged) + sender.disable_multicast() + + # re-read link-stats + sender_stats1 = sender.link_stats() + listeners_stats1 = map(lambda i:i.link_stats(), listeners) + bridged_stats1 = map(lambda i:i.link_stats(), bridged) + + # Check that listeners got multi cast traffic + tx = sender_stats1["tx_bytes"] - sender_stats["tx_bytes"] + rx = map(lambda i:i[1]["rx_bytes"] - i[0]["rx_bytes"], + zip(listeners_stats, listeners_stats1)) + err = filter(lambda i:i[0] < self._mc_high_thershold, zip(rx, listeners)) + err_str = map(lambda i:("Traffic isn't received for %s:%s count %d" % + (i[1].get_host().get_id(), i[1].get_id(), i[0]), + i[1]), err) + for i in err_str: + self.custom(i[1].get_host(), "iperf_mc", i[0]) + for i in zip(rx, listeners): + logging.info("Measured traffic on %s:%s is %dMb, bytes lost %d (%d%%)" % + (i[1].get_host().get_id(), i[1].get_id(), + i[0] / 1000000, + max(tx - i[0], 0), + (max(tx - i[0], 0) * 100) / tx)) + + # Check that only listeners got traffic + rx = map(lambda i:i[1]["rx_bytes"] - i[0]["rx_bytes"], + zip(bridged_stats, bridged_stats1)) + err = filter(lambda i:i[0] > self._mc_low_thershold, zip(rx, bridged)) + err_str = map(lambda i:("Received unwanted traffic for %s:%s count %d" % + (i[1].get_host().get_id(), i[1].get_id(), i[0]), + i[1]), err) + for i in err_str: + self.custom(i[1].get_host(), "iperf_mc", i[0]) + def pktgen(self, if1, if2, pkt_size, desc=None): if1.set_mtu(self._mtu) m1 = if1.get_host()
Check layer 2 multicast using iperf between multiple hosts and verify packets are been send to the right hosts.
Signed-off-by: Elad Raz eladr@mellanox.com Signed-off-by: Jiri Pirko jiri@mellanox.com --- recipes/switchdev/l2-020-bridge_mdb.py | 50 +++++++++++++++++++++++ recipes/switchdev/l2-020-bridge_mdb.xml | 70 +++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 recipes/switchdev/l2-020-bridge_mdb.py create mode 100644 recipes/switchdev/l2-020-bridge_mdb.xml
diff --git a/recipes/switchdev/l2-020-bridge_mdb.py b/recipes/switchdev/l2-020-bridge_mdb.py new file mode 100644 index 0000000..fdd85e8 --- /dev/null +++ b/recipes/switchdev/l2-020-bridge_mdb.py @@ -0,0 +1,50 @@ +""" +Copyright 2016 Mellanox Technologies. All rights reserved. +Licensed under the GNU General Public License, version 2 as +published by the Free Software Foundation; see COPYING for details. +""" + +__author__ = """ +eladr@mellanox.com (Elad Raz) +jiri@mellanox.com (Jiri Pirko) +""" + +from lnst.Controller.Task import ctl +from TestLib import TestLib +from time import sleep + +def test_ip(major, minor): + return ["192.168.10%d.%d/24" % (major, minor), + "2002:%d::%d/64" % (major, minor)] + +def do_task(ctl, hosts, ifaces, aliases): + m1, m2, sw = hosts + m1_if, m2_if, m3_if, m4_if, sw_if1, sw_if2, sw_if3, sw_if4 = ifaces + + # Create a bridge + sw_br = sw.create_bridge(slaves=[sw_if1, sw_if2, sw_if3, sw_if4], + options={"vlan_filtering": 1}) + + m1_if.set_addresses(["192.168.101.10/24", "2002::1/64"]) + m2_if.set_addresses(["192.168.101.11/24", "2002::2/64"]) + m3_if.set_addresses(["192.168.101.13/24", "2002::3/64"]) + m4_if.set_addresses(["192.168.101.14/24", "2002::4/64"]) + sleep(15) + + tl = TestLib(ctl, aliases) + tl.iperf_mc(m1_if, [m2_if, m4_if], [m3_if], "239.255.1.3") + tl.iperf_mc(m1_if, [m4_if], [], "239.255.1.4") + tl.iperf_mc(m2_if, [m3_if, m4_if, m1_if] , [], "239.255.1.5") + +do_task(ctl, [ctl.get_host("machine1"), + ctl.get_host("machine2"), + ctl.get_host("switch")], + [ctl.get_host("machine1").get_interface("veth1"), + ctl.get_host("machine1").get_interface("veth3"), + ctl.get_host("machine2").get_interface("veth1"), + ctl.get_host("machine2").get_interface("veth3"), + ctl.get_host("switch").get_interface("if1"), + ctl.get_host("switch").get_interface("if2"), + ctl.get_host("switch").get_interface("if3"), + ctl.get_host("switch").get_interface("if4")], + ctl.get_aliases()) diff --git a/recipes/switchdev/l2-020-bridge_mdb.xml b/recipes/switchdev/l2-020-bridge_mdb.xml new file mode 100644 index 0000000..3ff4cfc --- /dev/null +++ b/recipes/switchdev/l2-020-bridge_mdb.xml @@ -0,0 +1,70 @@ +<lnstrecipe xmlns:xi="http://www.w3.org/2003/XInclude"> + <xi:include href="default_aliases.xml" /> + <network> + <host id="machine1"> + <interfaces> + <eth id="if1" label="A" /> + <veth_pair> + <veth id="veth0"/> + <veth id="veth1" netns="nsif1"> + </veth> + </veth_pair> + <bridge id="brif1"> + <slaves> + <slave id="if1"/> + <slave id="veth0"/> + </slaves> + </bridge> + <eth id="if2" label="B" /> + <veth_pair> + <veth id="veth2"/> + <veth id="veth3" netns="nsif2"> + </veth> + </veth_pair> + <bridge id="brif2"> + <slaves> + <slave id="if2"/> + <slave id="veth2"/> + </slaves> + </bridge> + </interfaces> + </host> + <host id="machine2"> + <interfaces> + <eth id="if1" label="C" /> + <veth_pair> + <veth id="veth0"/> + <veth id="veth1" netns="nsif1"> + </veth> + </veth_pair> + <bridge id="brif1"> + <slaves> + <slave id="if1"/> + <slave id="veth0"/> + </slaves> + </bridge> + <eth id="if2" label="D" /> + <veth_pair> + <veth id="veth2"/> + <veth id="veth3" netns="nsif2"> + </veth> + </veth_pair> + <bridge id="brif2"> + <slaves> + <slave id="if2"/> + <slave id="veth2"/> + </slaves> + </bridge> + </interfaces> + </host> + <host id="switch"> + <interfaces> + <eth id="if1" label="A" /> + <eth id="if2" label="B" /> + <eth id="if3" label="C" /> + <eth id="if4" label="D" /> + </interfaces> + </host> + </network> + <task python="l2-020-bridge_mdb.py" /> +</lnstrecipe>
On Sun, May 08, 2016 at 04:18:09PM +0300, Elad Raz wrote:
Hi,
This patchset add switchdev recipes for layer 2 multicast. The pacthset starts introducing support with named namespaces. Using named namespace allow easy debugging, a user can simply execute "ip netns exec <ns> bash" and see what is been configured inside the namespace.
The link_stat() command, or "ip -s link" command is used to understand how many packets were send and receive over the interface. It's been used in the Multicast recipes to check selective traffic only for the joined networks. In addition a second patch automatically adds "ip -s link" on all interfaces and debug display the information. It's also useful for debugging connection and understand where the faults are.
The "set_addresses" is used to on the fly assign IP address without "resetting" interface (which kills veth paris).
I'm preparing a follow-up patch which will add Layer 3 routing support.
Elad Raz (10): NetTestSlave: Adding named namespace to netns InterfaceAPI: Adding get_netns() InterfaceAPI: Adding link_stat() command InterfaceAPI: Adding set_addresses() command InterfaceAPI: Adding slave route support Controller: Dump interface statistics at exit switchdev: TestLib: Adding support for netns switchdev: TestLib: changing _generate_default_desc() to be multi-iface switchdev: TestLib: Adding iperf_mc() command recipes: switchdev: Adding multicast recipe
lnst/Controller/Machine.py | 34 +++++++++ lnst/Controller/Task.py | 15 ++++ lnst/Slave/InterfaceManager.py | 38 ++++++++++ lnst/Slave/NetConfigDevice.py | 3 + lnst/Slave/NetTestSlave.py | 62 +++++++++++++++- recipes/switchdev/TestLib.py | 128 +++++++++++++++++++++++++++++--- recipes/switchdev/l2-020-bridge_mdb.py | 50 +++++++++++++ recipes/switchdev/l2-020-bridge_mdb.xml | 70 +++++++++++++++++ 8 files changed, 385 insertions(+), 15 deletions(-) create mode 100644 recipes/switchdev/l2-020-bridge_mdb.py create mode 100644 recipes/switchdev/l2-020-bridge_mdb.xml
-- 2.4.3 _______________________________________________ LNST-developers mailing list lnst-developers@lists.fedorahosted.org https://lists.fedorahosted.org/admin/lists/lnst-developers@lists.fedorahoste...
applied, thanks
-Ondrej
lnst-developers@lists.fedorahosted.org