diff --git a/lnst/Devices/Device.py b/lnst/Devices/Device.py index d64be627d..5e05c0084 100644 --- a/lnst/Devices/Device.py +++ b/lnst/Devices/Device.py @@ -1167,13 +1167,20 @@ def alt_if_names(self): return self._nl_msg.get_attr("IFLA_PROP_LIST").get_attrs("IFLA_ALT_IFNAME") except: return [] - + def keep_addrs_on_down(self): exec_cmd(f"echo 1 > /proc/sys/net/ipv6/conf/{self.name}/keep_addr_on_down") def remove_addrs_on_down(self): exec_cmd(f"echo 0 > /proc/sys/net/ipv6/conf/{self.name}/keep_addr_on_down") + # TODO: implement through pyroute once supported + def vf_trust(self, vf_index: int, trusted: str): + if trusted not in ["on", "off"]: + raise DeviceConfigError(f"Incorrect value for vf trust: {trusted}") + + exec_cmd(f"ip link set dev {self.name} vf {vf_index} trust {trusted}") + #TODO implement proper Route objects #consider the same as with tc? # def route_add(self, dest): diff --git a/lnst/Recipes/ENRT/BondRecipe.py b/lnst/Recipes/ENRT/BondRecipe.py index 5e120d4b6..81268e310 100644 --- a/lnst/Recipes/ENRT/BondRecipe.py +++ b/lnst/Recipes/ENRT/BondRecipe.py @@ -1,8 +1,6 @@ from collections.abc import Collection from lnst.Common.Parameters import ( Param, - IntParam, - StrParam, IPv4NetworkParam, IPv6NetworkParam, ) @@ -18,10 +16,11 @@ CommonHWSubConfigMixin) from lnst.Recipes.ENRT.ConfigMixins.PerfReversibleFlowMixin import ( PerfReversibleFlowMixin) +from lnst.Recipes.ENRT.BondingMixin import BondingMixin from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints -from lnst.Devices import BondDevice +from lnst.Devices import RemoteDevice -class BondRecipe(PerfReversibleFlowMixin, CommonHWSubConfigMixin, OffloadSubConfigMixin, +class BondRecipe(BondingMixin, PerfReversibleFlowMixin, CommonHWSubConfigMixin, OffloadSubConfigMixin, BaremetalEnrtRecipe): """ This recipe implements Enrt testing for a network scenario that looks @@ -43,16 +42,9 @@ class BondRecipe(PerfReversibleFlowMixin, CommonHWSubConfigMixin, OffloadSubConf | host1 | | host2 | '-----------------------------' '----------------' - The recipe provides additional recipe parameters to configure the bonding + Refer to :any:`BondingMixin` for parameters to configure the bonding device. - :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. - All sub configurations are included via Mixin classes. The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. @@ -73,9 +65,6 @@ class BondRecipe(PerfReversibleFlowMixin, CommonHWSubConfigMixin, OffloadSubConf net_ipv4 = IPv4NetworkParam(default="192.168.101.0/24") net_ipv6 = IPv6NetworkParam(default="fc00::/64") - bonding_mode = StrParam(mandatory=True) - miimon_value = IntParam(mandatory=True) - def test_wide_configuration(self): """ Test wide configuration for this recipe involves creating a bonding @@ -91,12 +80,14 @@ def test_wide_configuration(self): host1, host2 = self.matched.host1, self.matched.host2 config = super().test_wide_configuration() - host1.bond0 = BondDevice(mode=self.params.bonding_mode, - miimon=self.params.miimon_value) - for dev in [host1.eth0, host1.eth1]: - dev.down() - host1.bond0.slave_add(dev) + self.create_bond_devices( + { + "host1": { + "bond0": [host1.eth0, host1.eth1] + } + } + ) ipv4_addr = interface_addresses(self.params.net_ipv4) ipv6_addr = interface_addresses(self.params.net_ipv6) @@ -141,6 +132,12 @@ def generate_test_wide_description(self, config: EnrtConfiguration): host1.bond0.miimon ) ] + + if self.params.bonding_mode in ["active-backup", "1"]: + desc += ["Configured {}.{}.fail_over_mac = {}".format( + host1.hostid, host1.bond0.name, + self.params.fail_over_mac + )] return desc def generate_ping_endpoints(self, config): @@ -245,3 +242,7 @@ def pause_frames_dev_list(self): """ return [self.matched.host1.eth0, self.matched.host1.eth1, self.matched.host2.eth0] + + @property + def vf_trust_device_list(self) -> list[RemoteDevice]: + return [sriov_devices.phys_dev for sriov_devices in self.vf_config[self.matched.host1]] diff --git a/lnst/Recipes/ENRT/BondingMixin.py b/lnst/Recipes/ENRT/BondingMixin.py new file mode 100644 index 000000000..2f06386d0 --- /dev/null +++ b/lnst/Recipes/ENRT/BondingMixin.py @@ -0,0 +1,70 @@ +from lnst.Common.Parameters import ( + IntParam, + StrParam, + ChoiceParam, +) +from lnst.Devices import RemoteDevice, BondDevice +from lnst.Controller.Recipe import RecipeError + + +class BondingMixin: + """ + The recipe mixin provides additional recipe parameters to configure the + bonding device. + + :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. + :param fail_over_mac: + the fail_over_mac mode to be configured on the bond0 device. + """ + bonding_mode = StrParam(mandatory=True) + miimon_value = IntParam(mandatory=True) + fail_over_mac = ChoiceParam( + StrParam, choices={"none", "active", "follow"}, default="none" + ) + + def create_bond_devices( + self, bond_devices_specs: dict[str: dict[str: list[RemoteDevice]]] = {} + ) -> None: + """ + The derived class should call: + ``` + self.create_bond_devices( + { + "host1": { + "bond0": [host1.nic1, host1.nic2] + }, + "host2": { + "bond0": [host2.nic1, host2.nic2] + } + } + + That would create devices accessible by host1.bond0 and host2.bond0 + ) + ``` + """ + device_params = dict( + mode=self.params.bonding_mode, + miimon=self.params.miimon_value, + ) + + if self.params.bonding_mode in ["active-backup", "1"]: + device_params["fail_over_mac"] = self.params.fail_over_mac + + for host_str, spec in bond_devices_specs.items(): + for bond_dev_name, bonded_devices in spec.items(): + if len({dev.host for dev in bonded_devices}) > 1: + raise RecipeError( + f"Cannot create bond device with ports coming from different hosts, {bonded_devices}" + ) + + host = getattr(self.matched, host_str) + setattr(host, bond_dev_name, BondDevice(**device_params)) + bond_device = getattr(host, bond_dev_name) + for dev in bonded_devices: + dev.down() + bond_device.slave_add(dev) diff --git a/lnst/Recipes/ENRT/DoubleBondRecipe.py b/lnst/Recipes/ENRT/DoubleBondRecipe.py index 3a74a7408..cdd72b2db 100644 --- a/lnst/Recipes/ENRT/DoubleBondRecipe.py +++ b/lnst/Recipes/ENRT/DoubleBondRecipe.py @@ -1,8 +1,6 @@ from collections.abc import Collection from lnst.Common.Parameters import ( Param, - IntParam, - StrParam, IPv4NetworkParam, IPv6NetworkParam, ) @@ -16,11 +14,39 @@ OffloadSubConfigMixin) from lnst.Recipes.ENRT.ConfigMixins.CommonHWSubConfigMixin import ( CommonHWSubConfigMixin) +from lnst.Recipes.ENRT.BondingMixin import BondingMixin from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints -from lnst.Devices import BondDevice +from lnst.Devices import RemoteDevice -class DoubleBondRecipe(CommonHWSubConfigMixin, OffloadSubConfigMixin, +class DoubleBondRecipe(BondingMixin, CommonHWSubConfigMixin, OffloadSubConfigMixin, BaremetalEnrtRecipe): + """ + This recipe implements Enrt testing for a network scenario that looks + as follows + + .. code-block:: none + + .--------. + .----------------+ +------------------. + | .-------+ switch +---------. | + | | '--------' | | + .-------------------. .-------------------. + | | bond0 | | | | bond0 | | + | .---'--. .---'--. | | .---'--. .---'--. | + .----|-| eth0 |-| eth1 |-|----. .----|-| eth0 |-| eth1 |-|----. + | | '------' '------' | | | | '------' '------' | | + | '-------------------' | | '-------------------' | + | | | | + | host1 | | host2 | + '-----------------------------' '-----------------------------' + + Refer to :any:`BondingMixin` for parameters to configure the bonding + device. + + All sub configurations are included via Mixin classes. + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + """ host1 = HostReq() host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) host1.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) @@ -38,21 +64,25 @@ class DoubleBondRecipe(CommonHWSubConfigMixin, OffloadSubConfigMixin, net_ipv4 = IPv4NetworkParam(default="192.168.101.0/24") net_ipv6 = IPv6NetworkParam(default="fc00::/64") - bonding_mode = StrParam(mandatory=True) - miimon_value = IntParam(mandatory=True) - def test_wide_configuration(self): host1, host2 = self.matched.host1, self.matched.host2 config = super().test_wide_configuration() ipv4_addr = interface_addresses(self.params.net_ipv4) ipv6_addr = interface_addresses(self.params.net_ipv6) + + self.create_bond_devices( + { + "host1": { + "bond0": [host1.eth0, host1.eth1] + }, + "host2": { + "bond0": [host2.eth0, host2.eth1] + } + } + ) + for host in [host1, host2]: - host.bond0 = BondDevice(mode=self.params.bonding_mode, - miimon=self.params.miimon_value) - for dev in [host.eth0, host.eth1]: - dev.down() - host.bond0.slave_add(dev) config.configure_and_track_ip(host.bond0, next(ipv4_addr)) config.configure_and_track_ip(host.bond0, next(ipv6_addr)) for dev in [host.eth0, host.eth1, host.bond0]: @@ -63,7 +93,6 @@ def test_wide_configuration(self): return config def generate_test_wide_description(self, config: EnrtConfiguration): - host1, host2 = self.matched.host1, self.matched.host2 desc = super().generate_test_wide_description(config) desc += [ "\n".join([ @@ -93,6 +122,16 @@ def generate_test_wide_description(self, config: EnrtConfiguration): for dev in config.configured_devices ]) ] + desc.extend([ + "\n".join([ + "Configured {}.{}.fail_over_mac = {}".format( + dev.host.hostid, dev.name, dev.fail_over_mac + ) + for dev in config.configured_devices + ]) + if self.params.bonding_mode in ["active-backup", "1"] else [] + ]) + return desc def generate_ping_endpoints(self, config): @@ -118,3 +157,7 @@ def dev_interrupt_hw_config_dev_list(self): def parallel_stream_qdisc_hw_config_dev_list(self): host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0, host2.eth1] + + @property + def vf_trust_device_list(self) -> list[RemoteDevice]: + return [sriov_devices.phys_dev for sriov_devices_list in self.vf_config.values() for sriov_devices in sriov_devices_list] diff --git a/lnst/Recipes/ENRT/UseVfsMixin.py b/lnst/Recipes/ENRT/UseVfsMixin.py index 5fd5ea450..41db9da80 100644 --- a/lnst/Recipes/ENRT/UseVfsMixin.py +++ b/lnst/Recipes/ENRT/UseVfsMixin.py @@ -1,6 +1,7 @@ -from lnst.Common.Parameters import BoolParam +from lnst.Common.Parameters import BoolParam, ChoiceParam from lnst.Controller.Requirements import DeviceReq from lnst.Recipes.ENRT.SRIOVDevices import SRIOVDevices +from lnst.Devices import RemoteDevice class UseVfsMixin: @@ -12,11 +13,16 @@ class UseVfsMixin: with VF Device instances. This allows user to interact with the network interfaces without additional changes to the code of recipe. + Mixin provides two parameters: + * use_vfs - main boolean parameter to enable or disable (default) use of VFs + * vf_trust - (optional) set the trust parameter of the used VFs, 'on' or 'off' + There are some limitations, for example pause frames cannot be configured since the VF do not support these. """ use_vfs = BoolParam(default=False) + vf_trust = ChoiceParam(choices={'on', 'off'}) def test_wide_configuration(self): config = super().test_wide_configuration() @@ -24,7 +30,7 @@ def test_wide_configuration(self): if not self.params.use_vfs: return config - config.vf_config = {} + self.vf_config = {} for host_key, host_req in self.req: dev_names = [key for key, value in host_req if isinstance(value, DeviceReq)] host = getattr(self.matched, host_key) @@ -33,17 +39,22 @@ def test_wide_configuration(self): for dev_name in dev_names: dev = getattr(host, dev_name) sriov_devices = SRIOVDevices(dev, 1) + vf_dev = sriov_devices.vfs[0] host.map_device(dev_name, {"ifname": vf_dev.name}) - host_config = config.vf_config.setdefault(host, []) + host_config = self.vf_config.setdefault(host, []) host_config.append(sriov_devices) + if self.params.get("vf_trust"): + for dev in self.vf_trust_device_list: + dev.vf_trust(0, self.params.vf_trust) + return config def test_wide_deconfiguration(self, config): if self.params.use_vfs: - for host, sriov_devices_list in config.vf_config.items(): + for host, sriov_devices_list in self.vf_config.items(): for sriov_devices in sriov_devices_list: vf_dev = sriov_devices.vfs[0] host.map_device(vf_dev._id, {"ifname": sriov_devices.phys_dev.name}) @@ -56,10 +67,14 @@ def generate_test_wide_description(self, config): if self.params.use_vfs: description += [ - f"Using vf device {vf_dev.name} of pf {sriov_devices.phys_dev.name} for DeviceReq {host.hostid}.{vf_dev._id}" - for host, sriov_devices_list in config.vf_config.items() + f"Using vf device {vf_dev.name} of pf {sriov_devices.phys_dev.name} for DeviceReq {host.hostid}.{vf_dev._id}" + (f" trusted={self.params.vf_trust}" if self.params.get("vf_trust") and sriov_devices.phys_dev in self.vf_trust_device_list else "") + for host, sriov_devices_list in self.vf_config.items() for sriov_devices in sriov_devices_list for vf_dev in sriov_devices.vfs ] return description + + @property + def vf_trust_device_list(self) -> list[RemoteDevice]: + return [] diff --git a/lnst/Recipes/ENRT/VlansOverBondRecipe.py b/lnst/Recipes/ENRT/VlansOverBondRecipe.py index 3ae680a6f..c0242a76e 100644 --- a/lnst/Recipes/ENRT/VlansOverBondRecipe.py +++ b/lnst/Recipes/ENRT/VlansOverBondRecipe.py @@ -2,7 +2,6 @@ from lnst.Common.Parameters import ( Param, IntParam, - StrParam, IPv4NetworkParam, IPv6NetworkParam, ) @@ -18,13 +17,14 @@ CommonHWSubConfigMixin) from lnst.Recipes.ENRT.ConfigMixins.PerfReversibleFlowMixin import ( PerfReversibleFlowMixin) +from lnst.Recipes.ENRT.BondingMixin import BondingMixin from lnst.Devices import VlanDevice from lnst.Devices.VlanDevice import VlanDevice as Vlan -from lnst.Devices import BondDevice +from lnst.Devices import RemoteDevice from lnst.Recipes.ENRT.PingMixins import VlanPingEvaluatorMixin from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints -class VlansOverBondRecipe(PerfReversibleFlowMixin, VlanPingEvaluatorMixin, +class VlansOverBondRecipe(BondingMixin, PerfReversibleFlowMixin, VlanPingEvaluatorMixin, CommonHWSubConfigMixin, OffloadSubConfigMixin, BaremetalEnrtRecipe): r""" @@ -50,16 +50,9 @@ class VlansOverBondRecipe(PerfReversibleFlowMixin, VlanPingEvaluatorMixin, | host1 | | host2 | '------------------------' '---------------------' - The recipe provides additional recipe parameters to configure the bonding + Refer to :any:`BondingMixin` for parameters to configure the bonding device. - :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 - All sub configurations are included via Mixin classes. The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. @@ -90,9 +83,6 @@ class VlansOverBondRecipe(PerfReversibleFlowMixin, VlanPingEvaluatorMixin, vlan2_ipv4 = IPv4NetworkParam(default="192.168.30.0/24") vlan2_ipv6 = IPv6NetworkParam(default="fc00:0:0:3::/64") - bonding_mode = StrParam(mandatory=True) - miimon_value = IntParam(mandatory=True) - def test_wide_configuration(self): """ Test wide configuration for this recipe involves creating one bonding @@ -117,11 +107,14 @@ def test_wide_configuration(self): host1, host2 = self.matched.host1, self.matched.host2 config = super().test_wide_configuration() - host1.bond0 = BondDevice(mode=self.params.bonding_mode, - miimon=self.params.miimon_value) - for dev in [host1.eth0, host1.eth1]: - dev.down() - host1.bond0.slave_add(dev) + self.create_bond_devices( + { + "host1": + { + "bond0": [host1.eth0, host1.eth1] + } + } + ) host1.vlan0 = VlanDevice(realdev=host1.bond0, vlan_id=self.params.vlan0_id) host1.vlan1 = VlanDevice(realdev=host1.bond0, vlan_id=self.params.vlan1_id) @@ -200,6 +193,12 @@ def generate_test_wide_description(self, config: EnrtConfiguration): host1.bond0.miimon ) ] + + if self.params.bonding_mode in ["active-backup", "1"]: + desc += ["Configured {}.{}.fail_over_mac = {}".format( + host1.hostid, host1.bond0.name, + host1.bond0.fail_over_mac + )] return desc def generate_ping_endpoints(self, config): @@ -309,3 +308,7 @@ def pause_frames_dev_list(self): """ host1, host2 = self.matched.host1, self.matched.host2 return [host1.eth0, host1.eth1, host2.eth0] + + @property + def vf_trust_device_list(self) -> list[RemoteDevice]: + return [sriov_devices.phys_dev for sriov_devices in self.vf_config[self.matched.host1]]