This patch series extends the ENRT test recipe set with additional tunnel recipes.
The series also includes updates to VxlanDevice and a bug fix for Device class required by VxlanGpeTunnelRecipe.
Jan Tluka (13): Recipes.ENRT: add GeneveLwtTunnelRecipe Recipes.ENRT: add GreLwtTunnelRecipe Recipes.ENRT: add VxlanLwtTunnelRecipe Recipes.ENRT: add GreOvsTunnelRecipe Recipes.ENRT: add GeneveOvsTunnelRecipe Recipes.ENRT: add VxlanOvsTunnelRecipe Recipes.ENRT: add GreTunnelOverVlanRecipe Recipes.ENRT: add GreTunnelOverMacvlanRecipe Recipes.ENRT: add VxlanNetnsTunnelRecipe Devices.VxlanDevice: add gpe property Devices.VxlanDevice: add learning property Devices.Device: handle RTM_NEWLINK messages without any IFLA_ADDRESS Recipes.ENRT: add VxlanGpeTunnelRecipe
docs/source/geneve_lwt_tunnel_recipe.rst | 6 + docs/source/geneve_ovs_tunnel_recipe.rst | 6 + docs/source/gre_lwt_tunnel_recipe.rst | 6 + docs/source/gre_ovs_tunnel_recipe.rst | 6 + .../source/gre_tunnel_over_macvlan_recipe.rst | 6 + docs/source/gre_tunnel_over_vlan_recipe.rst | 6 + docs/source/specific_scenarios.rst | 10 + docs/source/vxlan_gpe_tunnel_recipe.rst | 6 + docs/source/vxlan_lwt_tunnel_recipe.rst | 6 + docs/source/vxlan_netns_tunnel_recipe.rst | 6 + docs/source/vxlan_ovs_tunnel_recipe.rst | 6 + lnst/Devices/Device.py | 5 +- lnst/Devices/VxlanDevice.py | 19 ++ lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py | 216 ++++++++++++++++ lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py | 162 ++++++++++++ lnst/Recipes/ENRT/GreLwtTunnelRecipe.py | 221 ++++++++++++++++ lnst/Recipes/ENRT/GreOvsTunnelRecipe.py | 170 ++++++++++++ .../ENRT/GreTunnelOverMacvlanRecipe.py | 200 +++++++++++++++ lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py | 201 +++++++++++++++ lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py | 215 ++++++++++++++++ lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py | 215 ++++++++++++++++ lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py | 241 ++++++++++++++++++ lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py | 181 +++++++++++++ lnst/Recipes/ENRT/__init__.py | 10 + 24 files changed, 2125 insertions(+), 1 deletion(-) create mode 100644 docs/source/geneve_lwt_tunnel_recipe.rst create mode 100644 docs/source/geneve_ovs_tunnel_recipe.rst create mode 100644 docs/source/gre_lwt_tunnel_recipe.rst create mode 100644 docs/source/gre_ovs_tunnel_recipe.rst create mode 100644 docs/source/gre_tunnel_over_macvlan_recipe.rst create mode 100644 docs/source/gre_tunnel_over_vlan_recipe.rst create mode 100644 docs/source/vxlan_gpe_tunnel_recipe.rst create mode 100644 docs/source/vxlan_lwt_tunnel_recipe.rst create mode 100644 docs/source/vxlan_netns_tunnel_recipe.rst create mode 100644 docs/source/vxlan_ovs_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/GreLwtTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/GreOvsTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/GreTunnelOverMacvlanRecipe.py create mode 100644 lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py create mode 100644 lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/geneve_lwt_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py | 216 +++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 224 insertions(+) create mode 100644 docs/source/geneve_lwt_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py
diff --git a/docs/source/geneve_lwt_tunnel_recipe.rst b/docs/source/geneve_lwt_tunnel_recipe.rst new file mode 100644 index 00000000..3cee5d5a --- /dev/null +++ b/docs/source/geneve_lwt_tunnel_recipe.rst @@ -0,0 +1,6 @@ +GeneveLwtTunnelRecipe +^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.GeneveLwtTunnelRecipe.GeneveLwtTunnelRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 74573461..4b952d48 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -15,4 +15,5 @@ Specific ENRT scenarios ipip_tunnel_recipe ip6tnl_tunnel_recipe geneve_tunnel_recipe + geneve_lwt_tunnel_recipe l2tp_tunnel_recipe diff --git a/lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py b/lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py new file mode 100644 index 00000000..f50d6dd1 --- /dev/null +++ b/lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py @@ -0,0 +1,216 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + AF_INET6, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Devices import GeneveDevice, LoopbackDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Common.Parameters import Param, StrParam, ChoiceParam +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe + + +class GeneveLwtTunnelRecipe(BaseTunnelRecipe): + """ + This class implements a recipe that configures a simple Geneve lightweight + tunnel between two hosts. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | | | + | ----' '--- | | ----' '--- | + | gnv tunnel | | gnv tunnel | + | ---------- | | ---------- | + | | | | + | host1 | | host2 | + '--------------' '--------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + + The recipe provides additional parameter: + + :param carrier_ipversion: + This parameter specifies whether IPv4 or IPv6 addresses are + used for the underlying (carrier) network. The value is either + **ipv4** or **ipv6** + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + # TODO: ping over IPv6 does not work yet + ip_versions = Param(default=("ipv4",)) + carrier_ipversion = ChoiceParam(type=StrParam, choices=set(["ipv4", "ipv6"])) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of the Ethernet + devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + for i, device in enumerate([host1.eth0, host2.eth0]): + if self.params.carrier_ipversion == "ipv4": + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + else: + device.ip_add(ipaddress("fc00::" + str(i + 1) + "/64")) + device.up() + configuration.test_wide_devices.append(device) + + self.wait_tentative_ips(configuration.test_wide_devices) + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + The Geneve tunnel devices are created with external flag specified + so that the encapsulation can be defined externally by routes. + + Routes for IPv4 and IPv6 networks to be tunneled through the Geneve are + added. + + IPv4 and IPv6 addresses of the tunneled networks are configured on + the loopback devices of the matched hosts. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0] + endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0] + + m1_dummy_ip = ipaddress("172.16.10.1/32") + m1_dummy_ip6 = ipaddress("fc00:a::1/128") + m2_dummy_ip = ipaddress("172.16.20.1/32") + m2_dummy_ip6 = ipaddress("fc00:b::1/128") + + m1.gnv_tunnel = GeneveDevice(external=True) + m2.gnv_tunnel = GeneveDevice(external=True) + m1.lo = LoopbackDevice() + m2.lo = LoopbackDevice() + + # A + m1.lo.ip_add(m1_dummy_ip) + m1.lo.ip_add(m1_dummy_ip6) + m1.gnv_tunnel.mtu = 1400 + m1.gnv_tunnel.up() + + # B + m2.lo.ip_add(m2_dummy_ip) + m2.lo.ip_add(m2_dummy_ip6) + m2.gnv_tunnel.mtu = 1400 + m2.gnv_tunnel.up() + + tunnel_id = 1234 + encap = "ip" if self.params.carrier_ipversion == "ipv4" else "ip6" + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip, encap, tunnel_id, endpoint2_ip, m1.gnv_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip, encap, tunnel_id, endpoint1_ip, m2.gnv_tunnel.name + ) + ) + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip6, encap, tunnel_id, endpoint2_ip, m1.gnv_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip6, encap, tunnel_id, endpoint1_ip, m2.gnv_tunnel.name + ) + ) + + configuration.tunnel_devices.extend([m1.gnv_tunnel, m2.gnv_tunnel]) + self.wait_tentative_ips([m1.lo, m2.lo]) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are the loopback devices that + are configured with IP addresses of the tunnelled networks. + + Returned as:: + + [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + """ + return [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for ip or ip6 + protocol and grep patterns to match the ICMP or ICMP6 echo requests + encapsulated by Geneve. + """ + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + m1_carrier = self.matched.host1.eth0 + m2_carrier = self.matched.host2.eth0 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + if self.params.carrier_ipversion == "ipv4": + pa_kwargs["p_filter"] = "ip host {}".format(m1_carrier_ip) + grep_pattern = "IP " + else: + pa_kwargs["p_filter"] = "ip6" + grep_pattern = "IP6 " + + grep_pattern += "{}.[0-9]+ > {}.[0-9]+: Geneve.*vni 0x4d2: ".format( + m1_carrier_ip, m2_carrier_ip + ) + + if isinstance(ip2, Ip4Address): + grep_pattern += "IP {} > {}: ICMP".format(ip1, ip2) + elif isinstance(ip2, Ip6Address): + grep_pattern += "IP6 {} > {}: ICMP6".format(ip1, ip2) + + pa_kwargs["grep_for"] = [grep_pattern] + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index 0b198e25..eb91b4c8 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -88,6 +88,7 @@ from lnst.Recipes.ENRT.SitTunnelRecipe import SitTunnelRecipe from lnst.Recipes.ENRT.IpIpTunnelRecipe import IpIpTunnelRecipe from lnst.Recipes.ENRT.Ip6TnlTunnelRecipe import Ip6TnlTunnelRecipe from lnst.Recipes.ENRT.GeneveTunnelRecipe import GeneveTunnelRecipe +from lnst.Recipes.ENRT.GeneveLwtTunnelRecipe import GeneveLwtTunnelRecipe from lnst.Recipes.ENRT.L2TPTunnelRecipe import L2TPTunnelRecipe
from lnst.Recipes.ENRT.BaseEnrtRecipe import BaseEnrtRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/gre_lwt_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/GreLwtTunnelRecipe.py | 221 ++++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 229 insertions(+) create mode 100644 docs/source/gre_lwt_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GreLwtTunnelRecipe.py
diff --git a/docs/source/gre_lwt_tunnel_recipe.rst b/docs/source/gre_lwt_tunnel_recipe.rst new file mode 100644 index 00000000..6a3d63e4 --- /dev/null +++ b/docs/source/gre_lwt_tunnel_recipe.rst @@ -0,0 +1,6 @@ +GreLwtTunnelRecipe +^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.GreLwtTunnelRecipe.GreLwtTunnelRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 4b952d48..01459da1 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -8,6 +8,7 @@ Specific ENRT scenarios vlans_recipe vlans_over_bond_recipe gre_tunnel_recipe + gre_lwt_tunnel_recipe gre_tunnel_over_bond_recipe ip6gre_tunnel_recipe ip6gre_netns_tunnel_recipe diff --git a/lnst/Recipes/ENRT/GreLwtTunnelRecipe.py b/lnst/Recipes/ENRT/GreLwtTunnelRecipe.py new file mode 100644 index 00000000..8b8aac6b --- /dev/null +++ b/lnst/Recipes/ENRT/GreLwtTunnelRecipe.py @@ -0,0 +1,221 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + AF_INET6, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Devices import GreDevice, LoopbackDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Common.Parameters import Param, StrParam, ChoiceParam +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe + + +class GreLwtTunnelRecipe(BaseTunnelRecipe): + """ + This class implements a recipe that configures a simple Gre lightweight + tunnel between two hosts. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | | | + | ----' '--- | | ----' '--- | + | gre tunnel | | gre tunnel | + | ---------- | | ---------- | + | | | | + | host1 | | host2 | + '--------------' '--------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + + The recipe provides additional parameter: + + :param carrier_ipversion: + This parameter specifies whether IPv4 or IPv6 addresses are + used for the underlying (carrier) network. The value is either + **ipv4** or **ipv6** + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + # TODO: ping over IPv6 does not work yet + ip_versions = Param(default=("ipv4",)) + carrier_ipversion = ChoiceParam(type=StrParam, choices=set(["ipv4", "ipv6"])) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of the Ethernet + devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + for i, device in enumerate([host1.eth0, host2.eth0]): + if self.params.carrier_ipversion == "ipv4": + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + else: + device.ip_add(ipaddress("fc00::" + str(i + 1) + "/64")) + device.up() + configuration.test_wide_devices.append(device) + + self.wait_tentative_ips(configuration.test_wide_devices) + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + The Gre tunnel devices are created with external flag specified + so that the encapsulation can be defined externally by routes. + + Routes for IPv4 and IPv6 networks to be tunneled through the Gre are + added. + + IPv4 and IPv6 addresses of the tunneled networks are configured on + the loopback devices of the matched hosts. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0] + endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0] + + m1_dummy_ip = ipaddress("172.16.10.1/32") + m1_dummy_ip6 = ipaddress("fc00:a::1/128") + m2_dummy_ip = ipaddress("172.16.20.1/32") + m2_dummy_ip6 = ipaddress("fc00:b::1/128") + + m1.gre_tunnel = GreDevice(external=True) + m2.gre_tunnel = GreDevice(external=True) + m1.lo = LoopbackDevice() + m2.lo = LoopbackDevice() + + # A + m1.lo.ip_add(m1_dummy_ip) + m1.lo.ip_add(m1_dummy_ip6) + m1.gre_tunnel.mtu = 1400 + m1.gre_tunnel.up() + + # B + m2.lo.ip_add(m2_dummy_ip) + m2.lo.ip_add(m2_dummy_ip6) + m2.gre_tunnel.mtu = 1400 + m2.gre_tunnel.up() + + tunnel_id = 1234 + encap = "ip" if self.params.carrier_ipversion == "ipv4" else "ip6" + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip, encap, tunnel_id, endpoint2_ip, m1.gre_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip, encap, tunnel_id, endpoint1_ip, m2.gre_tunnel.name + ) + ) + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip6, encap, tunnel_id, endpoint2_ip, m1.gre_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip6, encap, tunnel_id, endpoint1_ip, m2.gre_tunnel.name + ) + ) + + configuration.tunnel_devices.extend([m1.gre_tunnel, m2.gre_tunnel]) + self.wait_tentative_ips([m1.lo, m2.lo]) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are the loopback devices that + are configured with IP addresses of the tunnelled networks. + + Returned as:: + + [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + """ + return [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for gre protocol + and grep patterns to match the ICMP or ICMP6 echo requests encapsulated + by Gre. + """ + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + m1_carrier = self.matched.host1.eth0 + m2_carrier = self.matched.host2.eth0 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = "proto gre" + + if isinstance(ip2, Ip4Address): + pat1 = "{} > {}: GREv0, .* IP {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + elif isinstance(ip2, Ip6Address): + pat1 = "{} > {}: GREv0, .* IP6 {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = grep_pattern + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index eb91b4c8..454ab296 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -82,6 +82,7 @@ from lnst.Recipes.ENRT.VxlanMulticastRecipe import VxlanMulticastRecipe from lnst.Recipes.ENRT.VxlanRemoteRecipe import VxlanRemoteRecipe from lnst.Recipes.ENRT.GreTunnelRecipe import GreTunnelRecipe from lnst.Recipes.ENRT.GreTunnelOverBondRecipe import GreTunnelOverBondRecipe +from lnst.Recipes.ENRT.GreLwtTunnelRecipe import GreLwtTunnelRecipe from lnst.Recipes.ENRT.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe from lnst.Recipes.ENRT.Ip6GreNetnsTunnelRecipe import Ip6GreNetnsTunnelRecipe from lnst.Recipes.ENRT.SitTunnelRecipe import SitTunnelRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/specific_scenarios.rst | 1 + docs/source/vxlan_lwt_tunnel_recipe.rst | 6 + lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py | 215 ++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 223 insertions(+) create mode 100644 docs/source/vxlan_lwt_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py
diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 01459da1..d372af6d 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -17,4 +17,5 @@ Specific ENRT scenarios ip6tnl_tunnel_recipe geneve_tunnel_recipe geneve_lwt_tunnel_recipe + vxlan_lwt_tunnel_recipe l2tp_tunnel_recipe diff --git a/docs/source/vxlan_lwt_tunnel_recipe.rst b/docs/source/vxlan_lwt_tunnel_recipe.rst new file mode 100644 index 00000000..f86caf2a --- /dev/null +++ b/docs/source/vxlan_lwt_tunnel_recipe.rst @@ -0,0 +1,6 @@ +VxlanLwtTunnelRecipe +^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.VxlanLwtTunnelRecipe.VxlanLwtTunnelRecipe + :members: + :show-inheritance: diff --git a/lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py b/lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py new file mode 100644 index 00000000..020f4e70 --- /dev/null +++ b/lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py @@ -0,0 +1,215 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + AF_INET6, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Devices import VxlanDevice, LoopbackDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Common.Parameters import Param, StrParam, ChoiceParam +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe + + +class VxlanLwtTunnelRecipe(BaseTunnelRecipe): + """ + This class implements a recipe that configures a simple Vxlan lightweight + tunnel between two hosts. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | | | + | ----' '--- | | ----' '--- | + | vxlan tunnel | | vxlan tunnel | + | ---------- | | ---------- | + | | | | + | host1 | | host2 | + '--------------' '--------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + + The recipe provides additional parameter: + + :param carrier_ipversion: + This parameter specifies whether IPv4 or IPv6 addresses are + used for the underlying (carrier) network. The value is either + **ipv4** or **ipv6** + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + # TODO: ping over IPv6 does not work yet + ip_versions = Param(default=("ipv4",)) + # TODO: IPv6 does not work as carrier network + carrier_ipversion = ChoiceParam(type=StrParam, choices=set(["ipv4"])) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of the Ethernet + devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + for i, device in enumerate([host1.eth0, host2.eth0]): + if self.params.carrier_ipversion == "ipv4": + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + else: + device.ip_add(ipaddress("fc00::" + str(i + 1) + "/64")) + device.up() + configuration.test_wide_devices.append(device) + + self.wait_tentative_ips(configuration.test_wide_devices) + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + The Vxlan tunnel devices are created with external flag specified + so that the encapsulation can be defined externally by routes. + + Routes for IPv4 and IPv6 networks to be tunneled through the Vxlan are + added. + + IPv4 and IPv6 addresses of the tunneled networks are configured on + the loopback devices of the matched hosts. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0] + endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0] + + m1_dummy_ip = ipaddress("172.16.10.1/32") + m1_dummy_ip6 = ipaddress("fc00:a::1/128") + m2_dummy_ip = ipaddress("172.16.20.1/32") + m2_dummy_ip6 = ipaddress("fc00:b::1/128") + + m1.vxlan_tunnel = VxlanDevice(external=True) + m2.vxlan_tunnel = VxlanDevice(external=True) + m1.lo = LoopbackDevice() + m2.lo = LoopbackDevice() + + # A + m1.lo.ip_add(m1_dummy_ip) + m1.lo.ip_add(m1_dummy_ip6) + m1.vxlan_tunnel.mtu = 1400 + m1.vxlan_tunnel.up() + + # B + m2.lo.ip_add(m2_dummy_ip) + m2.lo.ip_add(m2_dummy_ip6) + m2.vxlan_tunnel.mtu = 1400 + m2.vxlan_tunnel.up() + + tunnel_id = 1234 + encap = "ip" if self.params.carrier_ipversion == "ipv4" else "ip6" + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip, encap, tunnel_id, endpoint2_ip, m1.vxlan_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip, encap, tunnel_id, endpoint1_ip, m2.vxlan_tunnel.name + ) + ) + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip6, encap, tunnel_id, endpoint2_ip, m1.vxlan_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip6, encap, tunnel_id, endpoint1_ip, m2.vxlan_tunnel.name + ) + ) + + configuration.tunnel_devices.extend([m1.vxlan_tunnel, m2.vxlan_tunnel]) + self.wait_tentative_ips([m1.lo, m2.lo]) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are the loopback devices that + are configured with IP addresses of the tunnelled networks. + + Returned as:: + + [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + """ + return [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for source + and destination addresses matching the carrier network with udp + header bits specific to VXLAN tunneling. The grep patterns match + the ICMP or ICMP6 echo requests encapsulated by Vxlan. + """ + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + m1_carrier = self.matched.host1.eth0 + m2_carrier = self.matched.host2.eth0 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = ( + "src {} and dst {} " + "and udp[8:2] = 0x0800 & 0x0800 " + "and udp[11:4] = 1234 & 0x00FFFFFF".format(m2_carrier_ip, m1_carrier_ip) + ) + + if isinstance(ip2, Ip4Address): + grep_pattern = "IP {} > {}: ICMP echo reply".format(ip2, ip1) + elif isinstance(ip2, Ip6Address): + grep_pattern = "IP6 {} > {}: ICMP6, echo reply".format(ip2, ip1) + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = [grep_pattern] + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index 454ab296..f683d928 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -90,6 +90,7 @@ from lnst.Recipes.ENRT.IpIpTunnelRecipe import IpIpTunnelRecipe from lnst.Recipes.ENRT.Ip6TnlTunnelRecipe import Ip6TnlTunnelRecipe from lnst.Recipes.ENRT.GeneveTunnelRecipe import GeneveTunnelRecipe from lnst.Recipes.ENRT.GeneveLwtTunnelRecipe import GeneveLwtTunnelRecipe +from lnst.Recipes.ENRT.VxlanLwtTunnelRecipe import VxlanLwtTunnelRecipe from lnst.Recipes.ENRT.L2TPTunnelRecipe import L2TPTunnelRecipe
from lnst.Recipes.ENRT.BaseEnrtRecipe import BaseEnrtRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/gre_ovs_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/GreOvsTunnelRecipe.py | 170 ++++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 178 insertions(+) create mode 100644 docs/source/gre_ovs_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GreOvsTunnelRecipe.py
diff --git a/docs/source/gre_ovs_tunnel_recipe.rst b/docs/source/gre_ovs_tunnel_recipe.rst new file mode 100644 index 00000000..05f81f17 --- /dev/null +++ b/docs/source/gre_ovs_tunnel_recipe.rst @@ -0,0 +1,6 @@ +GreOvsTunnelRecipe +^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.GreOvsTunnelRecipe.GreOvsTunnelRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index d372af6d..13bbf6ac 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -9,6 +9,7 @@ Specific ENRT scenarios vlans_over_bond_recipe gre_tunnel_recipe gre_lwt_tunnel_recipe + gre_ovs_tunnel_recipe gre_tunnel_over_bond_recipe ip6gre_tunnel_recipe ip6gre_netns_tunnel_recipe diff --git a/lnst/Recipes/ENRT/GreOvsTunnelRecipe.py b/lnst/Recipes/ENRT/GreOvsTunnelRecipe.py new file mode 100644 index 00000000..3ce3608c --- /dev/null +++ b/lnst/Recipes/ENRT/GreOvsTunnelRecipe.py @@ -0,0 +1,170 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Common.Parameters import Param +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Devices import OvsBridgeDevice +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.CommonHWSubConfigMixin import ( + CommonHWSubConfigMixin, +) + + +class GreOvsTunnelRecipe( + CommonHWSubConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a simple GRE tunnel using + OpenVSwitch between two hosts. + + .. code-block:: none + + .--------. + .----------| switch |-------. + | '--------' | + | | + .-------|----------. .-------|----------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | .----| |-------. | | .----| |-------. | + | | | | OvS | | | | | | OvS | | + | | | | | | | | | | | | + | | ---' '--- | | | | ---' '--- | | + | | gre tunnel | | | | gre tunnel | | + | | ---------- | | | | ---------- | | + | '--------------' | | '--------------' | + | | | | + | host1 | | host2 | + '------------------' '------------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of the Ethernet + devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + for i, device in enumerate([host1.eth0, host2.eth0]): + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + device.up() + configuration.test_wide_devices.append(device) + + self.wait_tentative_ips(configuration.test_wide_devices) + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + OvS bridges are created on each of the matched hosts with two ports. + One port as an integration port and another port of type GRE acting + as a tunnel interface connecting tunneled networks. + + Integration ports are configured with IPv4 and IPv6 addresses + of the tunneled networks. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + ip_filter = {"family": AF_INET} + + for i, (host, endpoint) in enumerate([(m1, endpoint2), (m2, endpoint1)]): + remote_ip = endpoint.ips_filter(**ip_filter)[0] + host.br0 = OvsBridgeDevice() + host.int0 = host.br0.port_add(interface_options={"type": "internal"}) + configuration.tunnel_devices.append(host.int0) + host.int0.ip_add(ipaddress("192.168.200." + str(i + 1) + "/24")) + host.int0.ip_add(ipaddress("fc00::" + str(i + 1) + "/64")) + + host.br0.tunnel_add("gre", {"options:remote_ip": remote_ip}) + + host.br0.up() + host.int0.up() + + self.wait_tentative_ips(configuration.tunnel_devices) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are simply the tunnel endpoints + + Returned as:: + + [PingEndpoints(self.matched.host1.int0, self.matched.host2.int0)] + """ + return [PingEndpoints(self.matched.host1.int0, self.matched.host2.int0)] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for gre protocol + and grep patterns to match the ICMP or ICMP6 echo requests. + """ + ip_filter = {"family": AF_INET} + m1_carrier = self.matched.host1.eth0 + m2_carrier = self.matched.host2.eth0 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = "proto gre" + + if isinstance(ip2, Ip4Address): + pat1 = "{} > {}: GREv0, .* IP {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + elif isinstance(ip2, Ip6Address): + pat1 = "{} > {}: GREv0, .* IP6 {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = grep_pattern + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index f683d928..b99872fe 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -83,6 +83,7 @@ from lnst.Recipes.ENRT.VxlanRemoteRecipe import VxlanRemoteRecipe from lnst.Recipes.ENRT.GreTunnelRecipe import GreTunnelRecipe from lnst.Recipes.ENRT.GreTunnelOverBondRecipe import GreTunnelOverBondRecipe from lnst.Recipes.ENRT.GreLwtTunnelRecipe import GreLwtTunnelRecipe +from lnst.Recipes.ENRT.GreOvsTunnelRecipe import GreOvsTunnelRecipe from lnst.Recipes.ENRT.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe from lnst.Recipes.ENRT.Ip6GreNetnsTunnelRecipe import Ip6GreNetnsTunnelRecipe from lnst.Recipes.ENRT.SitTunnelRecipe import SitTunnelRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/geneve_ovs_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py | 162 +++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 170 insertions(+) create mode 100644 docs/source/geneve_ovs_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py
diff --git a/docs/source/geneve_ovs_tunnel_recipe.rst b/docs/source/geneve_ovs_tunnel_recipe.rst new file mode 100644 index 00000000..448534cc --- /dev/null +++ b/docs/source/geneve_ovs_tunnel_recipe.rst @@ -0,0 +1,6 @@ +GeneveOvsTunnelRecipe +^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.GeneveOvsTunnelRecipe.GeneveOvsTunnelRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 13bbf6ac..bdb12396 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -18,5 +18,6 @@ Specific ENRT scenarios ip6tnl_tunnel_recipe geneve_tunnel_recipe geneve_lwt_tunnel_recipe + geneve_ovs_tunnel_recipe vxlan_lwt_tunnel_recipe l2tp_tunnel_recipe diff --git a/lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py b/lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py new file mode 100644 index 00000000..da494b45 --- /dev/null +++ b/lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py @@ -0,0 +1,162 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Common.Parameters import Param +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Devices import OvsBridgeDevice +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.CommonHWSubConfigMixin import ( + CommonHWSubConfigMixin, +) + + +class GeneveOvsTunnelRecipe( + CommonHWSubConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a simple Geneve tunnel using + OpenVSwitch between two hosts. + + .. code-block:: none + + .--------. + .----------| switch |-------. + | '--------' | + | | + .-------|----------. .-------|----------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | .----| |-------. | | .----| |-------. | + | | | | OvS | | | | | | OvS | | + | | | | | | | | | | | | + | | ---' '--- | | | | ---' '--- | | + | | gnv tunnel | | | | gnv tunnel | | + | | ---------- | | | | ---------- | | + | '--------------' | | '--------------' | + | | | | + | host1 | | host2 | + '------------------' '------------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of the Ethernet + devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + for i, device in enumerate([host1.eth0, host2.eth0]): + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + device.up() + configuration.test_wide_devices.append(device) + + self.wait_tentative_ips(configuration.test_wide_devices) + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + OvS bridges are created on each of the matched hosts with two ports. + One port as an integration port and another port of type Geneve acting + as a tunnel interface connecting tunneled networks. + + Integration ports are configured with IPv4 and IPv6 addresses + of the tunneled networks. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + ip_filter = {"family": AF_INET} + + for i, (host, endpoint) in enumerate([(m1, endpoint2), (m2, endpoint1)]): + remote_ip = endpoint.ips_filter(**ip_filter)[0] + host.br0 = OvsBridgeDevice() + host.int0 = host.br0.port_add(interface_options={"type": "internal"}) + configuration.tunnel_devices.append(host.int0) + host.int0.ip_add(ipaddress("192.168.200." + str(i + 1) + "/24")) + host.int0.ip_add(ipaddress("fc00::" + str(i + 1) + "/64")) + + host.br0.tunnel_add( + "geneve", {"options:remote_ip": remote_ip, "options:key": 1234} + ) + + host.br0.up() + host.int0.up() + + self.wait_tentative_ips(configuration.tunnel_devices) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are simply the tunnel endpoints + + Returned as:: + + [PingEndpoints(self.matched.host1.int0, self.matched.host2.int0)] + """ + return [PingEndpoints(self.matched.host1.int0, self.matched.host2.int0)] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for ip protocol + and grep patterns to match the ICMP or ICMP6 echo requests. + """ + ip_filter = {"family": AF_INET} + m1_carrier = self.matched.host1.eth0 + m2_carrier = self.matched.host2.eth0 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = "ip" + + grep_pattern = "IP {}.[0-9]+ > {}.[0-9]+: Geneve.*vni 0x4d2: ".format( + m1_carrier_ip, m2_carrier_ip + ) + + if isinstance(ip2, Ip4Address): + grep_pattern += "IP {} > {}: ICMP".format(ip1, ip2) + elif isinstance(ip2, Ip6Address): + grep_pattern += "IP6 {} > {}: ICMP6".format(ip1, ip2) + + pa_kwargs["grep_for"] = [grep_pattern] + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index b99872fe..67bf3de7 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -91,6 +91,7 @@ from lnst.Recipes.ENRT.IpIpTunnelRecipe import IpIpTunnelRecipe from lnst.Recipes.ENRT.Ip6TnlTunnelRecipe import Ip6TnlTunnelRecipe from lnst.Recipes.ENRT.GeneveTunnelRecipe import GeneveTunnelRecipe from lnst.Recipes.ENRT.GeneveLwtTunnelRecipe import GeneveLwtTunnelRecipe +from lnst.Recipes.ENRT.GeneveOvsTunnelRecipe import GeneveOvsTunnelRecipe from lnst.Recipes.ENRT.VxlanLwtTunnelRecipe import VxlanLwtTunnelRecipe from lnst.Recipes.ENRT.L2TPTunnelRecipe import L2TPTunnelRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/specific_scenarios.rst | 1 + docs/source/vxlan_ovs_tunnel_recipe.rst | 6 + lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py | 181 ++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 189 insertions(+) create mode 100644 docs/source/vxlan_ovs_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py
diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index bdb12396..00fd0d4f 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -20,4 +20,5 @@ Specific ENRT scenarios geneve_lwt_tunnel_recipe geneve_ovs_tunnel_recipe vxlan_lwt_tunnel_recipe + vxlan_ovs_tunnel_recipe l2tp_tunnel_recipe diff --git a/docs/source/vxlan_ovs_tunnel_recipe.rst b/docs/source/vxlan_ovs_tunnel_recipe.rst new file mode 100644 index 00000000..e92beb92 --- /dev/null +++ b/docs/source/vxlan_ovs_tunnel_recipe.rst @@ -0,0 +1,6 @@ +VxlanOvsTunnelRecipe +^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.VxlanOvsTunnelRecipe.VxlanOvsTunnelRecipe + :members: + :show-inheritance: diff --git a/lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py b/lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py new file mode 100644 index 00000000..a096d6ab --- /dev/null +++ b/lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py @@ -0,0 +1,181 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Common.Parameters import Param +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Devices import OvsBridgeDevice +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.CommonHWSubConfigMixin import ( + CommonHWSubConfigMixin, +) + + +class VxlanOvsTunnelRecipe( + CommonHWSubConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a simple Vxlan tunnel using + OpenVSwitch between two hosts. + + .. code-block:: none + + .--------. + .----------| switch |-------. + | '--------' | + | | + .-------|----------. .-------|----------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | .----| |-------. | | .----| |-------. | + | | | | OvS | | | | | | OvS | | + | | | | | | | | | | | | + | | ---' '------ | | | | ---' '------ | | + | | vxlan tunnel | | | | vxlan tunnel | | + | | ------------ | | | | ------------ | | + | '--------------' | | '--------------' | + | | | | + | host1 | | host2 | + '------------------' '------------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of the Ethernet + devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + for i, device in enumerate([host1.eth0, host2.eth0]): + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + device.up() + configuration.test_wide_devices.append(device) + + self.wait_tentative_ips(configuration.test_wide_devices) + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + OvS bridges are created on each of the matched hosts with two ports. + One port as an integration port and another port of type VXLAN acting + as a tunnel interface connecting tunneled networks. + + Integration ports are configured with IPv4 and IPv6 addresses + of the tunneled networks. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + ip_filter = {"family": AF_INET} + + for i, (host, endpoint) in enumerate([(m1, endpoint2), (m2, endpoint1)]): + remote_ip = endpoint.ips_filter(**ip_filter)[0] + host.br0 = OvsBridgeDevice() + host.int0 = host.br0.port_add( + interface_options={"type": "internal", "ofport_request": 5} + ) + configuration.tunnel_devices.append(host.int0) + host.int0.ip_add(ipaddress("192.168.200." + str(i + 1) + "/24")) + host.int0.ip_add(ipaddress("fc00::" + str(i + 1) + "/64")) + + host.br0.tunnel_add( + "vxlan", + { + "options:remote_ip": remote_ip, + "options:key": "flow", + "ofport_request": 10, + }, + ) + + host.br0.flows_add( + [ + "table=0,in_port=5,actions=set_field:1234->tun_id,output:10", + "table=0,in_port=10,tun_id=1234,actions=output:5", + "table=0,priority=100,actions=drop", + ] + ) + + host.br0.up() + host.int0.up() + + self.wait_tentative_ips(configuration.tunnel_devices) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are simply the tunnel endpoints + + Returned as:: + + [PingEndpoints(self.matched.host1.int0, self.matched.host2.int0)] + """ + return [PingEndpoints(self.matched.host1.int0, self.matched.host2.int0)] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for source + and destination addresses matching the carrier network with udp + header bits specific to VXLAN tunneling. The grep patterns match + the ICMP or ICMP6 echo requests encapsulated by Vxlan. + """ + ip_filter = {"family": AF_INET} + m1_carrier = self.matched.host1.eth0 + m2_carrier = self.matched.host2.eth0 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = ( + "src {} and dst {} " + "and udp[8:2] = 0x0800 & 0x0800 " + "and udp[11:4] = 1234 & 0x00FFFFFF".format(m2_carrier_ip, m1_carrier_ip) + ) + + if isinstance(ip2, Ip4Address): + grep_pattern = "IP {} > {}: ICMP echo reply".format(ip2, ip1) + elif isinstance(ip2, Ip6Address): + grep_pattern = "IP6 {} > {}: ICMP6, echo reply".format(ip2, ip1) + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = [grep_pattern] + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index 67bf3de7..69db2b7b 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -93,6 +93,7 @@ from lnst.Recipes.ENRT.GeneveTunnelRecipe import GeneveTunnelRecipe from lnst.Recipes.ENRT.GeneveLwtTunnelRecipe import GeneveLwtTunnelRecipe from lnst.Recipes.ENRT.GeneveOvsTunnelRecipe import GeneveOvsTunnelRecipe from lnst.Recipes.ENRT.VxlanLwtTunnelRecipe import VxlanLwtTunnelRecipe +from lnst.Recipes.ENRT.VxlanOvsTunnelRecipe import VxlanOvsTunnelRecipe from lnst.Recipes.ENRT.L2TPTunnelRecipe import L2TPTunnelRecipe
from lnst.Recipes.ENRT.BaseEnrtRecipe import BaseEnrtRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/gre_tunnel_over_vlan_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py | 201 +++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 209 insertions(+) create mode 100644 docs/source/gre_tunnel_over_vlan_recipe.rst create mode 100644 lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py
diff --git a/docs/source/gre_tunnel_over_vlan_recipe.rst b/docs/source/gre_tunnel_over_vlan_recipe.rst new file mode 100644 index 00000000..cf4b1527 --- /dev/null +++ b/docs/source/gre_tunnel_over_vlan_recipe.rst @@ -0,0 +1,6 @@ +GreTunnelOverVlanRecipe +^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.GreTunnelOverVlanRecipe.GreTunnelOverVlanRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 00fd0d4f..44b7da91 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -11,6 +11,7 @@ Specific ENRT scenarios gre_lwt_tunnel_recipe gre_ovs_tunnel_recipe gre_tunnel_over_bond_recipe + gre_tunnel_over_vlan_recipe ip6gre_tunnel_recipe ip6gre_netns_tunnel_recipe sit_tunnel_recipe diff --git a/lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py b/lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py new file mode 100644 index 00000000..a660843c --- /dev/null +++ b/lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py @@ -0,0 +1,201 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Common.Parameters import Param +from lnst.Devices import GreDevice, VlanDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.CommonHWSubConfigMixin import ( + CommonHWSubConfigMixin, +) + + +class GreTunnelOverVlanRecipe( + CommonHWSubConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a GRE tunnel between + two hosts that are connected through a vlan device. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | + | vlan0(id=10) | | vlan0(id=10) | + | | | | | | | | + | | | | | | | | + | ----' '--- | | ----' '--- | + | gre tunnel | | gre tunnel | + | ---------- | | ---------- | + | | | | + | host1 | | host2 | + '--------------' '--------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + host1.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + host2.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of two Ethernet + devices on the matched hosts. A VLAN is configured on top of each + device. + """ + host1, host2 = self.matched.host1, self.matched.host2 + + host1.vlan10 = VlanDevice(realdev=host1.eth0, vlan_id=10) + host2.vlan10 = VlanDevice(realdev=host2.eth0, vlan_id=10) + + for i, device in enumerate([host1.vlan10, host2.vlan10]): + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + configuration.test_wide_devices.append(device) + + for dev in [ + host1.eth0, + host1.vlan10, + host2.eth0, + host2.vlan10, + ]: + dev.up() + + configuration.tunnel_endpoints = (host1.vlan10, host2.vlan10) + + def create_tunnel(self, configuration): + """ + The GRE tunnel devices are configured with local and remote ip addresses + matching the VLAN device IP addresses. + + The GRE tunnel devices are configured with IPv4 and IPv6 addresses + of individual networks. Routes are configured accordingly. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + ip_filter = {"family": AF_INET} + endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0] + endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0] + + a_ip4 = Ip4Address("192.168.6.2/24") + a_net4 = "192.168.6.0/24" + b_ip4 = Ip4Address("192.168.7.2/24") + b_net4 = "192.168.7.0/24" + + a_ip6 = Ip6Address("6001:db8:ac10:fe01::2/64") + a_net6 = "6001:db8:ac10:fe01::0/64" + b_ip6 = Ip6Address("7001:db8:ac10:fe01::2/64") + b_net6 = "7001:db8:ac10:fe01::0/64" + + m1.gre_tunnel = GreDevice(local=endpoint1_ip, remote=endpoint2_ip) + m2.gre_tunnel = GreDevice(local=endpoint2_ip, remote=endpoint1_ip) + + # A + m1.gre_tunnel.up() + m1.gre_tunnel.ip_add(a_ip4) + m1.gre_tunnel.ip_add(a_ip6) + m1.run("ip -4 route add {} dev {}".format(b_net4, m1.gre_tunnel.name)) + m1.run("ip -6 route add {} dev {}".format(b_net6, m1.gre_tunnel.name)) + + # B + m2.gre_tunnel.up() + m2.gre_tunnel.ip_add(b_ip4) + m2.gre_tunnel.ip_add(b_ip6) + m2.run("ip -4 route add {} dev {}".format(a_net4, m2.gre_tunnel.name)) + m2.run("ip -6 route add {} dev {}".format(a_net6, m2.gre_tunnel.name)) + + configuration.tunnel_devices.extend([m1.gre_tunnel, m2.gre_tunnel]) + self.wait_tentative_ips(configuration.tunnel_devices) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are simply the tunnel endpoints + + Returned as:: + + [PingEndpoints(self.matched.host1.gre_tunnel, self.matched.host2.gre_tunnel)] + """ + return [ + PingEndpoints(self.matched.host1.gre_tunnel, self.matched.host2.gre_tunnel) + ] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for gre protocol + and grep patterns to match the ICMP or ICMP6 echo requests. + """ + ip_filter = {"family": AF_INET} + m1_carrier = self.matched.host1.vlan10 + m2_carrier = self.matched.host2.vlan10 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = "proto gre" + + if isinstance(ip2, Ip4Address): + pat1 = "{} > {}: GREv0, .* IP {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + elif isinstance(ip2, Ip6Address): + pat1 = "{} > {}: GREv0, .* IP6 {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = grep_pattern + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index 69db2b7b..9b3fd537 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -82,6 +82,7 @@ from lnst.Recipes.ENRT.VxlanMulticastRecipe import VxlanMulticastRecipe from lnst.Recipes.ENRT.VxlanRemoteRecipe import VxlanRemoteRecipe from lnst.Recipes.ENRT.GreTunnelRecipe import GreTunnelRecipe from lnst.Recipes.ENRT.GreTunnelOverBondRecipe import GreTunnelOverBondRecipe +from lnst.Recipes.ENRT.GreTunnelOverVlanRecipe import GreTunnelOverVlanRecipe from lnst.Recipes.ENRT.GreLwtTunnelRecipe import GreLwtTunnelRecipe from lnst.Recipes.ENRT.GreOvsTunnelRecipe import GreOvsTunnelRecipe from lnst.Recipes.ENRT.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- .../source/gre_tunnel_over_macvlan_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + .../ENRT/GreTunnelOverMacvlanRecipe.py | 200 ++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 208 insertions(+) create mode 100644 docs/source/gre_tunnel_over_macvlan_recipe.rst create mode 100644 lnst/Recipes/ENRT/GreTunnelOverMacvlanRecipe.py
diff --git a/docs/source/gre_tunnel_over_macvlan_recipe.rst b/docs/source/gre_tunnel_over_macvlan_recipe.rst new file mode 100644 index 00000000..de09c451 --- /dev/null +++ b/docs/source/gre_tunnel_over_macvlan_recipe.rst @@ -0,0 +1,6 @@ +GreTunnelOverMacvlanRecipe +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.GreTunnelOverMacvlanRecipe.GreTunnelOverMacvlanRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 44b7da91..879404c2 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -12,6 +12,7 @@ Specific ENRT scenarios gre_ovs_tunnel_recipe gre_tunnel_over_bond_recipe gre_tunnel_over_vlan_recipe + gre_tunnel_over_macvlan_recipe ip6gre_tunnel_recipe ip6gre_netns_tunnel_recipe sit_tunnel_recipe diff --git a/lnst/Recipes/ENRT/GreTunnelOverMacvlanRecipe.py b/lnst/Recipes/ENRT/GreTunnelOverMacvlanRecipe.py new file mode 100644 index 00000000..e1ca93d7 --- /dev/null +++ b/lnst/Recipes/ENRT/GreTunnelOverMacvlanRecipe.py @@ -0,0 +1,200 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Common.Parameters import Param +from lnst.Devices import GreDevice, MacvlanDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.CommonHWSubConfigMixin import ( + CommonHWSubConfigMixin, +) + + +class GreTunnelOverMacvlanRecipe( + CommonHWSubConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a GRE tunnel between + two hosts that are connected through a macvlan device. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | + | macvlan0 | | macvlan0 | + | | | | | | | | + | | | | | | | | + | ----' '--- | | ----' '--- | + | gre tunnel | | gre tunnel | + | ---------- | | ---------- | + | | | | + | host1 | | host2 | + '--------------' '--------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + host1.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + host2.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of two MACVLAN + devices configured on top of the Ethernet devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + + host1.macvlan10 = MacvlanDevice(realdev=host1.eth0, hwaddr="0A:00:00:00:00:01") + host2.macvlan10 = MacvlanDevice(realdev=host2.eth0, hwaddr="0A:00:00:00:00:02") + + for i, device in enumerate([host1.macvlan10, host2.macvlan10]): + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + configuration.test_wide_devices.append(device) + + for dev in [ + host1.eth0, + host1.macvlan10, + host2.eth0, + host2.macvlan10, + ]: + dev.up() + + configuration.tunnel_endpoints = (host1.macvlan10, host2.macvlan10) + + def create_tunnel(self, configuration): + """ + The GRE tunnel devices are configured with local and remote ip addresses + matching the MACVLAN device IP addresses. + + The GRE tunnel devices are configured with IPv4 and IPv6 addresses + of individual networks. Routes are configured accordingly. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + ip_filter = {"family": AF_INET} + endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0] + endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0] + + a_ip4 = Ip4Address("192.168.6.2/24") + a_net4 = "192.168.6.0/24" + b_ip4 = Ip4Address("192.168.7.2/24") + b_net4 = "192.168.7.0/24" + + a_ip6 = Ip6Address("6001:db8:ac10:fe01::2/64") + a_net6 = "6001:db8:ac10:fe01::0/64" + b_ip6 = Ip6Address("7001:db8:ac10:fe01::2/64") + b_net6 = "7001:db8:ac10:fe01::0/64" + + m1.gre_tunnel = GreDevice(local=endpoint1_ip, remote=endpoint2_ip) + m2.gre_tunnel = GreDevice(local=endpoint2_ip, remote=endpoint1_ip) + + # A + m1.gre_tunnel.up() + m1.gre_tunnel.ip_add(a_ip4) + m1.gre_tunnel.ip_add(a_ip6) + m1.run("ip -4 route add {} dev {}".format(b_net4, m1.gre_tunnel.name)) + m1.run("ip -6 route add {} dev {}".format(b_net6, m1.gre_tunnel.name)) + + # B + m2.gre_tunnel.up() + m2.gre_tunnel.ip_add(b_ip4) + m2.gre_tunnel.ip_add(b_ip6) + m2.run("ip -4 route add {} dev {}".format(a_net4, m2.gre_tunnel.name)) + m2.run("ip -6 route add {} dev {}".format(a_net6, m2.gre_tunnel.name)) + + configuration.tunnel_devices.extend([m1.gre_tunnel, m2.gre_tunnel]) + self.wait_tentative_ips(configuration.tunnel_devices) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are simply the tunnel endpoints + + Returned as:: + + [PingEndpoints(self.matched.host1.gre_tunnel, self.matched.host2.gre_tunnel)] + """ + return [ + PingEndpoints(self.matched.host1.gre_tunnel, self.matched.host2.gre_tunnel) + ] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for gre protocol + and grep patterns to match the ICMP or ICMP6 echo requests. + """ + ip_filter = {"family": AF_INET} + m1_carrier = self.matched.host1.macvlan10 + m2_carrier = self.matched.host2.macvlan10 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = "proto gre" + + if isinstance(ip2, Ip4Address): + pat1 = "{} > {}: GREv0, .* IP {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + elif isinstance(ip2, Ip6Address): + pat1 = "{} > {}: GREv0, .* IP6 {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = grep_pattern + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index 9b3fd537..d18da30c 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -83,6 +83,7 @@ from lnst.Recipes.ENRT.VxlanRemoteRecipe import VxlanRemoteRecipe from lnst.Recipes.ENRT.GreTunnelRecipe import GreTunnelRecipe from lnst.Recipes.ENRT.GreTunnelOverBondRecipe import GreTunnelOverBondRecipe from lnst.Recipes.ENRT.GreTunnelOverVlanRecipe import GreTunnelOverVlanRecipe +from lnst.Recipes.ENRT.GreTunnelOverMacvlanRecipe import GreTunnelOverMacvlanRecipe from lnst.Recipes.ENRT.GreLwtTunnelRecipe import GreLwtTunnelRecipe from lnst.Recipes.ENRT.GreOvsTunnelRecipe import GreOvsTunnelRecipe from lnst.Recipes.ENRT.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/specific_scenarios.rst | 1 + docs/source/vxlan_netns_tunnel_recipe.rst | 6 + lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py | 241 ++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 249 insertions(+) create mode 100644 docs/source/vxlan_netns_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py
diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 879404c2..8687ed45 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -23,4 +23,5 @@ Specific ENRT scenarios geneve_ovs_tunnel_recipe vxlan_lwt_tunnel_recipe vxlan_ovs_tunnel_recipe + vxlan_netns_tunnel_recipe l2tp_tunnel_recipe diff --git a/docs/source/vxlan_netns_tunnel_recipe.rst b/docs/source/vxlan_netns_tunnel_recipe.rst new file mode 100644 index 00000000..f7ee9be5 --- /dev/null +++ b/docs/source/vxlan_netns_tunnel_recipe.rst @@ -0,0 +1,6 @@ +VxlanNetnsTunnelRecipe +^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.VxlanNetnsTunnelRecipe.VxlanNetnsTunnelRecipe + :members: + :show-inheritance: diff --git a/lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py b/lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py new file mode 100644 index 00000000..72b491ff --- /dev/null +++ b/lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py @@ -0,0 +1,241 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Controller.NetNamespace import NetNamespace +from lnst.Common.IpAddress import ( + AF_INET, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Common.Parameters import Param +from lnst.Devices import VxlanDevice, VethPair, BridgeDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.CommonHWSubConfigMixin import ( + CommonHWSubConfigMixin, +) + + +class VxlanNetnsTunnelRecipe( + CommonHWSubConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a VXLAN tunnel between + two network namespaces on two hosts. + + .. code-block:: none + + .--------. + .------------| switch |--------. + | '--------' | + | | + .-------|------------. .-------|------------. + | .--'---. | | .--'---. | + | | eth0 | | | | eth0 | | + | '------' | | '------' | + | \ | | \ | + | .--------. | | .--------. | + | | bridge | | | | bridge | | + | '--------' | | '--------' | + | / | | / | + | .-------. | | .-------. | + | | veth0 | | | | veth0 | | + | '--.----' | | '--.----' | + | .-------------. | | .-------------. | + | | \ netns | | | | \ netns | | + | | .------. | | | | .------. | | + | | |veth0_1| | | | | |veth0_1| | | + | | '-------' | | | | '-------' | | + | | | | | | | | | | | | + | | -----' '---- | | | | -----' '---- | | + | | vxlan tunnel | | | | vxlan tunnel | | + | | ------------ | | | | ------------ | | + | '--------------' | | '--------------' | + | | | | + | host1 | | host1 | + '--------------------' '--------------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of + + - an Ethernet device on each of the matched hosts + - a pair of veth devices on each host with one end of the pair + connected to Ethernet device through bridge and the second one + moved to a network namespace + + The veth devices in the namespaces are assigned an IPv4 address and + will be used as tunnel endpoints + """ + host1, host2 = self.matched.host1, self.matched.host2 + + host1.newns = NetNamespace("host1-net1") + host2.newns = NetNamespace("host2-net1") + + # veth_pair + host1.veth0, host1.veth1 = VethPair() + host2.veth0, host2.veth1 = VethPair() + + # one veth end to netns + host1.newns.veth1 = host1.veth1 + host2.newns.veth1 = host2.veth1 + + # second veth bridged with NIC + host1.bridge = BridgeDevice() + host1.bridge.slave_add(host1.veth0) + host1.bridge.slave_add(host1.eth0) + host2.bridge = BridgeDevice() + host2.bridge.slave_add(host2.veth0) + host2.bridge.slave_add(host2.eth0) + + for device in [ + host1.veth0, + host2.veth0, + host1.bridge, + host2.bridge, + host1.eth0, + host2.eth0, + ]: + device.up() + configuration.test_wide_devices.append(device) + + for i, device in enumerate([host1.newns.veth1, host2.newns.veth1]): + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + device.up() + configuration.test_wide_devices.append(device) + + for i, device in enumerate([host1.veth0, host2.veth0]): + device.up() + + configuration.tunnel_endpoints = (host1.newns.veth1, host2.newns.veth1) + + def create_tunnel(self, configuration): + """ + The VXLAN tunnel devices are created in the network namespaces and + configured with local and remote ip addresses matching the veth + devices IP addresses. + + The VXLAN tunnel devices are configured with IPv4 and IPv6 addresses + of individual networks. Routes are configured accordingly. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + + a_ip4 = Ip4Address("192.168.6.2/24") + a_net4 = "192.168.6.0/24" + b_ip4 = Ip4Address("192.168.7.2/24") + b_net4 = "192.168.7.0/24" + + a_ip6 = Ip6Address("6001:db8:ac10:fe01::2/64") + a_net6 = "6001:db8:ac10:fe01::0/64" + b_ip6 = Ip6Address("7001:db8:ac10:fe01::2/64") + b_net6 = "7001:db8:ac10:fe01::0/64" + + vxlan_group_ip = "239.1.1.1" + + m1.vxlan_tunnel = VxlanDevice( + vxlan_id=1234, realdev=endpoint1, group=vxlan_group_ip + ) + m2.vxlan_tunnel = VxlanDevice( + vxlan_id=1234, realdev=endpoint2, group=vxlan_group_ip + ) + + # A + m1.vxlan_tunnel.up() + m1.vxlan_tunnel.ip_add(a_ip4) + m1.vxlan_tunnel.ip_add(a_ip6) + m1.run("ip -4 route add {} dev {}".format(b_net4, m1.vxlan_tunnel.name)) + m1.run("ip -6 route add {} dev {}".format(b_net6, m1.vxlan_tunnel.name)) + + # B + m2.vxlan_tunnel.up() + m2.vxlan_tunnel.ip_add(b_ip4) + m2.vxlan_tunnel.ip_add(b_ip6) + m2.run("ip -4 route add {} dev {}".format(a_net4, m2.vxlan_tunnel.name)) + m2.run("ip -6 route add {} dev {}".format(a_net6, m2.vxlan_tunnel.name)) + + configuration.tunnel_devices.extend([m1.vxlan_tunnel, m2.vxlan_tunnel]) + self.wait_tentative_ips(configuration.tunnel_devices) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are simply the tunnel endpoints + + Returned as:: + + [PingEndpoints(self.matched.host1.newns.vxlan_tunnel, self.matched.host2.newns.vxlan_tunnel)] + """ + return [ + PingEndpoints( + self.matched.host1.newns.vxlan_tunnel, + self.matched.host2.newns.vxlan_tunnel, + ) + ] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for ip6 protocol + and grep patterns to match the ICMP or ICMP6 echo requests encapsulated + by VXLAN. + """ + ip_filter = {"family": AF_INET} + + m1_carrier = self.matched.host1.newns.veth1 + m2_carrier = self.matched.host2.newns.veth1 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = ( + "src {} and dst {} " + "and udp[8:2] = 0x0800 & 0x0800 " + "and udp[11:4] = 1234 & 0x00FFFFFF".format(m2_carrier_ip, m1_carrier_ip) + ) + + if isinstance(ip2, Ip4Address): + grep_pattern = "IP {} > {}: ICMP echo reply".format(ip2, ip1) + elif isinstance(ip2, Ip6Address): + grep_pattern = "IP6 {} > {}: ICMP6, echo reply".format(ip2, ip1) + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = [grep_pattern] + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index d18da30c..900ed27c 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -96,6 +96,7 @@ from lnst.Recipes.ENRT.GeneveLwtTunnelRecipe import GeneveLwtTunnelRecipe from lnst.Recipes.ENRT.GeneveOvsTunnelRecipe import GeneveOvsTunnelRecipe from lnst.Recipes.ENRT.VxlanLwtTunnelRecipe import VxlanLwtTunnelRecipe from lnst.Recipes.ENRT.VxlanOvsTunnelRecipe import VxlanOvsTunnelRecipe +from lnst.Recipes.ENRT.VxlanNetnsTunnelRecipe import VxlanNetnsTunnelRecipe from lnst.Recipes.ENRT.L2TPTunnelRecipe import L2TPTunnelRecipe
from lnst.Recipes.ENRT.BaseEnrtRecipe import BaseEnrtRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- lnst/Devices/VxlanDevice.py | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/lnst/Devices/VxlanDevice.py b/lnst/Devices/VxlanDevice.py index 1426d3e2..d3c7c55c 100644 --- a/lnst/Devices/VxlanDevice.py +++ b/lnst/Devices/VxlanDevice.py @@ -117,3 +117,13 @@ class VxlanDevice(SoftDevice): if val: self._set_linkinfo_data_attr("IFLA_VXLAN_COLLECT_METADATA", True) self._nl_link_sync("set") + + @property + def gpe(self): + return self._get_linkinfo_data_attr("IFLA_VXLAN_GPE") is not None + + @gpe.setter + def gpe(self, val): + if val: + self._set_linkinfo_data_attr("IFLA_VXLAN_GPE", True) + self._nl_link_sync("set")
Signed-off-by: Jan Tluka jtluka@redhat.com --- lnst/Devices/VxlanDevice.py | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/lnst/Devices/VxlanDevice.py b/lnst/Devices/VxlanDevice.py index d3c7c55c..380bdeb9 100644 --- a/lnst/Devices/VxlanDevice.py +++ b/lnst/Devices/VxlanDevice.py @@ -127,3 +127,12 @@ class VxlanDevice(SoftDevice): if val: self._set_linkinfo_data_attr("IFLA_VXLAN_GPE", True) self._nl_link_sync("set") + + @property + def learning(self): + return self._get_linkinfo_data_attr("IFLA_VXLAN_LEARNING") + + @learning.setter + def learning(self, val): + self._set_linkinfo_data_attr("IFLA_VXLAN_LEARNING", val) + self._nl_link_sync("set")
There are cases when IFLA_ADDRESS is not included in the netlink message, for example when vxlan device is created with gpe flag:
ip link add vxlan0 type vxlan external gpe
When the IFLA_ADDRESS is not present in the message the None value is passed to hwaddress() parser function that raises an exception.
A check is added to hwaddr property whether the IFLA_ADDRESS attribute is present. If it's not the parser function is bypassed and None is returned.
Signed-off-by: Jan Tluka jtluka@redhat.com --- lnst/Devices/Device.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/lnst/Devices/Device.py b/lnst/Devices/Device.py index fd5ef86b..adebd117 100644 --- a/lnst/Devices/Device.py +++ b/lnst/Devices/Device.py @@ -416,7 +416,10 @@ class Device(object, metaclass=DeviceMeta): Returns a HWAddress object representing the hardware address of the device as reported by the kernel. """ - return hwaddress(self._nl_msg.get_attr("IFLA_ADDRESS")) + if self._nl_msg.get_attr("IFLA_ADDRESS"): + return hwaddress(self._nl_msg.get_attr("IFLA_ADDRESS")) + else: + return None
@hwaddr.setter def hwaddr(self, addr):
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/specific_scenarios.rst | 1 + docs/source/vxlan_gpe_tunnel_recipe.rst | 6 + lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py | 215 ++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 223 insertions(+) create mode 100644 docs/source/vxlan_gpe_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py
diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 8687ed45..8fbe9dd4 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -24,4 +24,5 @@ Specific ENRT scenarios vxlan_lwt_tunnel_recipe vxlan_ovs_tunnel_recipe vxlan_netns_tunnel_recipe + vxlan_gpe_tunnel_recipe l2tp_tunnel_recipe diff --git a/docs/source/vxlan_gpe_tunnel_recipe.rst b/docs/source/vxlan_gpe_tunnel_recipe.rst new file mode 100644 index 00000000..0a70b6ba --- /dev/null +++ b/docs/source/vxlan_gpe_tunnel_recipe.rst @@ -0,0 +1,6 @@ +VxlanGpeTunnelRecipe +^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.VxlanGpeTunnelRecipe.VxlanGpeTunnelRecipe + :members: + :show-inheritance: diff --git a/lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py b/lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py new file mode 100644 index 00000000..07cd9621 --- /dev/null +++ b/lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py @@ -0,0 +1,215 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + AF_INET6, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Devices import VxlanDevice, LoopbackDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Common.Parameters import Param, StrParam, ChoiceParam +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe + + +class VxlanGpeTunnelRecipe(BaseTunnelRecipe): + """ + This class implements a recipe that configures a simple Vxlan GPE + tunnel between two hosts. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | | | + | ----' '--- | | ----' '--- | + | vxlan tunnel | | vxlan tunnel | + | ---------- | | ---------- | + | | | | + | host1 | | host2 | + '--------------' '--------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + + The recipe provides additional parameter: + + :param carrier_ipversion: + This parameter specifies whether IPv4 or IPv6 addresses are + used for the underlying (carrier) network. The value is either + **ipv4** or **ipv6** + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + # TODO: ping over IPv6 does not work yet + ip_versions = Param(default=("ipv4",)) + # TODO: IPv6 does not work as carrier network + carrier_ipversion = ChoiceParam(type=StrParam, choices=set(["ipv4"])) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of the Ethernet + devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + for i, device in enumerate([host1.eth0, host2.eth0]): + if self.params.carrier_ipversion == "ipv4": + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + else: + device.ip_add(ipaddress("fc00::" + str(i + 1) + "/64")) + device.up() + configuration.test_wide_devices.append(device) + + self.wait_tentative_ips(configuration.test_wide_devices) + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + The Vxlan tunnel devices are created with external flag specified + so that the encapsulation can be defined externally by routes. + + The devices are configured to use VXLAN-GPE. + + Routes for IPv4 and IPv6 networks to be tunneled through the Vxlan are + added. + + IPv4 and IPv6 addresses of the tunneled networks are configured on + the loopback devices of the matched hosts. + + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0] + endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0] + + m1_dummy_ip = ipaddress("172.16.10.1/32") + m1_dummy_ip6 = ipaddress("fc00:a::1/128") + m2_dummy_ip = ipaddress("172.16.20.1/32") + m2_dummy_ip6 = ipaddress("fc00:b::1/128") + + m1.vxlan_tunnel = VxlanDevice(external=True, gpe=True, learning=0) + m2.vxlan_tunnel = VxlanDevice(external=True, gpe=True, learning=0) + m1.lo = LoopbackDevice() + m2.lo = LoopbackDevice() + + # A + m1.lo.ip_add(m1_dummy_ip) + m1.lo.ip_add(m1_dummy_ip6) + m1.vxlan_tunnel.mtu = 1400 + m1.vxlan_tunnel.up() + + # B + m2.lo.ip_add(m2_dummy_ip) + m2.lo.ip_add(m2_dummy_ip6) + m2.vxlan_tunnel.mtu = 1400 + m2.vxlan_tunnel.up() + + tunnel_id = 1234 + encap = "ip" if self.params.carrier_ipversion == "ipv4" else "ip6" + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip, encap, tunnel_id, endpoint2_ip, m1.vxlan_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip, encap, tunnel_id, endpoint1_ip, m2.vxlan_tunnel.name + ) + ) + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip6, encap, tunnel_id, endpoint2_ip, m1.vxlan_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip6, encap, tunnel_id, endpoint1_ip, m2.vxlan_tunnel.name + ) + ) + + configuration.tunnel_devices.extend([m1.vxlan_tunnel, m2.vxlan_tunnel]) + self.wait_tentative_ips([m1.lo, m2.lo]) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are the loopback devices that + are configured with IP addresses of the tunnelled networks. + + Returned as:: + + [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + """ + return [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for source + and destination addresses matching the carrier network with udp + header bits specific to VXLAN tunneling. The grep patterns match + the ICMP or ICMP6 echo requests encapsulated by Vxlan with + VXLAN-GPE extension. + """ + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + m1_carrier = self.matched.host1.eth0 + m2_carrier = self.matched.host2.eth0 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = "src {} and dst {}".format(m2_carrier_ip, m1_carrier_ip) + + if isinstance(ip2, Ip4Address): + grep_pattern = "VXLAN-GPE.*IP {} > {}: ICMP echo reply".format(ip2, ip1) + elif isinstance(ip2, Ip6Address): + grep_pattern = "VXLAN-GPE.*IP6 {} > {}: ICMP6, echo reply".format(ip2, ip1) + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = [grep_pattern] + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index 900ed27c..34eb4877 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -97,6 +97,7 @@ from lnst.Recipes.ENRT.GeneveOvsTunnelRecipe import GeneveOvsTunnelRecipe from lnst.Recipes.ENRT.VxlanLwtTunnelRecipe import VxlanLwtTunnelRecipe from lnst.Recipes.ENRT.VxlanOvsTunnelRecipe import VxlanOvsTunnelRecipe from lnst.Recipes.ENRT.VxlanNetnsTunnelRecipe import VxlanNetnsTunnelRecipe +from lnst.Recipes.ENRT.VxlanGpeTunnelRecipe import VxlanGpeTunnelRecipe from lnst.Recipes.ENRT.L2TPTunnelRecipe import L2TPTunnelRecipe
from lnst.Recipes.ENRT.BaseEnrtRecipe import BaseEnrtRecipe
I found few minor issues with the patch set. Please do not apply this one. I will send v2 soon.
The issues are related to missing various *ConfigMixin in some of the recipes.
-Jan
Fri, May 21, 2021 at 12:43:27PM CEST, jtluka@redhat.com wrote:
This patch series extends the ENRT test recipe set with additional tunnel recipes.
The series also includes updates to VxlanDevice and a bug fix for Device class required by VxlanGpeTunnelRecipe.
Jan Tluka (13): Recipes.ENRT: add GeneveLwtTunnelRecipe Recipes.ENRT: add GreLwtTunnelRecipe Recipes.ENRT: add VxlanLwtTunnelRecipe Recipes.ENRT: add GreOvsTunnelRecipe Recipes.ENRT: add GeneveOvsTunnelRecipe Recipes.ENRT: add VxlanOvsTunnelRecipe Recipes.ENRT: add GreTunnelOverVlanRecipe Recipes.ENRT: add GreTunnelOverMacvlanRecipe Recipes.ENRT: add VxlanNetnsTunnelRecipe Devices.VxlanDevice: add gpe property Devices.VxlanDevice: add learning property Devices.Device: handle RTM_NEWLINK messages without any IFLA_ADDRESS Recipes.ENRT: add VxlanGpeTunnelRecipe
docs/source/geneve_lwt_tunnel_recipe.rst | 6 + docs/source/geneve_ovs_tunnel_recipe.rst | 6 + docs/source/gre_lwt_tunnel_recipe.rst | 6 + docs/source/gre_ovs_tunnel_recipe.rst | 6 + .../source/gre_tunnel_over_macvlan_recipe.rst | 6 + docs/source/gre_tunnel_over_vlan_recipe.rst | 6 + docs/source/specific_scenarios.rst | 10 + docs/source/vxlan_gpe_tunnel_recipe.rst | 6 + docs/source/vxlan_lwt_tunnel_recipe.rst | 6 + docs/source/vxlan_netns_tunnel_recipe.rst | 6 + docs/source/vxlan_ovs_tunnel_recipe.rst | 6 + lnst/Devices/Device.py | 5 +- lnst/Devices/VxlanDevice.py | 19 ++ lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py | 216 ++++++++++++++++ lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py | 162 ++++++++++++ lnst/Recipes/ENRT/GreLwtTunnelRecipe.py | 221 ++++++++++++++++ lnst/Recipes/ENRT/GreOvsTunnelRecipe.py | 170 ++++++++++++ .../ENRT/GreTunnelOverMacvlanRecipe.py | 200 +++++++++++++++ lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py | 201 +++++++++++++++ lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py | 215 ++++++++++++++++ lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py | 215 ++++++++++++++++ lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py | 241 ++++++++++++++++++ lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py | 181 +++++++++++++ lnst/Recipes/ENRT/__init__.py | 10 + 24 files changed, 2125 insertions(+), 1 deletion(-) create mode 100644 docs/source/geneve_lwt_tunnel_recipe.rst create mode 100644 docs/source/geneve_ovs_tunnel_recipe.rst create mode 100644 docs/source/gre_lwt_tunnel_recipe.rst create mode 100644 docs/source/gre_ovs_tunnel_recipe.rst create mode 100644 docs/source/gre_tunnel_over_macvlan_recipe.rst create mode 100644 docs/source/gre_tunnel_over_vlan_recipe.rst create mode 100644 docs/source/vxlan_gpe_tunnel_recipe.rst create mode 100644 docs/source/vxlan_lwt_tunnel_recipe.rst create mode 100644 docs/source/vxlan_netns_tunnel_recipe.rst create mode 100644 docs/source/vxlan_ovs_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/GreLwtTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/GreOvsTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/GreTunnelOverMacvlanRecipe.py create mode 100644 lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py create mode 100644 lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py
-- 2.26.3
lnst-developers@lists.fedorahosted.org