This patch series adds several functional tunnel recipes to ENRT recipe package.
Jan Tluka (6): Recipes.ENRT: add Ip6GreTunnelRecipe Recipes.ENRT: add SitTunnelRecipe Recipes.ENRT: add GreTunnelOverBondRecipe Recipes.ENRT: add IpIpTunnelRecipe Recipes.ENRT: add Ip6TnlTunnelRecipe Recipes.ENRT: add GeneveTunnelRecipe
docs/source/geneve_tunnel_recipe.rst | 6 + docs/source/gre_tunnel_over_bond_recipe.rst | 6 + docs/source/ip6gre_tunnel_recipe.rst | 6 + docs/source/ip6tnl_tunnel_recipe.rst | 6 + docs/source/ipip_tunnel_recipe.rst | 6 + docs/source/sit_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 6 + lnst/Recipes/ENRT/GeneveTunnelRecipe.py | 184 +++++++++++++++ lnst/Recipes/ENRT/GreTunnelOverBondRecipe.py | 227 +++++++++++++++++++ lnst/Recipes/ENRT/Ip6GreTunnelRecipe.py | 187 +++++++++++++++ lnst/Recipes/ENRT/Ip6TnlTunnelRecipe.py | 154 +++++++++++++ lnst/Recipes/ENRT/IpIpTunnelRecipe.py | 154 +++++++++++++ lnst/Recipes/ENRT/SitTunnelRecipe.py | 171 ++++++++++++++ lnst/Recipes/ENRT/__init__.py | 6 + 14 files changed, 1125 insertions(+) create mode 100644 docs/source/geneve_tunnel_recipe.rst create mode 100644 docs/source/gre_tunnel_over_bond_recipe.rst create mode 100644 docs/source/ip6gre_tunnel_recipe.rst create mode 100644 docs/source/ip6tnl_tunnel_recipe.rst create mode 100644 docs/source/ipip_tunnel_recipe.rst create mode 100644 docs/source/sit_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GeneveTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/GreTunnelOverBondRecipe.py create mode 100644 lnst/Recipes/ENRT/Ip6GreTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/Ip6TnlTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/IpIpTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/SitTunnelRecipe.py
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/ip6gre_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/Ip6GreTunnelRecipe.py | 187 ++++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 195 insertions(+) create mode 100644 docs/source/ip6gre_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/Ip6GreTunnelRecipe.py
diff --git a/docs/source/ip6gre_tunnel_recipe.rst b/docs/source/ip6gre_tunnel_recipe.rst new file mode 100644 index 00000000..0e55d350 --- /dev/null +++ b/docs/source/ip6gre_tunnel_recipe.rst @@ -0,0 +1,6 @@ +Ip6GreTunnelRecipe +^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.Ip6GreTunnelRecipe.Ip6GreTunnelRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 0cf999cf..17d2bf18 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -8,4 +8,5 @@ Specific ENRT scenarios vlans_recipe vlans_over_bond_recipe gre_tunnel_recipe + ip6gre_tunnel_recipe l2tp_tunnel_recipe diff --git a/lnst/Recipes/ENRT/Ip6GreTunnelRecipe.py b/lnst/Recipes/ENRT/Ip6GreTunnelRecipe.py new file mode 100644 index 00000000..67fc8fc4 --- /dev/null +++ b/lnst/Recipes/ENRT/Ip6GreTunnelRecipe.py @@ -0,0 +1,187 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET6, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Common.Parameters import Param +from lnst.Devices import Ip6GreDevice +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 Ip6GreTunnelRecipe( + CommonHWSubConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a simple IP6GRE tunnel between + two hosts. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | | | + | ----' '--- | | ----' '--- | + | gre6 tunnel | | gre6 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("fc00:0:0:0::" + 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 IP6GRE 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_INET6, "is_link_local": False} + 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.gre6_tunnel = Ip6GreDevice(local=endpoint1_ip, remote=endpoint2_ip) + m2.gre6_tunnel = Ip6GreDevice(local=endpoint2_ip, remote=endpoint1_ip) + + # A + m1.gre6_tunnel.up() + m1.gre6_tunnel.ip_add(a_ip4) + m1.gre6_tunnel.ip_add(a_ip6) + m1.run("ip -4 route add {} dev {}".format(b_net4, m1.gre6_tunnel.name)) + m1.run("ip -6 route add {} dev {}".format(b_net6, m1.gre6_tunnel.name)) + + # B + m2.gre6_tunnel.up() + m2.gre6_tunnel.ip_add(b_ip4) + m2.gre6_tunnel.ip_add(b_ip6) + m2.run("ip -4 route add {} dev {}".format(a_net4, m2.gre6_tunnel.name)) + m2.run("ip -6 route add {} dev {}".format(a_net6, m2.gre6_tunnel.name)) + + configuration.tunnel_devices.extend([m1.gre6_tunnel, m2.gre6_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.gre6_tunnel, self.matched.host2.gre6_tunnel)] + """ + return [ + PingEndpoints( + self.matched.host1.gre6_tunnel, self.matched.host2.gre6_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 IP6GRE. + """ + 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"] = "ip6" + + if isinstance(ip2, Ip4Address): + pat1 = "{} > {}:( DSTOPT)? GREv0, .* IP {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}:( DSTOPT)? GREv0 | {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + elif isinstance(ip2, Ip6Address): + pat1 = ( + "{} > {}:( DSTOPT)? GREv0, .* IP6 {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + ) + pat2 = "{} > {}:( DSTOPT)? 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 4d057a8d..d7551913 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -81,6 +81,7 @@ from lnst.Recipes.ENRT.VlansRecipe import VlansRecipe 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.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe
from lnst.Recipes.ENRT.BaseEnrtRecipe import BaseEnrtRecipe from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/sit_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/SitTunnelRecipe.py | 171 +++++++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 179 insertions(+) create mode 100644 docs/source/sit_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/SitTunnelRecipe.py
diff --git a/docs/source/sit_tunnel_recipe.rst b/docs/source/sit_tunnel_recipe.rst new file mode 100644 index 00000000..1784e810 --- /dev/null +++ b/docs/source/sit_tunnel_recipe.rst @@ -0,0 +1,6 @@ +SitTunnelRecipe +^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.SitTunnelRecipe.SitTunnelRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 17d2bf18..e27ed9a2 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -9,4 +9,5 @@ Specific ENRT scenarios vlans_over_bond_recipe gre_tunnel_recipe ip6gre_tunnel_recipe + sit_tunnel_recipe l2tp_tunnel_recipe diff --git a/lnst/Recipes/ENRT/SitTunnelRecipe.py b/lnst/Recipes/ENRT/SitTunnelRecipe.py new file mode 100644 index 00000000..01de5767 --- /dev/null +++ b/lnst/Recipes/ENRT/SitTunnelRecipe.py @@ -0,0 +1,171 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Devices import SitDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Common.Parameters import StrParam, ChoiceParam +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe + + +class SitTunnelRecipe(BaseTunnelRecipe): + """ + This class implements a recipe that configures a simple SIT tunnel between + two hosts. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | | | + | ----' '--- | | ----' '--- | + | sit tunnel | | sit 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 tunnel_mode: + this parameter specifies the mode of the SIT tunnel, can be any of + the **any**, **ip6ip6**, **ipip** or **mplsip** + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + tunnel_mode = ChoiceParam( + type=StrParam, choices=set(["any", "ip6ip", "ipip", "mplsip"]), mandatory=True + ) + + 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) + + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + The SIT 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.sit_tunnel = SitDevice( + local=endpoint1_ip, remote=endpoint2_ip, mode=self.params.tunnel_mode + ) + m2.sit_tunnel = SitDevice( + local=endpoint2_ip, remote=endpoint1_ip, mode=self.params.tunnel_mode + ) + + # A + m1.sit_tunnel.up() + m1.sit_tunnel.ip_add(a_ip4) + m1.sit_tunnel.ip_add(a_ip6) + m1.run("ip -4 route add {} dev {}".format(b_net4, m1.sit_tunnel.name)) + m1.run("ip -6 route add {} dev {}".format(b_net6, m1.sit_tunnel.name)) + + # B + m2.sit_tunnel.up() + m2.sit_tunnel.ip_add(b_ip4) + m2.sit_tunnel.ip_add(b_ip6) + m2.run("ip -4 route add {} dev {}".format(a_net4, m2.sit_tunnel.name)) + m2.run("ip -6 route add {} dev {}".format(a_net6, m2.sit_tunnel.name)) + + configuration.tunnel_devices.extend([m1.sit_tunnel, m2.sit_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.sit_tunnel, self.matched.host2.sit_tunnel)] + """ + return [ + PingEndpoints(self.matched.host1.sit_tunnel, self.matched.host2.sit_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 SIT. + """ + 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 host {}".format(m1_carrier_ip) + + # TODO: handle mplsip mode + if isinstance(ip2, Ip4Address): + grep_pattern = [ + "IP {} > {}: IP {} > {}: ICMP".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + ] + + elif isinstance(ip2, Ip6Address): + grep_pattern = [ + "IP {} > {}: IP6 {} > {}: ICMP6".format( + m1_carrier_ip, m2_carrier_ip, 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 diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index d7551913..905b840f 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.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe +from lnst.Recipes.ENRT.SitTunnelRecipe import SitTunnelRecipe
from lnst.Recipes.ENRT.BaseEnrtRecipe import BaseEnrtRecipe from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/gre_tunnel_over_bond_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/GreTunnelOverBondRecipe.py | 227 +++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 235 insertions(+) create mode 100644 docs/source/gre_tunnel_over_bond_recipe.rst create mode 100644 lnst/Recipes/ENRT/GreTunnelOverBondRecipe.py
diff --git a/docs/source/gre_tunnel_over_bond_recipe.rst b/docs/source/gre_tunnel_over_bond_recipe.rst new file mode 100644 index 00000000..fc9f3b63 --- /dev/null +++ b/docs/source/gre_tunnel_over_bond_recipe.rst @@ -0,0 +1,6 @@ +GreTunnelOverBondRecipe +^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.GreTunnelOverBondRecipe.GreTunnelOverBondRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index e27ed9a2..df249c11 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_tunnel_over_bond_recipe ip6gre_tunnel_recipe sit_tunnel_recipe l2tp_tunnel_recipe diff --git a/lnst/Recipes/ENRT/GreTunnelOverBondRecipe.py b/lnst/Recipes/ENRT/GreTunnelOverBondRecipe.py new file mode 100644 index 00000000..3aa00b87 --- /dev/null +++ b/lnst/Recipes/ENRT/GreTunnelOverBondRecipe.py @@ -0,0 +1,227 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Common.Parameters import Param, IntParam, StrParam +from lnst.Devices import GreDevice, BondDevice +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 GreTunnelOverBondRecipe( + CommonHWSubConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a GRE tunnel between + two hosts that are connected through a bond device. + + .. code-block:: none + + .--------. + .------.---| switch |---.------. + | | '--------' | | + | | | | + .----|-- ---|----. .----|-- ---|----. + | .--'-. .-'--. | | .--'-. .-'--. | + | |eth0| |eth1| | | |eth0| |eth1| | + | '----' '----' | | '----' '----' | + | \ / | | \ / | + | .----/-. | | .----/-. | + | | bond0 | | | | bond0 | | + | '-------' | | '-------' | + | | | | | | | | + | ---' '---- | | ---' '---- | + | gre tunnel | | gre 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. + + The recipe provides additional parameter: + + :param bonding_mode: + (mandatory test parameter) the bonding mode to be configured on + the bond0 device. + :param miimon_value: + (mandatory test parameter) the miimon interval to be configured + on the bond0 device. + """ + + 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")) + + bonding_mode = StrParam(mandatory=True) + miimon_value = IntParam(mandatory=True) + 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 bonded together by bonding device on both of the matched + hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + + host1.bond = BondDevice( + mode=self.params.bonding_mode, miimon=self.params.miimon_value + ) + host2.bond = BondDevice( + mode=self.params.bonding_mode, miimon=self.params.miimon_value + ) + + for host, devices in [ + (host1, [host1.eth0, host1.eth1]), + (host2, [host2.eth0, host2.eth1]), + ]: + for dev in devices: + dev.down() + host.bond.slave_add(dev) + + for i, device in enumerate([host1.bond, host2.bond]): + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + configuration.test_wide_devices.append(device) + + for dev in [ + host1.eth0, + host1.eth1, + host1.bond, + host2.eth0, + host2.eth0, + host2.bond, + ]: + dev.up() + + configuration.tunnel_endpoints = (host1.bond, host2.bond) + + def create_tunnel(self, configuration): + """ + The GRE tunnel devices are configured with local and remote ip addresses + matching the bond 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.bond + m2_carrier = self.matched.host2.bond + 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.bond, self.matched.host2.bond] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index 905b840f..f11a88f1 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -81,6 +81,7 @@ from lnst.Recipes.ENRT.VlansRecipe import VlansRecipe 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.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe from lnst.Recipes.ENRT.SitTunnelRecipe import SitTunnelRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/ipip_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/IpIpTunnelRecipe.py | 154 ++++++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 162 insertions(+) create mode 100644 docs/source/ipip_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/IpIpTunnelRecipe.py
diff --git a/docs/source/ipip_tunnel_recipe.rst b/docs/source/ipip_tunnel_recipe.rst new file mode 100644 index 00000000..d5cafa71 --- /dev/null +++ b/docs/source/ipip_tunnel_recipe.rst @@ -0,0 +1,6 @@ +IpIpTunnelRecipe +^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.IpIpTunnelRecipe.IpIpTunnelRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index df249c11..fb515246 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -11,4 +11,5 @@ Specific ENRT scenarios gre_tunnel_over_bond_recipe ip6gre_tunnel_recipe sit_tunnel_recipe + ipip_tunnel_recipe l2tp_tunnel_recipe diff --git a/lnst/Recipes/ENRT/IpIpTunnelRecipe.py b/lnst/Recipes/ENRT/IpIpTunnelRecipe.py new file mode 100644 index 00000000..db1694d3 --- /dev/null +++ b/lnst/Recipes/ENRT/IpIpTunnelRecipe.py @@ -0,0 +1,154 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + ipaddress, +) +from lnst.Devices import IpIpDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Common.Parameters import StrParam, ChoiceParam +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe + + +class IpIpTunnelRecipe(BaseTunnelRecipe): + """ + This class implements a recipe that configures a simple IpIp tunnel between + two hosts. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | | | + | ----' '--- | | ----' '--- | + | ipip tunnel | | ipip 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 tunnel_mode: + this parameter specifies the mode of the IPIP tunnel, can be any + of the **any**, **ipip** or **mplsip** + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + tunnel_mode = ChoiceParam( + type=StrParam, choices=set(["any", "ipip", "mplsip"]), mandatory=True + ) + + 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("172.16.200." + str(i + 1) + "/16")) + device.up() + configuration.test_wide_devices.append(device) + + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + The ipip tunnel devices are configured with IPv4 addresses. + """ + 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 = ipaddress("192.168.200.1/24") + b_ip4 = ipaddress("192.168.200.2/24") + + m1.ipip_tunnel = IpIpDevice( + local=endpoint1_ip, + remote=endpoint2_ip, + mode=self.params.tunnel_mode, + ttl=64, + ) + m2.ipip_tunnel = IpIpDevice( + local=endpoint2_ip, + remote=endpoint1_ip, + mode=self.params.tunnel_mode, + ttl=64, + ) + + # A + m1.ipip_tunnel.up() + m1.ipip_tunnel.ip_add(a_ip4) + + # B + m2.ipip_tunnel.up() + m2.ipip_tunnel.ip_add(b_ip4) + + configuration.tunnel_devices.extend([m1.ipip_tunnel, m2.ipip_tunnel]) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are simply the tunnel endpoints + + Returned as:: + + [PingEndpoints(self.matched.host1.ipip_tunnel, self.matched.host2.ipip_tunnel)] + """ + return [ + PingEndpoints( + self.matched.host1.ipip_tunnel, self.matched.host2.ipip_tunnel + ) + ] + + 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 echo requests encapsulated + by IPIP. + """ + 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 host {}".format(m1_carrier_ip) + + # TODO: handle mplsip mode + grep_pattern = [ + "IP {} > {}: IP {} > {}: ICMP".format( + m1_carrier_ip, m2_carrier_ip, 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 diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index f11a88f1..81faee7c 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -84,6 +84,7 @@ from lnst.Recipes.ENRT.GreTunnelRecipe import GreTunnelRecipe from lnst.Recipes.ENRT.GreTunnelOverBondRecipe import GreTunnelOverBondRecipe from lnst.Recipes.ENRT.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe from lnst.Recipes.ENRT.SitTunnelRecipe import SitTunnelRecipe +from lnst.Recipes.ENRT.IpIpTunnelRecipe import IpIpTunnelRecipe
from lnst.Recipes.ENRT.BaseEnrtRecipe import BaseEnrtRecipe from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/ip6tnl_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/Ip6TnlTunnelRecipe.py | 154 ++++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 162 insertions(+) create mode 100644 docs/source/ip6tnl_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/Ip6TnlTunnelRecipe.py
diff --git a/docs/source/ip6tnl_tunnel_recipe.rst b/docs/source/ip6tnl_tunnel_recipe.rst new file mode 100644 index 00000000..e3d57d68 --- /dev/null +++ b/docs/source/ip6tnl_tunnel_recipe.rst @@ -0,0 +1,6 @@ +Ip6TnlTunnelRecipe +^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.Ip6TnlTunnelRecipe.Ip6TnlTunnelRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index fb515246..0e56c335 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -12,4 +12,5 @@ Specific ENRT scenarios ip6gre_tunnel_recipe sit_tunnel_recipe ipip_tunnel_recipe + ip6tnl_tunnel_recipe l2tp_tunnel_recipe diff --git a/lnst/Recipes/ENRT/Ip6TnlTunnelRecipe.py b/lnst/Recipes/ENRT/Ip6TnlTunnelRecipe.py new file mode 100644 index 00000000..eaa9cba4 --- /dev/null +++ b/lnst/Recipes/ENRT/Ip6TnlTunnelRecipe.py @@ -0,0 +1,154 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET6, + ipaddress, +) +from lnst.Devices import Ip6TnlDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Common.Parameters import StrParam, ChoiceParam +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe + + +class Ip6TnlTunnelRecipe(BaseTunnelRecipe): + """ + This class implements a recipe that configures a simple Ip6Tnl tunnel between + two hosts. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | | | + | ----' '--- | | ----' '--- | + | ip6tnl tunnel| | ip6tnl 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 tunnel_mode: + this parameter specifies the mode of the ip6tnl tunnel, can be any + of the **any**, **ipip6** or **ip6ip6** + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + tunnel_mode = ChoiceParam( + type=StrParam, choices=set(["any", "ipip6", "ip6ip6"]), mandatory=True + ) + + 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("fc00::" + str(i + 1) + "/64")) + device.up() + configuration.test_wide_devices.append(device) + + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + The ip6tnl tunnel devices are configured with IPv6 addresses. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + ip_filter = {"family": AF_INET6} + endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0] + endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0] + + a_ip6 = ipaddress("3001:db8:ac10:fe01::2/64") + b_ip6 = ipaddress("3001:db8:ac10:fe01::3/64") + + m1.ip6tnl = Ip6TnlDevice( + local=endpoint1_ip, + remote=endpoint2_ip, + mode=self.params.tunnel_mode, + ttl=64, + ) + m2.ip6tnl = Ip6TnlDevice( + local=endpoint2_ip, + remote=endpoint1_ip, + mode=self.params.tunnel_mode, + ttl=64, + ) + + # A + m1.ip6tnl.up() + m1.ip6tnl.ip_add(a_ip6) + + # B + m2.ip6tnl.up() + m2.ip6tnl.ip_add(b_ip6) + + configuration.tunnel_devices.extend([m1.ip6tnl, m2.ip6tnl]) + 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.ip6tnl, self.matched.host2.ip6tnl)] + """ + return [ + PingEndpoints( + self.matched.host1.ip6tnl, self.matched.host2.ip6tnl + ) + ] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for ip6 protocol + and grep patterns to match the ICMP6 echo requests encapsulated + by Ip6Tnl. + """ + 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"] = "ip6" + + grep_pattern = [ + "IP6 {} > {}: DSTOPT IP6 {} > {}: ICMP6".format( + m1_carrier_ip, m2_carrier_ip, 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 diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index 81faee7c..8de93e25 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -85,6 +85,7 @@ from lnst.Recipes.ENRT.GreTunnelOverBondRecipe import GreTunnelOverBondRecipe from lnst.Recipes.ENRT.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe 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.BaseEnrtRecipe import BaseEnrtRecipe from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/geneve_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/GeneveTunnelRecipe.py | 184 ++++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 192 insertions(+) create mode 100644 docs/source/geneve_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GeneveTunnelRecipe.py
diff --git a/docs/source/geneve_tunnel_recipe.rst b/docs/source/geneve_tunnel_recipe.rst new file mode 100644 index 00000000..6ac8e5b8 --- /dev/null +++ b/docs/source/geneve_tunnel_recipe.rst @@ -0,0 +1,6 @@ +GeneveTunnelRecipe +^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.GeneveTunnelRecipe.GeneveTunnelRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 0e56c335..011db055 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -13,4 +13,5 @@ Specific ENRT scenarios sit_tunnel_recipe ipip_tunnel_recipe ip6tnl_tunnel_recipe + geneve_tunnel_recipe l2tp_tunnel_recipe diff --git a/lnst/Recipes/ENRT/GeneveTunnelRecipe.py b/lnst/Recipes/ENRT/GeneveTunnelRecipe.py new file mode 100644 index 00000000..43aaa954 --- /dev/null +++ b/lnst/Recipes/ENRT/GeneveTunnelRecipe.py @@ -0,0 +1,184 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + AF_INET6, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Devices import GeneveDevice +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 GeneveTunnelRecipe(BaseTunnelRecipe): + """ + This class implements a recipe that configures a simple Geneve 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"), + ) + ) + + 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 configured with IPv4 and IPv6 addresses. + """ + 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] + + a_ip4 = Ip4Address("20.0.0.10/8") + b_ip4 = Ip4Address("20.0.0.20/8") + + a_ip6 = Ip6Address("fee0::10/64") + b_ip6 = Ip6Address("fee0::20/64") + + m1.gnv_tunnel = GeneveDevice(remote=endpoint2_ip, id=1234) + m2.gnv_tunnel = GeneveDevice(remote=endpoint1_ip, id=1234) + + # A + m1.gnv_tunnel.mtu = 1400 + m1.gnv_tunnel.up() + m1.gnv_tunnel.ip_add(a_ip4) + m1.gnv_tunnel.ip_add(a_ip6) + + # B + m2.gnv_tunnel.mtu = 1400 + m2.gnv_tunnel.up() + m2.gnv_tunnel.ip_add(b_ip4) + m2.gnv_tunnel.ip_add(b_ip6) + + configuration.tunnel_devices.extend([m1.gnv_tunnel, m2.gnv_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.gnv_tunnel, self.matched.host2.gnv_tunnel)] + """ + return [ + PingEndpoints(self.matched.host1.gnv_tunnel, self.matched.host2.gnv_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 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 8de93e25..0b7f4f86 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -86,6 +86,7 @@ from lnst.Recipes.ENRT.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe 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.BaseEnrtRecipe import BaseEnrtRecipe from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe
pushed, thanks.
-Ondrej
On Thu, May 13, 2021 at 02:35:23PM +0200, Jan Tluka wrote:
This patch series adds several functional tunnel recipes to ENRT recipe package.
Jan Tluka (6): Recipes.ENRT: add Ip6GreTunnelRecipe Recipes.ENRT: add SitTunnelRecipe Recipes.ENRT: add GreTunnelOverBondRecipe Recipes.ENRT: add IpIpTunnelRecipe Recipes.ENRT: add Ip6TnlTunnelRecipe Recipes.ENRT: add GeneveTunnelRecipe
docs/source/geneve_tunnel_recipe.rst | 6 + docs/source/gre_tunnel_over_bond_recipe.rst | 6 + docs/source/ip6gre_tunnel_recipe.rst | 6 + docs/source/ip6tnl_tunnel_recipe.rst | 6 + docs/source/ipip_tunnel_recipe.rst | 6 + docs/source/sit_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 6 + lnst/Recipes/ENRT/GeneveTunnelRecipe.py | 184 +++++++++++++++ lnst/Recipes/ENRT/GreTunnelOverBondRecipe.py | 227 +++++++++++++++++++ lnst/Recipes/ENRT/Ip6GreTunnelRecipe.py | 187 +++++++++++++++ lnst/Recipes/ENRT/Ip6TnlTunnelRecipe.py | 154 +++++++++++++ lnst/Recipes/ENRT/IpIpTunnelRecipe.py | 154 +++++++++++++ lnst/Recipes/ENRT/SitTunnelRecipe.py | 171 ++++++++++++++ lnst/Recipes/ENRT/__init__.py | 6 + 14 files changed, 1125 insertions(+) create mode 100644 docs/source/geneve_tunnel_recipe.rst create mode 100644 docs/source/gre_tunnel_over_bond_recipe.rst create mode 100644 docs/source/ip6gre_tunnel_recipe.rst create mode 100644 docs/source/ip6tnl_tunnel_recipe.rst create mode 100644 docs/source/ipip_tunnel_recipe.rst create mode 100644 docs/source/sit_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GeneveTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/GreTunnelOverBondRecipe.py create mode 100644 lnst/Recipes/ENRT/Ip6GreTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/Ip6TnlTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/IpIpTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/SitTunnelRecipe.py
-- 2.26.3 _______________________________________________ LNST-developers mailing list -- lnst-developers@lists.fedorahosted.org To unsubscribe send an email to lnst-developers-leave@lists.fedorahosted.org Fedora Code of Conduct: https://docs.fedoraproject.org/en-US/project/code-of-conduct/ List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines List Archives: https://lists.fedorahosted.org/archives/list/lnst-developers@lists.fedorahos... Do not reply to spam on the list, report it: https://pagure.io/fedora-infrastructure
lnst-developers@lists.fedorahosted.org