Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fail_over_mac parameter to bonding recipes #380

Closed
wants to merge 9 commits into from
9 changes: 8 additions & 1 deletion lnst/Devices/Device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
41 changes: 21 additions & 20 deletions lnst/Recipes/ENRT/BondRecipe.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from collections.abc import Collection
from lnst.Common.Parameters import (
Param,
IntParam,
StrParam,
IPv4NetworkParam,
IPv6NetworkParam,
)
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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]]
70 changes: 70 additions & 0 deletions lnst/Recipes/ENRT/BondingMixin.py
Original file line number Diff line number Diff line change
@@ -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)
69 changes: 56 additions & 13 deletions lnst/Recipes/ENRT/DoubleBondRecipe.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from collections.abc import Collection
from lnst.Common.Parameters import (
Param,
IntParam,
StrParam,
IPv4NetworkParam,
IPv6NetworkParam,
)
Expand All @@ -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"))
Expand All @@ -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]:
Expand All @@ -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([
Expand Down Expand Up @@ -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):
Expand All @@ -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]
27 changes: 21 additions & 6 deletions lnst/Recipes/ENRT/UseVfsMixin.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -12,19 +13,24 @@ 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()

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)
Expand All @@ -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})
Expand All @@ -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 []
Loading
Loading