[PATCH lnst] Add basic OVS offload tests
by Roi Dayan
We currently only do minimal testing.
OVS offload is done by adding rules to TC which then offloaded to supported
hardware. so the tests start traffic and then look for matching TC rules.
Testing east-west traffic, north-source traffic, VLAN, VXLAN, VAXLN IPv6.
Signed-off-by: Roi Dayan <roid(a)mellanox.com>
---
Hi,
This commit is adding initial basic tests for OVS offload.
The series adding offloading support to OVS is still being discussed
on the ovs-dev mailing list.
The series is also available in github here:
https://github.com/roidayan/ovs/tree/hw-offload-fixes-for-v5
Thanks,
Roi
recipes/ovs_offload/01-basic.README | 21 ++++++
recipes/ovs_offload/01-basic.py | 84 +++++++++++++++++++++++
recipes/ovs_offload/01-basic.xml | 54 +++++++++++++++
recipes/ovs_offload/03-vlan_in_host.README | 33 +++++++++
recipes/ovs_offload/03-vlan_in_host.py | 72 ++++++++++++++++++++
recipes/ovs_offload/03-vlan_in_host.xml | 67 ++++++++++++++++++
recipes/ovs_offload/04-vlan_in_guest.README | 31 +++++++++
recipes/ovs_offload/04-vlan_in_guest.py | 48 +++++++++++++
recipes/ovs_offload/04-vlan_in_guest.xml | 67 ++++++++++++++++++
recipes/ovs_offload/1_virt_ovs_vxlan.README | 41 +++++++++++
recipes/ovs_offload/1_virt_ovs_vxlan.py | 71 +++++++++++++++++++
recipes/ovs_offload/1_virt_ovs_vxlan.xml | 88 ++++++++++++++++++++++++
recipes/ovs_offload/1_virt_ovs_vxlan_ipv6.xml | 89 ++++++++++++++++++++++++
recipes/ovs_offload/Testlib.py | 90 +++++++++++++++++++++++++
14 files changed, 856 insertions(+), 0 deletions(-)
create mode 100644 recipes/ovs_offload/01-basic.README
create mode 100644 recipes/ovs_offload/01-basic.py
create mode 100644 recipes/ovs_offload/01-basic.xml
create mode 100644 recipes/ovs_offload/03-vlan_in_host.README
create mode 100644 recipes/ovs_offload/03-vlan_in_host.py
create mode 100644 recipes/ovs_offload/03-vlan_in_host.xml
create mode 100644 recipes/ovs_offload/04-vlan_in_guest.README
create mode 100644 recipes/ovs_offload/04-vlan_in_guest.py
create mode 100644 recipes/ovs_offload/04-vlan_in_guest.xml
create mode 100644 recipes/ovs_offload/1_virt_ovs_vxlan.README
create mode 100644 recipes/ovs_offload/1_virt_ovs_vxlan.py
create mode 100644 recipes/ovs_offload/1_virt_ovs_vxlan.xml
create mode 100644 recipes/ovs_offload/1_virt_ovs_vxlan_ipv6.xml
create mode 100644 recipes/ovs_offload/Testlib.py
diff --git a/recipes/ovs_offload/01-basic.README b/recipes/ovs_offload/01-basic.README
new file mode 100644
index 0000000..e13c4fe
--- /dev/null
+++ b/recipes/ovs_offload/01-basic.README
@@ -0,0 +1,21 @@
+Topology:
+
++----------------------+
+| |
+| +-----------------+ |
+| | ovs_bridge | |
+| +-+-------------+-+ |
+| | | |
+| +-+-+ +-+-+ |
++-|tap|---------|tap|--+
+ +-+-+ +-+-+
+ | |
+ | |
+ | |
+ +-+-+ +-+-+
++-|nic|--+ +-|nic|--+
+| +---+ | | +---+ |
+| guest1 | | guest2 |
+| | | |
++--------+ +--------+
+
diff --git a/recipes/ovs_offload/01-basic.py b/recipes/ovs_offload/01-basic.py
new file mode 100644
index 0000000..1bbf5a0
--- /dev/null
+++ b/recipes/ovs_offload/01-basic.py
@@ -0,0 +1,84 @@
+from lnst.Controller.Task import ctl
+from Testlib import Testlib
+
+# ------
+# SETUP
+# ------
+
+tl = Testlib(ctl)
+
+h1 = ctl.get_host("host1")
+g1 = ctl.get_host("guest1")
+g2 = ctl.get_host("guest2")
+
+g1.sync_resources(modules=["IcmpPing", "Icmp6Ping", "Iperf"])
+g2.sync_resources(modules=["Iperf"])
+
+# ------
+# TESTS
+# ------
+
+ipv = ctl.get_alias("ipv")
+do_iperf = ctl.get_alias("iperf")
+
+g1_guestnic = g1.get_interface("if1")
+g1_mac = g1_guestnic.get_hwaddr()
+g2_guestnic = g2.get_interface("if1")
+g2_mac = g2_guestnic.get_hwaddr()
+
+ping_count = 30
+ping_interval = 0.2
+ping_timeout = 10
+
+def ping(options={}):
+ options = dict(options)
+ options.update({
+ "addr": g2_guestnic.get_ip(0),
+ "count": ping_count,
+ "iface": g1_guestnic.get_devname(),
+ "interval": ping_interval
+ })
+ ping_mod = ctl.get_module("IcmpPing", options=options)
+ g1.run(ping_mod, timeout=ping_timeout)
+
+
+def ping6(options={}):
+ options = dict(options)
+ options.update({
+ "addr": g2_guestnic.get_ip(1),
+ "count": ping_count,
+ "iface": g1_guestnic.get_ip(1),
+ "interval": ping_interval
+ })
+ ping_mod6 = ctl.get_module("Icmp6Ping", options=options)
+ g1.run(ping_mod6, timeout=ping_timeout)
+
+
+def verify_tc_rules(proto):
+ m = tl.find_tc_rule(h1, 'tap1', g1_mac, g2_mac, proto, 'mirred')
+ if m:
+ tl.custom(h1, "TC rule %s vm1->vm2" % proto, opts=m)
+ else:
+ tl.custom(h1, "TC rule %s vm1->vm2" % proto, 'ERROR: cannot find tc rule')
+
+ m = tl.find_tc_rule(h1, 'tap2', g2_mac, g1_mac, proto, 'mirred')
+ if m:
+ tl.custom(h1, "TC rule %s vm2->vm1" % proto, opts=m)
+ else:
+ tl.custom(h1, "TC rule %s vm2->vm1" % proto, 'ERROR: cannot find tc rule')
+
+
+if ipv in ('ipv4', 'both'):
+ ping()
+ verify_tc_rules('ip')
+ for size in (200, 400, 1000):
+ ping({'size': size})
+
+if ipv in ('ipv6', 'both'):
+ ping6()
+ verify_tc_rules('ipv6')
+ for size in (200, 400, 1000):
+ ping6({'size': size})
+
+if do_iperf:
+ tl.iperf(g1_guestnic, g2_guestnic, 30, 'vm1->vm2')
diff --git a/recipes/ovs_offload/01-basic.xml b/recipes/ovs_offload/01-basic.xml
new file mode 100644
index 0000000..b020781
--- /dev/null
+++ b/recipes/ovs_offload/01-basic.xml
@@ -0,0 +1,54 @@
+<lnstrecipe>
+ <define>
+ <alias name="ipv" value="both" />
+ <alias name="iperf" value="yes" />
+ <alias name="net" value="1.1.1"/>
+ <alias name="net6" value="fc00:0:0:0"/>
+ </define>
+ <network>
+ <host id="guest1">
+ <params>
+ <param name="machine_type" value="guest"/>
+ </params>
+ <interfaces>
+ <eth id="if1" label="A">
+ <addresses>
+ <address>{$net}.10/24</address>
+ <address>{$net6}::10/64</address>
+ </addresses>
+ </eth>
+ </interfaces>
+ </host>
+ <host id="guest2">
+ <params>
+ <param name="machine_type" value="guest"/>
+ </params>
+ <interfaces>
+ <eth id="if1" label="B">
+ <addresses>
+ <address>{$net}.11/24</address>
+ <address>{$net6}::11/64</address>
+ </addresses>
+ </eth>
+ </interfaces>
+ </host>
+ <host id="host1">
+ <params>
+ <param name="ovs_support" value="true"/>
+ <param name="order" value="1"/>
+ </params>
+ <interfaces>
+ <eth id="tap1" label="A"/>
+ <eth id="tap2" label="B"/>
+ <ovs_bridge id="ovs1">
+ <slaves>
+ <slave id="tap1"/>
+ <slave id="tap2"/>
+ </slaves>
+ </ovs_bridge>
+ </interfaces>
+ </host>
+ </network>
+
+ <task python="01-basic.py" />
+</lnstrecipe>
diff --git a/recipes/ovs_offload/03-vlan_in_host.README b/recipes/ovs_offload/03-vlan_in_host.README
new file mode 100644
index 0000000..3b25b7d
--- /dev/null
+++ b/recipes/ovs_offload/03-vlan_in_host.README
@@ -0,0 +1,33 @@
+Topology:
+
+ +----------+
+ | | VLAN10
+ +-----------------+ switch +--------------------+
+ | | | |
+ | +----------+ |
+ | |
+ +-+-+ |
++------|nic|---------+ +-+-+
+| +-+-+ | +------|nic|------+
+| | | | +---+ |
+| | | | |
+| +------+-------+ | | |
+| | vlan10 | | | host2 |
+| | | | | |
+| | ovs_bridge | | | |
+| | | | | |
+| +-+------------+ | +-----------------+
+| | |
+| +-+-+ |
++-|tap|--------------+
+ +-+-+
+ |
+ |
+ |
+ +-+-+
++-|nic|--+
+| +---+ |
+| guest1 |
+| |
++--------+
+
diff --git a/recipes/ovs_offload/03-vlan_in_host.py b/recipes/ovs_offload/03-vlan_in_host.py
new file mode 100644
index 0000000..3a2c19e
--- /dev/null
+++ b/recipes/ovs_offload/03-vlan_in_host.py
@@ -0,0 +1,72 @@
+from lnst.Controller.Task import ctl
+from Testlib import Testlib
+
+# ------
+# SETUP
+# ------
+
+tl = Testlib(ctl)
+
+h1 = ctl.get_host("host1")
+g1 = ctl.get_host("guest1")
+h2 = ctl.get_host("host2")
+
+g1.sync_resources(modules=["IcmpPing", "Icmp6Ping", "Iperf"])
+h2.sync_resources(modules=["Iperf"])
+
+# ------
+# TESTS
+# ------
+
+ipv = ctl.get_alias("ipv")
+do_iperf = ctl.get_alias("iperf")
+
+h2_vlan10 = h2.get_interface("vlan10")
+h2_mac = h2_vlan10.get_hwaddr()
+g1_guestnic = g1.get_interface("guestnic")
+g1_mac = g1_guestnic.get_hwaddr()
+
+ping_count = 100
+ping_interval = 0.2
+
+ping_mod = ctl.get_module("IcmpPing",
+ options={
+ "addr": h2_vlan10.get_ip(0),
+ "count": ping_count,
+ "iface": g1_guestnic.get_devname(),
+ "interval": ping_interval
+ })
+
+ping_mod6 = ctl.get_module("Icmp6Ping",
+ options={
+ "addr": h2_vlan10.get_ip(1),
+ "count": ping_count,
+ "iface": g1_guestnic.get_ip(1),
+ "interval": ping_interval
+ })
+
+
+def verify_tc_rules(proto):
+ m = tl.find_tc_rule(h1, 'tap', g1_mac, h2_mac, proto, 'vlan push')
+ if m:
+ tl.custom(h1, "TC rule %s vlan push" % proto)
+ else:
+ tl.custom(h1, "TC rule %s vlan push" % proto, 'ERROR: cannot find tc rule')
+
+ m = tl.find_tc_rule(h1, 'nic', h2_mac, g1_mac, '802.1Q', 'vlan pop')
+ if m:
+ tl.custom(h1, "TC rule %s vlan pop" % proto)
+ else:
+ tl.custom(h1, "TC rule %s vlan pop" % proto, 'ERROR: cannot find tc rule')
+
+
+if ipv in ('ipv4', 'both'):
+ g1.run(ping_mod)
+ verify_tc_rules('ip')
+
+if ipv in ('ipv6', 'both'):
+ g1.run(ping_mod6)
+ verify_tc_rules('ipv6')
+
+if do_iperf:
+ tl.iperf(g1_guestnic, h2_vlan10, 100, 'vm1->h2')
diff --git a/recipes/ovs_offload/03-vlan_in_host.xml b/recipes/ovs_offload/03-vlan_in_host.xml
new file mode 100644
index 0000000..5f5c8c1
--- /dev/null
+++ b/recipes/ovs_offload/03-vlan_in_host.xml
@@ -0,0 +1,67 @@
+<lnstrecipe>
+ <define>
+ <alias name="ipv" value="both" />
+ <alias name="iperf" value="yes" />
+ <alias name="vlan10_net" value="1.1.1"/>
+ <alias name="vlan10_net6" value="fc00:0:0:0"/>
+ <alias name="vlan10_tag" value="10"/>
+ </define>
+ <network>
+ <host id="host1">
+ <params>
+ <param name="machine_type" value="baremetal"/>
+ <param name="order" value="1"/>
+ </params>
+ <interfaces>
+ <eth id="nic" label="to_switch"/>
+ <eth id="tap" label="to_guest"/>
+ <ovs_bridge id="ovs_br">
+ <slaves>
+ <slave id="tap" />
+ <slave id="nic" />
+ </slaves>
+ <vlan tag="{$vlan10_tag}">
+ <slaves>
+ <slave id="tap"/>
+ </slaves>
+ </vlan>
+ </ovs_bridge>
+ </interfaces>
+ </host>
+ <host id="guest1">
+ <params>
+ <param name="machine_type" value="guest"/>
+ </params>
+ <interfaces>
+ <eth id="guestnic" label="to_guest">
+ <addresses>
+ <address>{$vlan10_net}.10/24</address>
+ <address>{$vlan10_net6}::10/64</address>
+ </addresses>
+ </eth>
+ </interfaces>
+ </host>
+ <host id="host2">
+ <params>
+ <param name="machine_type" value="baremetal"/>
+ </params>
+ <interfaces>
+ <eth id="nic" label="to_switch"/>
+ <vlan id="vlan10">
+ <options>
+ <option name="vlan_tci" value="{$vlan10_tag}" />
+ </options>
+ <slaves>
+ <slave id="nic" />
+ </slaves>
+ <addresses>
+ <address>{$vlan10_net}.11/24</address>
+ <address>{$vlan10_net6}::11/64</address>
+ </addresses>
+ </vlan>
+ </interfaces>
+ </host>
+ </network>
+
+ <task python="03-vlan_in_host.py" />
+</lnstrecipe>
diff --git a/recipes/ovs_offload/04-vlan_in_guest.README b/recipes/ovs_offload/04-vlan_in_guest.README
new file mode 100644
index 0000000..a4bf384
--- /dev/null
+++ b/recipes/ovs_offload/04-vlan_in_guest.README
@@ -0,0 +1,31 @@
+Topology:
+
+ +----------+
+ | | VLAN10
+ +-----------------+ switch +-----------------+
+ | | | |
+ | +----------+ |
+ | |
+ +-+-+ |
++------|nic|------+ +-+-+
+| +-+-+ | +------|nic|------+
+| | | | +---+ |
+| +----+ | | |
+| | | | |
+| +-+---------+ | | |
+| | ovs_bridge| | | host2 |
+| +-+---------+ | | |
+| | host1 | | |
+| +-+-+ | | |
++-|tap|-----------+ | |
+ +-+-+ +-----------------+
+ |
+ |VLAN10
+ |
+ +-+-+
++-|nic|--+
+| +---+ |
+| guest1 |
+| |
++--------+
+
diff --git a/recipes/ovs_offload/04-vlan_in_guest.py b/recipes/ovs_offload/04-vlan_in_guest.py
new file mode 100644
index 0000000..fe66b9a
--- /dev/null
+++ b/recipes/ovs_offload/04-vlan_in_guest.py
@@ -0,0 +1,48 @@
+from lnst.Controller.Task import ctl
+
+# ------
+# SETUP
+# ------
+
+h1 = ctl.get_host("host1")
+g1 = ctl.get_host("guest1")
+h2 = ctl.get_host("host2")
+
+g1.sync_resources(modules=["IcmpPing", "Icmp6Ping"])
+h2.sync_resources(modules=["IcmpPing", "Icmp6Ping"])
+
+# ------
+# TESTS
+# ------
+
+ipv = ctl.get_alias("ipv")
+h2_vlan10 = h2.get_interface("vlan10")
+g1_vlan10 = g1.get_interface("vlan10")
+
+ping_count = 100
+ping_interval = 0.2
+
+ping_mod = ctl.get_module("IcmpPing",
+ options={
+ "addr": h2_vlan10.get_ip(0),
+ "count": ping_count,
+ "iface": g1_vlan10.get_devname(),
+ "interval": ping_interval
+ })
+
+ping_mod6 = ctl.get_module("Icmp6Ping",
+ options={
+ "addr": h2_vlan10.get_ip(1),
+ "count": ping_count,
+ "iface": g1_vlan10.get_ip(1),
+ "interval": ping_interval
+ })
+
+p_opts = "-L %s" % (h2_vlan10.get_ip(0))
+p_opts6 = "-L %s -6" % (h2_vlan10.get_ip(1))
+
+if ipv in ('ipv4', 'both'):
+ g1.run(ping_mod)
+
+if ipv in ('ipv6', 'both'):
+ g1.run(ping_mod6)
diff --git a/recipes/ovs_offload/04-vlan_in_guest.xml b/recipes/ovs_offload/04-vlan_in_guest.xml
new file mode 100644
index 0000000..8b183ec
--- /dev/null
+++ b/recipes/ovs_offload/04-vlan_in_guest.xml
@@ -0,0 +1,67 @@
+<lnstrecipe>
+ <define>
+ <alias name="ipv" value="both" />
+ <alias name="vlan10_net" value="192.168.10"/>
+ <alias name="vlan10_net6" value="fc00:0:0:0"/>
+ <alias name="vlan10_tag" value="10"/>
+ </define>
+ <network>
+ <host id="host1">
+ <params>
+ <param name="machine_type" value="baremetal"/>
+ </params>
+ <interfaces>
+ <eth id="nic" label="to_switch" />
+ <eth id="tap" label="to_guest" />
+ <ovs_bridge id="br">
+ <slaves>
+ <slave id="tap" />
+ <slave id="nic" />
+ </slaves>
+ </ovs_bridge>
+ </interfaces>
+ </host>
+ <host id="guest1">
+ <params>
+ <param name="machine_type" value="guest"/>
+ </params>
+ <interfaces>
+ <eth id="guestnic" label="to_guest" />
+ <vlan id="vlan10">
+ <options>
+ <option name="vlan_tci" value="{$vlan10_tag}" />
+ </options>
+ <slaves>
+ <slave id="guestnic" />
+ </slaves>
+ <addresses>
+ <address>{$vlan10_net}.10/24</address>
+ <address>{$vlan10_net6}::10/64</address>
+ </addresses>
+ </vlan>
+ </interfaces>
+ </host>
+ <host id="host2">
+ <params>
+ <param name="machine_type" value="baremetal"/>
+ </params>
+ <interfaces>
+ <eth id="nic" label="to_switch"/>
+ <vlan id="vlan10">
+ <options>
+ <option name="vlan_tci" value="{$vlan10_tag}" />
+ </options>
+ <slaves>
+ <slave id="nic" />
+ </slaves>
+ <addresses>
+ <address>{$vlan10_net}.11/24</address>
+ <address>{$vlan10_net6}::11/64</address>
+ </addresses>
+ </vlan>
+ </interfaces>
+ </host>
+ </network>
+
+ <task python="04-vlan_in_guest.py" />
+</lnstrecipe>
diff --git a/recipes/ovs_offload/1_virt_ovs_vxlan.README b/recipes/ovs_offload/1_virt_ovs_vxlan.README
new file mode 100644
index 0000000..11a7061
--- /dev/null
+++ b/recipes/ovs_offload/1_virt_ovs_vxlan.README
@@ -0,0 +1,41 @@
+Topology:
+
+ switch
+ +--------+
+ | |
+ +--------------------+ +----------------------+
+ | | | |
+ | | | |
+ | | | |
+ | +--------+ |
+ | |
+ | |
+ +--+--+ +--+--+
++---------| eth |--------+ +---------| eth |--------+
+| +-----+ | | +-----+ |
+| | | |
+| +----------------------------------------------------+ |
+| | | | | |
+| +----------+---------+ | | +----------+---------+ |
+| | vxlan | | | | vxlan | |
+| | | | | | | |
+| | ovs_bridge | | | | ovs_bridge | |
+| |tun_id | | | | | |
+| | 100 | | | | +----+ | |
+| +--+-----------------+ | | +-------|int0|-------+ |
+| | host1 | | +----+ |
+| | | | |
+| | | | host2 |
+| +-+-+ | | |
++--+tap+-----------------+ +------------------------+
+ +-+-+
+ |
+ +-+-+
++--+eth+--+
+| +---+ |
+| |
+| guest1 |
+| |
+| |
++---------+
+
diff --git a/recipes/ovs_offload/1_virt_ovs_vxlan.py b/recipes/ovs_offload/1_virt_ovs_vxlan.py
new file mode 100644
index 0000000..adc4122
--- /dev/null
+++ b/recipes/ovs_offload/1_virt_ovs_vxlan.py
@@ -0,0 +1,71 @@
+from lnst.Controller.Task import ctl
+from lnst.RecipeCommon.ModuleWrap import ping, ping6
+from Testlib import Testlib
+import logging
+
+# ------
+# SETUP
+# ------
+
+tl = Testlib(ctl)
+
+# hosts
+host1 = ctl.get_host("h1")
+host2 = ctl.get_host("h2")
+
+# guest machines
+guest1 = ctl.get_host("test_host1")
+
+guest1.sync_resources(modules=["IcmpPing", "Icmp6Ping"])
+
+# ------
+# TESTS
+# ------
+
+ipv = ctl.get_alias("ipv")
+mtu = ctl.get_alias("mtu")
+
+g1_nic = guest1.get_interface("if1")
+h2_nic = host2.get_device("int0")
+
+vxlan_port = ctl.get_alias("vxlan_port")
+vxlan_dev = "vxlan_sys_%s" % vxlan_port
+
+
+def do_pings():
+ ping_opts = {"count": 100, "interval": 0.2}
+ if ipv in ['ipv4', 'both']:
+ ping((guest1, g1_nic, 0, {"scope": 0}),
+ (host2, h2_nic, 0, {"scope": 0}),
+ options=ping_opts, expect="pass")
+ verify_tc_rules('ip')
+
+ if ipv in ['ipv6', 'both']:
+ ping6((guest1, g1_nic, 1, {"scope": 0}),
+ (host2, h2_nic, 1, {"scope": 0}),
+ options=ping_opts, expect="pass")
+ verify_tc_rules('ipv6')
+
+
+def verify_tc_rules(proto):
+ g1_mac = g1_nic.get_hwaddr()
+ h2_mac = h2_nic.get_hwaddr()
+
+ # encap rule
+ m = tl.find_tc_rule(host1, 'tap1', g1_mac, h2_mac, proto, 'tunnel_key set')
+ desc = "TC rule %s tunnel_key set" % proto
+ if m:
+ tl.custom(host1, desc)
+ else:
+ tl.custom(host1, desc, 'ERROR: cannot find tc rule')
+
+ # decap rule
+ m = tl.find_tc_rule(host1, vxlan_dev, h2_mac, g1_mac, proto, 'tunnel_key unset')
+ desc = "TC rule %s tunnel_key unset" % proto
+ if m:
+ tl.custom(host1, desc)
+ else:
+ tl.custom(host1, desc, 'ERROR: cannot find tc rule')
+
+
+do_pings()
diff --git a/recipes/ovs_offload/1_virt_ovs_vxlan.xml b/recipes/ovs_offload/1_virt_ovs_vxlan.xml
new file mode 100644
index 0000000..b1e3357
--- /dev/null
+++ b/recipes/ovs_offload/1_virt_ovs_vxlan.xml
@@ -0,0 +1,88 @@
+<lnstrecipe>
+ <define>
+ <alias name="ipv" value="both" />
+ <alias name="mtu" value="1450" />
+ <alias name="net" value="77.77.77"/>
+ <alias name="vxlan_net" value="1.1.1"/>
+ <alias name="vxlan_net6" value="fc00:0:0:0"/>
+ <alias name="vxlan_key" value="100"/>
+ <alias name="vxlan_port" value="1478"/>
+ </define>
+ <network>
+ <host id="h1">
+ <params>
+ <param name="machine_type" value="baremetal"/>
+ <param name="order" value="1"/>
+ </params>
+ <interfaces>
+ <eth id="if1" label="n1">
+ <addresses>
+ <address value="{$net}.1/24"/>
+ </addresses>
+ </eth>
+ <eth id="tap1" label="to_guest1"/>
+ <ovs_bridge id="ovs1">
+ <slaves>
+ <slave id="tap1">
+ <options>
+ <option name="ofport_request" value="5"/>
+ </options>
+ </slave>
+ </slaves>
+ <tunnel id="vxlan1" type="vxlan">
+ <options>
+ <option name="option:remote_ip" value="{$net}.2"/>
+ <option name="option:key" value="{$vxlan_key}"/>
+ <option name="option:dst_port" value="{$vxlan_port}"/>
+ <option name="ofport_request" value="10"/>
+ </options>
+ </tunnel>
+ </ovs_bridge>
+ </interfaces>
+ </host>
+ <host id="test_host1">
+ <interfaces>
+ <eth id="if1" label="to_guest1">
+ <addresses>
+ <address value="{$vxlan_net}.1/24"/>
+ <address value="{$vxlan_net6}::1/64"/>
+ </addresses>
+ </eth>
+ </interfaces>
+ </host>
+ <host id="h2">
+ <params>
+ <param name="machine_type" value="baremetal"/>
+ </params>
+ <interfaces>
+ <eth id="if1" label="n1">
+ <addresses>
+ <address value="{$net}.2/24"/>
+ </addresses>
+ </eth>
+ <ovs_bridge id="ovs2">
+ <internal id="int0">
+ <options>
+ <option name="ofport_request" value="5"/>
+ <option name="name" value="int0"/>
+ </options>
+ <addresses>
+ <address value="{$vxlan_net}.2/24"/>
+ <address value="{$vxlan_net6}::2/24"/>
+ </addresses>
+ </internal>
+ <tunnel id="vxlan1" type="vxlan">
+ <options>
+ <option name="option:remote_ip" value="{$net}.1"/>
+ <option name="option:key" value="{$vxlan_key}"/>
+ <option name="option:dst_port" value="{$vxlan_port}"/>
+ <option name="ofport_request" value="10"/>
+ </options>
+ </tunnel>
+ </ovs_bridge>
+ </interfaces>
+ </host>
+ </network>
+
+ <task python="1_virt_ovs_vxlan.py"/>
+</lnstrecipe>
diff --git a/recipes/ovs_offload/1_virt_ovs_vxlan_ipv6.xml b/recipes/ovs_offload/1_virt_ovs_vxlan_ipv6.xml
new file mode 100644
index 0000000..ede7d66
--- /dev/null
+++ b/recipes/ovs_offload/1_virt_ovs_vxlan_ipv6.xml
@@ -0,0 +1,89 @@
+<lnstrecipe>
+ <define>
+ <alias name="ipv" value="both" />
+ <alias name="mtu" value="1450" />
+ <alias name="net" value="2002:0db8:0:f101"/>
+ <alias name="vxlan_net" value="1.1.1"/>
+ <alias name="vxlan_net6" value="fc00:0:0:0"/>
+ <alias name="vxlan_key" value="100"/>
+ <alias name="vxlan_port2" value="4789"/>
+ <alias name="vxlan_port" value="1478"/>
+ </define>
+ <network>
+ <host id="h1">
+ <params>
+ <param name="machine_type" value="baremetal"/>
+ <param name="order" value="1"/>
+ </params>
+ <interfaces>
+ <eth id="if1" label="n1">
+ <addresses>
+ <address value="{$net}::1/64"/>
+ </addresses>
+ </eth>
+ <eth id="tap1" label="to_guest1"/>
+ <ovs_bridge id="ovs1">
+ <slaves>
+ <slave id="tap1">
+ <options>
+ <option name="ofport_request" value="5"/>
+ </options>
+ </slave>
+ </slaves>
+ <tunnel id="vxlan1" type="vxlan">
+ <options>
+ <option name="option:remote_ip" value="{$net}::2"/>
+ <option name="option:key" value="{$vxlan_key}"/>
+ <option name="option:dst_port" value="{$vxlan_port}"/>
+ <option name="ofport_request" value="10"/>
+ </options>
+ </tunnel>
+ </ovs_bridge>
+ </interfaces>
+ </host>
+ <host id="test_host1">
+ <interfaces>
+ <eth id="if1" label="to_guest1">
+ <addresses>
+ <address value="{$vxlan_net}.1/24"/>
+ <address value="{$vxlan_net6}::1/64"/>
+ </addresses>
+ </eth>
+ </interfaces>
+ </host>
+ <host id="h2">
+ <params>
+ <param name="machine_type" value="baremetal"/>
+ </params>
+ <interfaces>
+ <eth id="if1" label="n1">
+ <addresses>
+ <address value="{$net}::2/64"/>
+ </addresses>
+ </eth>
+ <ovs_bridge id="ovs2">
+ <internal id="int0">
+ <options>
+ <option name="ofport_request" value="5"/>
+ <option name="name" value="int0"/>
+ </options>
+ <addresses>
+ <address value="{$vxlan_net}.2/24"/>
+ <address value="{$vxlan_net6}::2/24"/>
+ </addresses>
+ </internal>
+ <tunnel id="vxlan1" type="vxlan">
+ <options>
+ <option name="option:remote_ip" value="{$net}::1"/>
+ <option name="option:key" value="{$vxlan_key}"/>
+ <option name="option:dst_port" value="{$vxlan_port}"/>
+ <option name="ofport_request" value="10"/>
+ </options>
+ </tunnel>
+ </ovs_bridge>
+ </interfaces>
+ </host>
+ </network>
+
+ <task python="1_virt_ovs_vxlan.py"/>
+</lnstrecipe>
diff --git a/recipes/ovs_offload/Testlib.py b/recipes/ovs_offload/Testlib.py
new file mode 100644
index 0000000..e14bae7
--- /dev/null
+++ b/recipes/ovs_offload/Testlib.py
@@ -0,0 +1,90 @@
+import re
+import logging
+
+
+class Testlib:
+ def __init__(self, ctl):
+ self._ctl = ctl
+
+ def set_hw_offload(self, host, nics, state):
+ cmd = "ethtool -K {nic} hw-tc-offload %s" % state
+ for nic in nics:
+ logging.info("Set hw-tc-offload=%s for %s" % (state, nic))
+ host.run(cmd.format(nic=nic))
+
+ def enable_hw_offload(self, host, nics):
+ self.set_hw_offload(host, nics, 'on')
+
+ def disable_hw_offload(self, host, nics):
+ self.set_hw_offload(host, nics, 'off')
+
+ def custom(self, host, desc, err_msg=None, opts=None):
+ host.sync_resources(modules=["Custom"])
+ options = {}
+ if opts:
+ options.update(opts)
+ if err_msg:
+ options["fail"] = "yes"
+ options["msg"] = err_msg
+ custom_mod = self._ctl.get_module("Custom", options=options)
+ host.run(custom_mod, desc=desc)
+
+ def find_tc_rule(self, host, nic, src_mac, dst_mac, proto='.*', action='.*', tunnel=''):
+ # find dev. i.e. ens5f0
+ try:
+ if1 = host.get_interface(nic)
+ dev = if1.get_devname()
+ except KeyError:
+ dev = nic
+
+ # tc output
+ cmd = host.run("tc -s filter show dev %s parent ffff:" % dev)
+ out = cmd.out().strip()
+
+ if not out:
+ return None
+
+ # find rule
+ pat_filter = r"^protocol %s pref .* dst_mac %s\n\s*src_mac %s\n.*\n\s*action order \s*\d+\s*:\s* %s" % (
+ proto, str(dst_mac).lower(), str(src_mac).lower(), action)
+ pat_action = r".*\n\s*action order .* %s.*\n\s*Sent (?P<bytes>\d+) bytes (?P<pkts>\d+) pkt \(dropped (?P<drop>\d+), overlimits \d+ requeues \d+\)" % action
+
+ logging.debug("device %s pattern '%s' action '%s'", dev, pat_filter, pat_action)
+
+ for f in re.split('\n\s*filter\s*', out):
+ if re.match(pat_filter, f, re.S + re.M):
+ logging.debug("matched filter")
+ m = re.match(pat_action, f, re.S + re.M)
+ if m:
+ logging.debug("matched action")
+ return m.groupdict()
+ return None
+ return None
+
+ def _get_iperf_srv_mod(self):
+ modules_options = {
+ "role": "server",
+ }
+ return self._ctl.get_module("Iperf", options=modules_options)
+
+ def _get_iperf_cli_mod(self, server, duration):
+ modules_options = {
+ "role": "client",
+ "iperf_server": server,
+ "duration": duration,
+ "iperf_opts": "-l 1024k -w 512k -P 12"
+ }
+ return self._ctl.get_module("Iperf", options=modules_options)
+
+ def iperf(self, cli_if, srv_if, duration, desc):
+ srv_ip = srv_if.get_ip(0)
+ srv_m = self._get_iperf_srv_mod()
+ cli_m = self._get_iperf_cli_mod(srv_ip, duration)
+
+ cli_host = cli_if.get_host()
+ srv_host = srv_if.get_host()
+
+ srv_proc = srv_host.run(srv_m, bg=True)
+ self._ctl.wait(2)
+ cli_host.run(cli_m, timeout=duration + 15, desc=desc)
+ srv_proc.intr()
--
1.7.1
6 years
[PATCH] Task: optimize get_device() function
by Jiri Prochazka
Function dev_db_get_name() is here called twice and can be reduced to
only one call. Because it queries Slave machine n times, where n is
size of device database on Slave, it can take a lot of time when
database is large.
This reduces time it takes to return from this function
to a half.
Signed-off-by: Jiri Prochazka <jprochaz(a)redhat.com>
---
lnst/Controller/Task.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnst/Controller/Task.py b/lnst/Controller/Task.py
index 4f4a772..af1db96 100644
--- a/lnst/Controller/Task.py
+++ b/lnst/Controller/Task.py
@@ -314,7 +314,7 @@ class HostAPI(object):
def get_device(self, name):
dev = self._m.dev_db_get_name(name)
if dev:
- return DeviceAPI(self._m.dev_db_get_name(name), self)
+ return DeviceAPI(dev, self)
else:
raise TaskError("No device with name '%s' found." % str(name))
--
2.9.3
6 years, 1 month
[PATCH] bash-completion: add missing options to lnst-ctl completion
by Jan Tluka
Signed-off-by: Jan Tluka <jtluka(a)redhat.com>
---
install/lnst-ctl.bash | 22 +++++++++++++++-------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/install/lnst-ctl.bash b/install/lnst-ctl.bash
index 27b064b..c1dd9c8 100644
--- a/install/lnst-ctl.bash
+++ b/install/lnst-ctl.bash
@@ -12,15 +12,19 @@ _list_has_item()
_lnst_ctl()
{
- local SHORT_OPTS="-a -A -c -d -h -m -o -p -x -v"
- local LONG_OPTS="--define-alias --override-alias --config --debug \
- --help --no-colours --disable-pool-checks \
- --packet-capture --result --verbose"
+ local SHORT_OPTS="-a -A -c -C -d -h -m -o -p -r -u -x -v"
+ local LONG_OPTS="--define-alias --override-alias \
+ --config --config-override \
+ --debug --help --no-colours --disable-pool-checks \
+ --packet-capture --pools --reduce-sync \
+ --result --multi-match --verbose"
local REQUIRE_ARG="-a --define-alias \
-A --override-alias \
-c --config \
- -x --result"
- local ACTIONS="config_only match_setup run list_pools"
+ -C --config-override \
+ -x --result \
+ --pools"
+ local ACTIONS="config_only match_setup run list_pools deconfigure"
local cur=${COMP_WORDS[COMP_CWORD]}
local prev=${COMP_WORDS[COMP_CWORD-1]}
@@ -29,7 +33,7 @@ _lnst_ctl()
case "$prev" in
-a|--define-alias) return 0 ;;
-A|--override-alias) return 0 ;;
- -c|--config)
+ -c|--config|-C|--config-override)
_filedir
return 0
;;
@@ -37,6 +41,10 @@ _lnst_ctl()
_filedir
return 0
;;
+ --pools)
+ _filedir
+ return 0
+ ;;
esac
# Complete long and shor options
--
2.7.4
6 years, 1 month
[PATCH] bash-completion: remove shebang from bash completion scripts
by Jan Tluka
This fixes rpmlint errors reported from automated testing on koji builds.
Signed-off-by: Jan Tluka <jtluka(a)redhat.com>
---
install/lnst-ctl.bash | 2 --
install/lnst-pool-wizard.bash | 2 --
install/lnst-slave.bash | 2 --
3 files changed, 6 deletions(-)
diff --git a/install/lnst-ctl.bash b/install/lnst-ctl.bash
index 62aab66..27b064b 100644
--- a/install/lnst-ctl.bash
+++ b/install/lnst-ctl.bash
@@ -1,5 +1,3 @@
-#!/bin/bash
-
# Bash completion script for lnst-ctl command
# Author: Radek Pazdera <rpazdera(a)redhat.com>
diff --git a/install/lnst-pool-wizard.bash b/install/lnst-pool-wizard.bash
index 599b6fa..a2c083e 100644
--- a/install/lnst-pool-wizard.bash
+++ b/install/lnst-pool-wizard.bash
@@ -1,5 +1,3 @@
-#!/bin/bash
-
# Bash completion script for lnst-pool-wizard command
# Author: Jiri Prochazka <jprochaz(a)redhat.com>
diff --git a/install/lnst-slave.bash b/install/lnst-slave.bash
index d939d7a..19da8f3 100644
--- a/install/lnst-slave.bash
+++ b/install/lnst-slave.bash
@@ -1,5 +1,3 @@
-#!/bin/bash
-
# Bash completion script for lnst-slave command
# Author: Radek Pazdera <rpazdera(a)redhat.com>
--
2.7.4
6 years, 1 month
[PATCH v2] regression_tests: ipsec_esp_ah_comp add comp key
by Kamil Jerabek
The configuration of compression in iproute version 2 requires one addition
parameter. This commit adds key to compression configuration if it is needed.
Signed-off-by: Kamil Jerabek <kjerabek(a)redhat.com>
---
Changes in:
v2 * revision of import changes
---
recipes/regression_tests/phase3/ipsec_esp_ah_comp.py | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/recipes/regression_tests/phase3/ipsec_esp_ah_comp.py b/recipes/regression_tests/phase3/ipsec_esp_ah_comp.py
index 7286f60..fada91e 100644
--- a/recipes/regression_tests/phase3/ipsec_esp_ah_comp.py
+++ b/recipes/regression_tests/phase3/ipsec_esp_ah_comp.py
@@ -160,12 +160,12 @@ def configure_ipsec(ciph_alg, ciph_key, hash_alg, hash_key, ip_version):
m1.run("ip xfrm state add "\
"src %s dst %s proto comp spi 4 mode %s "\
- "comp deflate"\
- % (m1_addr, m2_addr, ipsec_mode))
+ "comp deflate %s"\
+ % (m1_addr, m2_addr, ipsec_mode, m1_key))
m1.run("ip xfrm state add "\
"src %s dst %s proto comp spi 1 mode %s "\
- "comp deflate"\
- % (m2_addr, m1_addr, ipsec_mode))
+ "comp deflate %s"\
+ % (m2_addr, m1_addr, ipsec_mode, m1_key))
m1.run("ip xfrm state add "\
"src %s dst %s proto esp spi 2 mode %s "\
@@ -210,12 +210,12 @@ def configure_ipsec(ciph_alg, ciph_key, hash_alg, hash_key, ip_version):
m2.run("ip xfrm state add "\
"src %s dst %s proto comp spi 4 mode %s "\
- "comp deflate"\
- % (m1_addr, m2_addr, ipsec_mode))
+ "comp deflate %s"\
+ % (m1_addr, m2_addr, ipsec_mode, m2_key))
m2.run("ip xfrm state add "\
"src %s dst %s proto comp spi 1 mode %s "\
- "comp deflate"\
- % (m2_addr, m1_addr, ipsec_mode))
+ "comp deflate %s"\
+ % (m2_addr, m1_addr, ipsec_mode, m2_key))
m2.run("ip xfrm state add "\
"src %s dst %s proto esp spi 2 mode %s "\
--
2.5.5
6 years, 2 months
[PATCH 1/3] recipes: switchdev: Add Qdisc helper
by Petr Machata
Add a very lightweight helper for handling tc qdiscs.
Signed-off-by: Petr Machata <petrm(a)mellanox.com>
---
recipes/switchdev/TestLib.py | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/recipes/switchdev/TestLib.py b/recipes/switchdev/TestLib.py
index d6b8fa7..a68b570 100644
--- a/recipes/switchdev/TestLib.py
+++ b/recipes/switchdev/TestLib.py
@@ -1,5 +1,5 @@
"""
-Copyright 2016 Mellanox Technologies. All rights reserved.
+Copyright 2016-2017 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.
"""
@@ -466,3 +466,22 @@ class TestLib:
err_msg = "got {} packets, expected {}".format(count, expected)
return self.custom(iface.get_host(), desc, err_msg)
+
+class Qdisc:
+ def __init__(self, iface, handle, qdisc):
+ self._ifname = iface.get_devname()
+ self._machine = iface.get_host()
+ self._handle = handle
+ self.run("tc qdisc add dev %s handle %x: %s"
+ % (self._ifname, self._handle, qdisc))
+
+ def filter_add(self, f):
+ self.run("tc filter add dev %s parent %x: %s"
+ % (self._ifname, self._handle, f))
+
+ def flush(self):
+ self.run("tc filter del dev %s parent %x:"
+ % (self._ifname, self._handle))
+
+ def run(self, command):
+ self._machine.run(command)
--
2.6.2
6 years, 2 months
[PATCH 3/3] recipes: switchdev: tc: Add a test for flower vlan
matching
by Petr Machata
This tests VLAN ID and VLAN PCP matching by dropping the matching
traffic and making sure that all traffic but the matching one gets
through.
Signed-off-by: Petr Machata <petrm(a)mellanox.com>
---
recipes/switchdev/tc-002-flower-vlan.py | 68 ++++++++++++++++++++++++++++++++
recipes/switchdev/tc-002-flower-vlan.xml | 24 +++++++++++
2 files changed, 92 insertions(+)
create mode 100644 recipes/switchdev/tc-002-flower-vlan.py
create mode 100644 recipes/switchdev/tc-002-flower-vlan.xml
diff --git a/recipes/switchdev/tc-002-flower-vlan.py b/recipes/switchdev/tc-002-flower-vlan.py
new file mode 100644
index 0000000..2eb419d
--- /dev/null
+++ b/recipes/switchdev/tc-002-flower-vlan.py
@@ -0,0 +1,68 @@
+"""
+Copyright 2017 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__ = """
+petrm(a)mellanox.com (Petr Machata)
+"""
+
+from lnst.Controller.Task import ctl
+from TestLib import TestLib, Qdisc
+from time import sleep
+
+def do_task(ctl, hosts, ifaces, aliases):
+ m1, m2, sw = hosts
+ m1_if1, m2_if1, sw_if1, sw_if2 = ifaces
+
+ m1_if1_85 = m1.create_vlan(m1_if1, 85,
+ ip=["192.168.85.10/24", "2002:85::1/64"])
+ m2_if1_85 = m2.create_vlan(m2_if1, 85,
+ ip=["192.168.85.11/24", "2002:85::2/64"])
+ m1.run("ip link set dev %s type vlan egress 0:7" % m1_if1_85.get_devname())
+ m2.run("ip link set dev %s type vlan egress 0:7" % m2_if1_85.get_devname())
+
+ m1_if1_95 = m1.create_vlan(m1_if1, 95,
+ ip=["192.168.95.10/24", "2002:95::1/64"])
+ m2_if1_95 = m2.create_vlan(m2_if1, 95,
+ ip=["192.168.95.11/24", "2002:95::2/64"])
+
+ sw_br1 = sw.create_bridge(slaves=[sw_if1, sw_if2], options={"vlan_filtering": 1})
+ sw_if1.add_br_vlan(85)
+ sw_if2.add_br_vlan(85)
+ sw_if1.add_br_vlan(95)
+ sw_if2.add_br_vlan(95)
+
+ sleep(15)
+
+ tl = TestLib(ctl, aliases)
+
+ # Test to establish that there is connectivity.
+ tl.ping_simple(m1_if1_85, m2_if1_85, count=10, limit_rate=9, interval=0.1)
+ tl.ping_simple(m1_if1_95, m2_if1_95, count=10, limit_rate=9, interval=0.1)
+
+ q1 = Qdisc(sw_if1, 0xffff, "ingress")
+
+ # Test that PCP match matches only that PCP.
+ q1.flush()
+ q1.filter_add("protocol 802.1q flower vlan_prio 7 skip_sw action drop")
+ sleep(1)
+ tl.ping_simple(m1_if1_85, m2_if1_85, limit_rate=10, fail_expected=True)
+ tl.ping_simple(m1_if1_95, m2_if1_95)
+
+ # Test that vlan match actually matches only that vlan.
+ q1.flush()
+ q1.filter_add("protocol 802.1q flower vlan_id 95 skip_sw action drop")
+ sleep(1)
+ tl.ping_simple(m1_if1_85, m2_if1_85)
+ tl.ping_simple(m1_if1_95, m2_if1_95, limit_rate=10, fail_expected=True)
+
+do_task(ctl, [ctl.get_host("machine1"),
+ ctl.get_host("machine2"),
+ ctl.get_host("switch")],
+ [ctl.get_host("machine1").get_interface("if1"),
+ ctl.get_host("machine2").get_interface("if1"),
+ ctl.get_host("switch").get_interface("if1"),
+ ctl.get_host("switch").get_interface("if2")],
+ ctl.get_aliases())
diff --git a/recipes/switchdev/tc-002-flower-vlan.xml b/recipes/switchdev/tc-002-flower-vlan.xml
new file mode 100644
index 0000000..7ac41f6
--- /dev/null
+++ b/recipes/switchdev/tc-002-flower-vlan.xml
@@ -0,0 +1,24 @@
+<lnstrecipe xmlns:xi="http://www.w3.org/2003/XInclude">
+ <xi:include href="default_aliases.xml" />
+ <network>
+ <host id="machine1">
+ <params/>
+ <interfaces>
+ <eth id="if1" label="A" />
+ </interfaces>
+ </host>
+ <host id="machine2">
+ <params/>
+ <interfaces>
+ <eth id="if1" label="B" />
+ </interfaces>
+ </host>
+ <host id="switch">
+ <interfaces>
+ <eth id="if1" label="A" />
+ <eth id="if2" label="B" />
+ </interfaces>
+ </host>
+ </network>
+ <task python="tc-002-flower-vlan.py" />
+</lnstrecipe>
--
2.6.2
6 years, 2 months
[PATCH 2/3] recipes: switchdev: tc: Add test for vlan modify action
by Petr Machata
Assume a topology with a central switch and two hosts. Each host sends
traffic on a different VLAN. The switch translates the VLANs to one
another using tc action vlan modify.
Signed-off-by: Petr Machata <petrm(a)mellanox.com>
---
recipes/switchdev/tc-001-action-vlan-modify.py | 44 +++++++++++++++++++++++++
recipes/switchdev/tc-001-action-vlan-modify.xml | 24 ++++++++++++++
2 files changed, 68 insertions(+)
create mode 100644 recipes/switchdev/tc-001-action-vlan-modify.py
create mode 100644 recipes/switchdev/tc-001-action-vlan-modify.xml
diff --git a/recipes/switchdev/tc-001-action-vlan-modify.py b/recipes/switchdev/tc-001-action-vlan-modify.py
new file mode 100644
index 0000000..a9c0d16
--- /dev/null
+++ b/recipes/switchdev/tc-001-action-vlan-modify.py
@@ -0,0 +1,44 @@
+"""
+Copyright 2017 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__ = """
+petrm(a)mellanox.com (Petr Machata)
+"""
+
+from lnst.Controller.Task import ctl
+from TestLib import TestLib, Qdisc
+from time import sleep
+
+def do_task(ctl, hosts, ifaces, aliases):
+ m1, m2, sw = hosts
+ m1_if1, m2_if1, sw_if1, sw_if2 = ifaces
+
+ m1_if1_85 = m1.create_vlan(m1_if1, 85, ip=["192.168.101.10/24", "2002::1/64"])
+ m2_if1_65 = m2.create_vlan(m2_if1, 65, ip=["192.168.101.11/24", "2002::2/64"])
+
+ sw_br1 = sw.create_bridge(slaves=[sw_if1, sw_if2], options={"vlan_filtering": 1})
+ sw_if1.add_br_vlan(85)
+ sw_if2.add_br_vlan(65)
+
+ q1 = Qdisc(sw_if1, 0xffff, "ingress")
+ q1.filter_add("protocol all flower skip_sw action vlan modify id 65")
+
+ q2 = Qdisc(sw_if2, 0xffff, "ingress")
+ q2.filter_add("protocol all flower skip_sw action vlan modify id 85")
+
+ sleep(10)
+
+ tl = TestLib(ctl, aliases)
+ tl.ping_simple(m1_if1_85, m2_if1_65)
+
+do_task(ctl, [ctl.get_host("machine1"),
+ ctl.get_host("machine2"),
+ ctl.get_host("switch")],
+ [ctl.get_host("machine1").get_interface("if1"),
+ ctl.get_host("machine2").get_interface("if1"),
+ ctl.get_host("switch").get_interface("if1"),
+ ctl.get_host("switch").get_interface("if2")],
+ ctl.get_aliases())
diff --git a/recipes/switchdev/tc-001-action-vlan-modify.xml b/recipes/switchdev/tc-001-action-vlan-modify.xml
new file mode 100644
index 0000000..d55658c
--- /dev/null
+++ b/recipes/switchdev/tc-001-action-vlan-modify.xml
@@ -0,0 +1,24 @@
+<lnstrecipe xmlns:xi="http://www.w3.org/2003/XInclude">
+ <xi:include href="default_aliases.xml" />
+ <network>
+ <host id="machine1">
+ <params/>
+ <interfaces>
+ <eth id="if1" label="A" />
+ </interfaces>
+ </host>
+ <host id="machine2">
+ <params/>
+ <interfaces>
+ <eth id="if1" label="B" />
+ </interfaces>
+ </host>
+ <host id="switch">
+ <interfaces>
+ <eth id="if1" label="A" />
+ <eth id="if2" label="B" />
+ </interfaces>
+ </host>
+ </network>
+ <task python="tc-001-action-vlan-modify.py" />
+</lnstrecipe>
--
2.6.2
6 years, 2 months
NEW API Discussion
by Ondrej Lichtner
Hi all,
for the past couple of weeks I've been going over the meeting recordings
we've had wrt the new Python API of LNST. I've been collecting
everything into a single file that I'm appending to this email. I'm
sending it here so that everyone can join the discussion before the
implementation itself begins. I'll warn you thougn... it's LONG :)
!!!NOTE it's not complete yet, I'm sending it now because we have an
upstream meeting planned for later today, namely Device/Interface API is
not complete.
The structure of the file is following:
1. commented pseudo code of how Test Modules will look like - they'll be
instantiated on the Controller and send ad-hoc to the slave where
they'll be executed --> no more synchronization on test start...
2. commented pseudo code of how Tasks will look like, they'll define
both the network requirements and the test execution as well.
3. short rough idea of how the tests/recipes will be executed.
4. 1st version of the API "specification"/documentation. Here I tried to
go through the current *API objects we currently have and make them more
"Pythonic", thinking of how they'll be used from a Task. I tried writing
it as class-method-attribute definitions with some documentation so
hopefully it makes some sense... Like I've said before,
Device/Interfaces are not complete so there's a lot missing there.
Please take a look and provide feedback. I'm sure there are other parts
in addition to Device/Interface APIs that are missing something so I'll
appreciate any help :).
================================================================================
new_api file:
1. test modules
class BaseTestModule:
def __init__(self, **kwargs):
#by defaults loads the params into self.params - no checks pseudocode:
for x in vars(self):
if isinstance(x, BaseType):
param_class = self.getattr(x)
try:
val = kwargs[x]
except KeyError:
if param_class.is_mandatory():
raise TestModuleError("Option x is mandatory")
self.setattr(x.params, param_class.construct(val))
del kwargs[x]
for x in kwargs.keys():
log.error("Undefined parameter x")
if len(kwargs):
raise TestModuleError("Undefined TestModule parameters")
def run():
#needs to be over-ridden - throw an exception to notify the test developer
class MyTest(BaseTestModule):
param = ParamType()
param2 = ParamType2()
param3 = Multiparam(ParamType())
#optional __init__
#def __init__(self, **kwargs):
#super(MyTest).__init__(kwargs)
#additional tester defined checks
def run():
#do my test
#parameters available in self.params
#in Task:
import lnst
#module lnst.modules will dynamically look for module classes in configured
#locations, similar to how we do it now
ping = lnst.modules.Ping(dst=m2.if1.ip[0], count=100, interval=0.1)
m1.run(ping)
================================================
2. Tasks:
class BaseTask(object):
def __init__(self):
#initialize instance specific requirements
self.requirements = Requirements()
for x in dir(self):
val = getattr(self, x)
setattr(self.requirements, x, val)
def test():
raise Exception("Method test MUST be defined.")
class MyTask(lnst.BaseTask):
#class-wide definition of requirements
m1 = HostSel(param="val", ...)
m1.if1 = IfaceSel(l2net="xyz", param="val", ...)
m2 = HostSel(param="val", ...)
m2.if1 = IfaceSel(l2net="xyz", param="val", ...)
def __init__(self, **kwargs):
super(self, lnst.BaseTask).__init__()
#do something with kwargs
#adjust instance specific requirements
self.requirements.m3 = HostSel(...)
def test():
self.matched.m1.run(Module)
self.matched.m1.run("command")
#or
def test(m1, m2):
m1.run(Module)
m2.run("command")
================================================
3. Running Tasks:
from MyTasks import MyTask
import lnst
task_instance = MyTask(params)
lnst(args)
lnst.run(task_instance)
OR
lnst-ctl -d run MyTask.py -- task_params
# looks for NAME class in the NAME.py file (MyTask in this case for which
# the condition "isinstance(NAME, BaseTask)" must be True
# could also run for all classes in the file where "isinstance(x, BaseTask)" is
# True. with the option to restrict to specific task class (or just run the
# first one?)... lnst-ctl rewritten to do the same as manually running the
# task from it's own python script
First do the second option - easier since we have this already, then refactor
the controller to create the lnst controller for the first option.
Aliases lose meaning - they're parameters passed to the MyTask __init__, when
using the lnst-ctl CLI, use "-- task_params"?? might not work for multiple tasks,
================================================
4. Tester facing API, inside the test() method:
Host objects available in self.matched.selector_name:
class Host: #HostAPI??? name can change
#attributes:
# dynamically filled object of Host attributes such as architecture and
# so on. Use example in test() would look like this:
# if host.params.arch == "x86":
# I separated this into the "params" object so I can overwrite its
# __getattr__ method and return None/UnknownParam exception for unknown
# parameters, and to avoid name conflicts with other attributes
params = object()
# dynamically filled object of NetDevice objects accessible directly as the
# object attributes:
# host.ifaces.eth0.set_ip(...)
# I separated this into the "ifaces" object to avoid name conflicts with
# other attributes
# creation of new NetDevices should be possible through simple assignement:
# m1.devs.new_team0 = TeamDevice(...)
# assignement of an incompatible Type or to an existing Device object will
# return an exception
# assignment of None? or del devs.new_team0 to deconfigure the device?
devs = object()
def run(what, bg=False, fail=False, timeout=60, path="", json=False, netns)
# will run "what" on the remote host
# "what" is either a Module object, or a string command that will be
# executed as a bash command
# "bg" when True, runs "what" on background - the run() call
# immediately returns, and "timeout" is ignored, the background
# process can be controlled through the returned Job object
# "fail" if True then the Job is expected to fail, and will be reported
# as PASSed if it does
# "timeout" in seconds, determines how long to block test execution for
# before killing the Job. Only when running in foreground
# "path" changes the current working directory to the specified path
# before "what" is executed and changes back after execution is
# finished.
# "tool" changes the current working directory to the directory of a
# speficied test_tool before "what" is executed and changes back
# after execution is finished.
# !!!!!!! this is from the current API and i'm not yet sure how we
# !!!!!!! want to handle those... so for now I'll keep it
# "json" if True will attempt to parse the returned stdout of the Job
# as json into a dictionary
# "netns" Job will be run in the specified network namespace
# Returns a Job object
def config(option, value)
# copied from old API, provides a shortcut for "echo $value # >/proc/or/sys/path"
# and returns the original value when the test is finished
def sync_resources(srcpath="", dstpath="", recursive=False)
# copies the specified file from the controller to the specified
# destination path, if recursive == True and srcpath refers to a
# directory it copies the entire directory
def {enable, disable}_service(service)
# copied from old API, enables or disables the specified service
def add_{bond, bridge,...}(params)
# this is how we can currently dynamically create net devices on the
# hosts. Even with the new assignment-based approach this could still,
# be usefull, though the method would need to be dynamically created to
# avoid useless work when adding a new netdev type. Something like:
# add_device("name", "Type", params) which would then do
# self.devs.name = TypeDevice(params) ??
def del_device(name)
# removes the specified device, probably easier (more logical?) to do
# this then "devs.name = None" and "del devs.name" would be unreliable
class Device: #DeviceAPI, InterfaceAPI? name can change...
# attributes:
# dynamically created Device attributes such as driver and so on. Use
# example in test() would look like this:
# if host.devs.eth0.driver == "ixgbe":
# achieved through rewriting of the __getattr__ method of the Device class
# should return None or throw UnknownParam exception for unknown parameters
# this should directly mirror the Device objects that are managed by the
# InterfaceManager on the Slave
# eg:
driver = something
mtu = something
ips = [IpAddress, ...]
class Job: #ProcessAPI? name can change...
#attributes:
# True if the Job finished, False if it's still running in the background
finished = bool
# contains the result data returned by the Job, None for bash commands
result = object
# contain the stdout and stderr generated by the job, None for Module Jobs
stdout = ""
stderr = ""
# simple True/False value indicating success/failure of the Job
passed = bool
def wait(timeout=0):
# for background jobs, will wait until the job finished
# "timeout" in seconds, determines how long to wait for. After timeout
# reached, nothing happens, status of the job can be checked with the
# "finished" attribute. If timeout=0, then wait forever.
def kill(signalnum=signal.SIGKILL):
# sends the specified signal to the process of the Job running in
# background
# "signalnum" the signal to be sent
6 years, 3 months