suse_ip module implementation

This commit is contained in:
Victor Zhestkov 2021-08-09 11:04:29 +03:00 committed by Gareth J. Greenaway
parent 190ab7cb2f
commit 83c5e0f05a
8 changed files with 1966 additions and 14 deletions

View file

@ -18,6 +18,8 @@ def __virtual__():
return (False, "Module linux_ip: Windows systems are not supported.")
if __grains__["os_family"] == "RedHat":
return (False, "Module linux_ip: RedHat systems are not supported.")
if __grains__["os_family"] == "Suse":
return (False, "Module linux_ip: SUSE systems are not supported.")
if __grains__["os_family"] == "Debian":
return (False, "Module linux_ip: Debian systems are not supported.")
if __grains__["os_family"] == "NILinuxRT":

1166
salt/modules/suse_ip.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -650,25 +650,30 @@ def managed(name, enabled=True, **kwargs):
present_slaves = __salt__["cmd.run"](
["cat", "/sys/class/net/{}/bonding/slaves".format(name)]
).split()
desired_slaves = kwargs["slaves"].split()
if isinstance(kwargs['slaves'], list):
desired_slaves = kwargs['slaves']
else:
desired_slaves = kwargs['slaves'].split()
missing_slaves = set(desired_slaves) - set(present_slaves)
# Enslave only slaves missing in master
if missing_slaves:
ifenslave_path = __salt__["cmd.run"](["which", "ifenslave"]).strip()
if ifenslave_path:
log.info(
"Adding slaves '%s' to the master %s",
" ".join(missing_slaves),
name,
log.debug("Missing slaves of {}: {}".format(name, missing_slaves))
if __grains__["os_family"] != "Suse":
ifenslave_path = __salt__["cmd.run"](["which", "ifenslave"]).strip()
if ifenslave_path:
log.info(
"Adding slaves '%s' to the master %s",
" ".join(missing_slaves),
name,
)
cmd = [ifenslave_path, name] + list(missing_slaves)
__salt__["cmd.run"](cmd, python_shell=False)
else:
log.error("Command 'ifenslave' not found")
ret["changes"]["enslave"] = "Added slaves '{}' to master '{}'".format(
" ".join(missing_slaves), name
)
cmd = [ifenslave_path, name] + list(missing_slaves)
__salt__["cmd.run"](cmd, python_shell=False)
else:
log.error("Command 'ifenslave' not found")
ret["changes"]["enslave"] = "Added slaves '{}' to master '{}'".format(
" ".join(missing_slaves), name
)
else:
log.info(
"All slaves '%s' are already added to the master %s"

View file

@ -0,0 +1,34 @@
{% if nickname %}NAME='{{nickname}}'
{%endif%}{% if startmode %}STARTMODE='{{startmode}}'
{%endif%}{% if proto %}BOOTPROTO='{{proto}}'
{%endif%}{% if uuid %}UUID='{{uuid}}'
{%endif%}{% if vlan %}VLAN='{{vlan}}'
{%endif%}{% if team_config %}TEAM_CONFIG='{{team_config}}'
{%endif%}{% if team_port_config %}TEAM_PORT_CONFIG='{{team_port_config}}'
{%endif%}{% if team_master %}TEAM_MASTER='{{team_master}}'
{%endif%}{% if ipaddr %}IPADDR='{{ipaddr}}'
{%endif%}{% if netmask %}NETMASK='{{netmask}}'
{%endif%}{% if prefix %}PREFIXLEN="{{prefix}}"
{%endif%}{% if ipaddrs %}{% for i in ipaddrs -%}
IPADDR{{loop.index}}='{{i}}'
{% endfor -%}
{%endif%}{% if clonenum_start %}CLONENUM_START="{{clonenum_start}}"
{%endif%}{% if gateway %}GATEWAY="{{gateway}}"
{%endif%}{% if arpcheck %}ARPCHECK="{{arpcheck}}"
{%endif%}{% if srcaddr %}SRCADDR="{{srcaddr}}"
{%endif%}{% if defroute %}DEFROUTE="{{defroute}}"
{%endif%}{% if bridge %}BRIDGE="{{bridge}}"
{%endif%}{% if stp %}STP="{{stp}}"
{%endif%}{% if delay or delay == 0 %}DELAY="{{delay}}"
{%endif%}{% if mtu %}MTU='{{mtu}}'
{%endif%}{% if zone %}ZONE='{{zone}}'
{%endif%}{% if bonding %}BONDING_MODULE_OPTS='{{bonding}}'
BONDING_MASTER='yes'
{% for sl in slaves -%}
BONDING_SLAVE{{loop.index}}='{{sl}}'
{% endfor -%}
{%endif%}{% if ethtool %}ETHTOOL_OPTIONS='{{ethtool}}'
{%endif%}{% if phys_dev %}ETHERDEVICE='{{phys_dev}}'
{%endif%}{% if vlan_id %}VLAN_ID='{{vlan_id}}'
{%endif%}{% if userctl %}USERCONTROL='{{userctl}}'
{%endif%}

View file

@ -0,0 +1,8 @@
{%- for route in routes -%}
{% if route.name %}# {{route.name}} {%- endif %}
{{ route.ipaddr }}
{%- if route.gateway %} {{route.gateway}}{% else %} -{% endif %}
{%- if route.netmask %} {{route.netmask}}{% else %} -{% endif %}
{%- if route.dev %} {{route.dev}}{% else %}{%- if iface and iface != "routes" %} {{iface}}{% else %} -{% endif %}{% endif %}
{%- if route.metric %} metric {{route.metric}} {%- endif %}
{% endfor -%}

View file

@ -0,0 +1,30 @@
{% if auto6_wait_at_boot %}AUTO6_WAIT_AT_BOOT="{{auto6_wait_at_boot}}"
{%endif%}{% if auto6_update %}AUTO6_UPDATE="{{auto6_update}}"
{%endif%}{% if link_required %}LINK_REQUIRED="{{link_required}}"
{%endif%}{% if wicked_debug %}WICKED_DEBUG="{{wicked_debug}}"
{%endif%}{% if wicked_log_level %}WICKED_LOG_LEVEL="{{wicked_log_level}}"
{%endif%}{% if check_duplicate_ip %}CHECK_DUPLICATE_IP="{{check_duplicate_ip}}"
{%endif%}{% if send_gratuitous_arp %}SEND_GRATUITOUS_ARP="{{send_gratuitous_arp}}"
{%endif%}{% if debug %}DEBUG="{{debug}}"
{%endif%}{% if wait_for_interfaces %}WAIT_FOR_INTERFACES="{{wait_for_interfaces}}"
{%endif%}{% if firewall %}FIREWALL="{{firewall}}"
{%endif%}{% if nm_online_timeout %}NM_ONLINE_TIMEOUT="{{nm_online_timeout}}"
{%endif%}{% if netconfig_modules_order %}NETCONFIG_MODULES_ORDER="{{netconfig_modules_order}}"
{%endif%}{% if netconfig_verbose %}NETCONFIG_VERBOSE="{{netconfig_verbose}}"
{%endif%}{% if netconfig_force_replace %}NETCONFIG_FORCE_REPLACE="{{netconfig_force_replace}}"
{%endif%}{% if dns_policy %}NETCONFIG_DNS_POLICY="{{dns_policy}}"
{%endif%}{% if dns_forwarder %}NETCONFIG_DNS_FORWARDER="{{dns_forwarder}}"
{%endif%}{% if dns_forwarder_fallback %}NETCONFIG_DNS_FORWARDER_FALLBACK="{{dns_forwarder_fallback}}"
{%endif%}{% if dns_search %}NETCONFIG_DNS_STATIC_SEARCHLIST="{{ dns_search|join(' ') }}"
{%endif%}{% if dns %}NETCONFIG_DNS_STATIC_SERVERS="{{ dns|join(' ') }}"
{%endif%}{% if dns_ranking %}NETCONFIG_DNS_RANKING="{{dns_ranking}}"
{%endif%}{% if dns_resolver_options %}NETCONFIG_DNS_RESOLVER_OPTIONS="{{dns_resolver_options}}"
{%endif%}{% if dns_resolver_sortlist %}NETCONFIG_DNS_RESOLVER_SORTLIST="{{dns_resolver_sortlist}}"
{%endif%}{% if ntp_policy %}NETCONFIG_NTP_POLICY="{{ntp_policy}}"
{%endif%}{% if ntp_static_servers %}NETCONFIG_NTP_STATIC_SERVERS="{{ntp_static_servers}}"
{%endif%}{% if nis_policy %}NETCONFIG_NIS_POLICY="{{nis_policy}}"
{%endif%}{% if nis_setdomainname %}NETCONFIG_NIS_SETDOMAINNAME="{{nis_setdomainname}}"
{%endif%}{% if nis_static_domain %}NETCONFIG_NIS_STATIC_DOMAIN="{{nis_static_domain}}"
{%endif%}{% if nis_static_servers %}NETCONFIG_NIS_STATIC_SERVERS="{{nis_static_servers}}"
{%endif%}{% if wireless_regulatory_domain %}WIRELESS_REGULATORY_DOMAIN="{{wireless_regulatory_domain}}"
{%endif%}

View file

@ -1115,6 +1115,7 @@ class SaltDistribution(distutils.dist.Distribution):
package_data = {
"salt.templates": [
"rh_ip/*.jinja",
"suse_ip/*.jinja",
"debian_ip/*.jinja",
"virt/*.jinja",
"git/*",

View file

@ -0,0 +1,706 @@
"""
:codeauthor: Jayesh Kariya <jayeshk@saltstack.com>
"""
import pytest
import copy
import os
import jinja2.exceptions
import salt.modules.suse_ip as suse_ip
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.mock import MagicMock, patch
from tests.support.unit import TestCase
"""
Test cases for salt.modules.suse_ip
"""
@pytest.fixture
def configure_loader_modules():
return {suse_ip: {"__grains__": {"os_family": "Suse"}}}
def test_error_message_iface_should_process_non_str_expected():
values = [1, True, False, "no-kaboom"]
iface = "ethtest"
option = "test"
msg = suse_ip._error_msg_iface(iface, option, values)
assert msg
assert msg.endswith("[1|True|False|no-kaboom]")
def test_error_message_network_should_process_non_str_expected():
values = [1, True, False, "no-kaboom"]
msg = suse_ip._error_msg_network("fnord", values)
assert msg
assert msg.endswith("[1|True|False|no-kaboom]")
def test_build_interface():
"""
Test to build an interface script for a network interface.
"""
with patch.object(suse_ip, "_raise_error_iface", return_value=None):
with pytest.raises(AttributeError):
suse_ip.build_interface("iface", "slave", True)
with patch.dict(
suse_ip.__salt__, {"network.interfaces": lambda: {"eth": True}}
):
with pytest.raises(AttributeError):
suse_ip.build_interface(
"iface",
"eth",
True,
netmask="255.255.255.255",
prefix=32,
test=True,
)
suse_ip.build_interface(
"iface",
"eth",
True,
ipaddrs=["A"],
test=True,
)
suse_ip.build_interface(
"iface",
"eth",
True,
ipv6addrs=["A"],
test=True,
)
with patch.object(suse_ip, "_raise_error_iface", return_value=None):
with patch.object(suse_ip, "_parse_settings_bond", MagicMock()):
mock = jinja2.exceptions.TemplateNotFound("foo")
with patch.object(
jinja2.Environment,
"get_template",
MagicMock(side_effect=mock),
):
assert suse_ip.build_interface("iface", "vlan", True) == ""
with patch.object(suse_ip, "_read_temp", return_value="A"):
with patch.object(
jinja2.Environment, "get_template", MagicMock()
):
assert suse_ip.build_interface("iface", "vlan", True, test="A") == "A"
with patch.object(
suse_ip, "_write_file_iface", return_value=None
):
with patch.object(
os.path, "join", return_value="A"
):
with patch.object(
suse_ip, "_read_file", return_value="A"
):
assert suse_ip.build_interface("iface", "vlan", True) == "A"
with patch.dict(
suse_ip.__salt__,
{
"network.interfaces": lambda: {
"eth": True
}
},
):
assert suse_ip.build_interface(
"iface",
"eth",
True,
ipaddrs=["127.0.0.1/8"],
) == "A"
assert suse_ip.build_interface(
"iface",
"eth",
True,
ipv6addrs=["fc00::1/128"],
) == "A"
def test_build_routes():
"""
Test to build a route script for a network interface.
"""
with patch.object(suse_ip, "_parse_routes", MagicMock()):
mock = jinja2.exceptions.TemplateNotFound("foo")
with patch.object(
jinja2.Environment, "get_template", MagicMock(side_effect=mock)
):
assert suse_ip.build_routes("iface") == ""
with patch.object(jinja2.Environment, "get_template", MagicMock()):
with patch.object(suse_ip, "_read_temp", return_value=["A"]):
assert suse_ip.build_routes("i", test="t") == ["A"]
with patch.object(suse_ip, "_read_file", return_value=["A"]):
with patch.object(os.path, "join", return_value="A"):
with patch.object(
suse_ip, "_write_file_network", return_value=None
):
assert suse_ip.build_routes("i", test=None) == ["A"]
def test_down():
"""
Test to shutdown a network interface
"""
with patch.dict(suse_ip.__salt__, {"cmd.run": MagicMock(return_value="A")}):
assert suse_ip.down("iface", "iface_type") == "A"
assert suse_ip.down("iface", "slave") is None
def test_get_interface():
"""
Test to return the contents of an interface script
"""
with patch.object(os.path, "join", return_value="A"):
with patch.object(suse_ip, "_read_file", return_value="A"):
assert suse_ip.get_interface("iface") == "A"
def test__parse_settings_eth_hwaddr_and_macaddr():
"""
Test that an AttributeError is thrown when hwaddr and macaddr are
passed together. They cannot be used together
"""
opts = {"hwaddr": 1, "macaddr": 2}
with pytest.raises(AttributeError):
suse_ip._parse_settings_eth(
opts=opts,
iface_type="eth",
enabled=True,
iface="eth0"
)
def test__parse_settings_eth_hwaddr():
"""
Make sure hwaddr gets added when parsing opts
"""
opts = {"hwaddr": "AA:BB:CC:11:22:33"}
with patch.dict(suse_ip.__salt__, {"network.interfaces": MagicMock()}):
results = suse_ip._parse_settings_eth(
opts=opts, iface_type="eth", enabled=True, iface="eth0"
)
assert "hwaddr" in results
assert results["hwaddr"] == opts["hwaddr"]
def test__parse_settings_eth_macaddr():
"""
Make sure macaddr gets added when parsing opts
"""
opts = {"macaddr": "AA:BB:CC:11:22:33"}
with patch.dict(suse_ip.__salt__, {"network.interfaces": MagicMock()}):
results = suse_ip._parse_settings_eth(
opts=opts, iface_type="eth", enabled=True, iface="eth0"
)
assert "macaddr" in results
assert results["macaddr"] == opts["macaddr"]
def test__parse_settings_eth_ethtool_channels():
"""
Make sure channels gets added when parsing opts
"""
opts = {"channels": {"rx": 4, "tx": 4, "combined": 4, "other": 4}}
with patch.dict(suse_ip.__grains__, {"num_cpus": 4}), patch.dict(
suse_ip.__salt__, {"network.interfaces": MagicMock()}
):
results = suse_ip._parse_settings_eth(
opts=opts, iface_type="eth", enabled=True, iface="eth0"
)
assert "ethtool" in results
assert results["ethtool"] == "-L eth0 rx 4 tx 4 other 4 combined 4"
def test_up():
"""
Test to start up a network interface
"""
with patch.dict(suse_ip.__salt__, {"cmd.run": MagicMock(return_value="A")}):
assert suse_ip.up("iface", "iface_type") == "A"
assert suse_ip.up("iface", "slave") is None
def test_get_routes():
"""
Test to return the contents of the interface routes script.
"""
with patch.object(os.path, "join", return_value="A"):
with patch.object(suse_ip, "_read_file", return_value=["A"]):
assert suse_ip.get_routes("iface") == ["A"]
def test_get_network_settings():
"""
Test to return the contents of the global network script.
"""
with patch.object(suse_ip, "_read_file", return_value="A"):
assert suse_ip.get_network_settings() == "A"
def test_apply_network_settings():
"""
Test to apply global network configuration.
"""
with patch.dict(
suse_ip.__salt__, {"service.reload": MagicMock(return_value=True)}
):
assert suse_ip.apply_network_settings()
def test_build_network_settings():
"""
Test to build the global network script.
"""
with patch.object(suse_ip, "_parse_suse_config", MagicMock()):
with patch.object(suse_ip, "_parse_network_settings", MagicMock()):
mock = jinja2.exceptions.TemplateNotFound("foo")
with patch.object(
jinja2.Environment, "get_template", MagicMock(side_effect=mock)
):
assert suse_ip.build_network_settings() == ""
with patch.object(jinja2.Environment, "get_template", MagicMock()):
with patch.object(suse_ip, "_read_temp", return_value="A"):
assert suse_ip.build_network_settings(test="t") == "A"
with patch.object(
suse_ip, "_write_file_network", return_value=None
):
with patch.object(suse_ip, "_read_file", return_value="A"):
cmd_run = MagicMock()
with patch.dict(suse_ip.__salt__, {"cmd.run": cmd_run}):
assert suse_ip.build_network_settings(test=None) == "A"
cmd_run.assert_called_once_with("netconfig update -f")
def _check_common_opts_bond(lines):
"""
Reduce code duplication by making sure that the expected options are
present in the config file. Note that this assumes that duplex="full"
was passed in the kwargs. If it wasn't, then there would be no
ETHTOOL_OPTS line.
"""
assert "STARTMODE='auto'" in lines
assert "BONDING_MASTER='yes'" in lines
assert "BONDING_SLAVE1='eth1'" in lines
assert "BONDING_SLAVE2='eth2'" in lines
assert "ETHTOOL_OPTIONS='duplex full'" in lines
def _validate_miimon_downdelay(kwargs):
"""
Validate that downdelay that is not a multiple of miimon raises an error
"""
# Make copy of kwargs so we don't modify what was passed in
kwargs = copy.copy(kwargs)
# Remove miimon and downdelay (if present) to test invalid input
for key in ("miimon", "downdelay"):
kwargs.pop(key, None)
kwargs["miimon"] = 100
kwargs["downdelay"] = 201
try:
suse_ip.build_interface(
"bond0",
"bond",
enabled=True,
**kwargs,
)
except AttributeError as exc:
assert "multiple of miimon" in str(exc)
else:
raise Exception("AttributeError was not raised")
def _validate_miimon_conf(kwargs, required=True):
"""
Validate miimon configuration
"""
# Make copy of kwargs so we don't modify what was passed in
kwargs = copy.copy(kwargs)
# Remove miimon and downdelay (if present) to test invalid input
for key in ("miimon", "downdelay"):
kwargs.pop(key, None)
if required:
# Leaving out miimon should raise an error
try:
suse_ip.build_interface(
"bond0",
"bond",
enabled=True,
**kwargs,
)
except AttributeError as exc:
assert "miimon" in str(exc)
else:
raise Exception("AttributeError was not raised")
_validate_miimon_downdelay(kwargs)
def _get_bonding_opts(kwargs):
results = suse_ip.build_interface(
"bond0",
"bond",
enabled=True,
**kwargs,
)
_check_common_opts_bond(results)
for line in results:
if line.startswith("BONDING_MODULE_OPTS="):
return sorted(line.split("=", 1)[-1].strip("'").split())
raise Exception("BONDING_MODULE_OPTS not found")
def _test_mode_0_or_2(mode_num=0):
"""
Modes 0 and 2 share the majority of code, with mode 2 being a superset
of mode 0. This function will do the proper asserts for the common code
in these two modes.
"""
kwargs = {
"test": True,
"duplex": "full",
"slaves": "eth1 eth2",
}
if mode_num == 0:
modes = ("balance-rr", mode_num, str(mode_num))
else:
modes = ("balance-xor", mode_num, str(mode_num))
for mode in modes:
kwargs["mode"] = mode
# Remove all miimon/arp settings to test invalid config
for key in (
"miimon",
"downdelay",
"arp_interval",
"arp_ip_targets",
):
kwargs.pop(key, None)
# Check that invalid downdelay is handled correctly
_validate_miimon_downdelay(kwargs)
# Leaving out miimon and arp_interval should raise an error
try:
bonding_opts = _get_bonding_opts(kwargs)
except AttributeError as exc:
assert "miimon or arp_interval" in str(exc)
else:
raise Exception("AttributeError was not raised")
kwargs["miimon"] = 100
kwargs["downdelay"] = 200
bonding_opts = _get_bonding_opts(kwargs)
expected = [
"downdelay=200",
"miimon=100",
"mode={}".format(mode_num),
"use_carrier=0",
]
assert bonding_opts == expected, bonding_opts
# Add arp settings, and test again
kwargs["arp_interval"] = 300
kwargs["arp_ip_target"] = ["1.2.3.4", "5.6.7.8"]
bonding_opts = _get_bonding_opts(kwargs)
expected = [
"arp_interval=300",
"arp_ip_target=1.2.3.4,5.6.7.8",
"downdelay=200",
"miimon=100",
"mode={}".format(mode_num),
"use_carrier=0",
]
assert bonding_opts == expected, bonding_opts
# Remove miimon and downdelay and test again
del kwargs["miimon"]
del kwargs["downdelay"]
bonding_opts = _get_bonding_opts(kwargs)
expected = [
"arp_interval=300",
"arp_ip_target=1.2.3.4,5.6.7.8",
"mode={}".format(mode_num),
]
assert bonding_opts == expected, bonding_opts
def test_build_interface_bond_mode_0():
"""
Test that mode 0 bond interfaces are properly built
"""
_test_mode_0_or_2(0)
def test_build_interface_bond_mode_1():
"""
Test that mode 1 bond interfaces are properly built
"""
kwargs = {
"test": True,
"mode": "active-backup",
"duplex": "full",
"slaves": "eth1 eth2",
"miimon": 100,
"downdelay": 200,
}
for mode in ("active-backup", 1, "1"):
kwargs.pop("primary", None)
kwargs["mode"] = mode
_validate_miimon_conf(kwargs)
bonding_opts = _get_bonding_opts(kwargs)
expected = [
"downdelay=200",
"miimon=100",
"mode=1",
"use_carrier=0",
]
assert bonding_opts == expected, bonding_opts
# Add a "primary" option and confirm that it shows up in
# the bonding opts.
kwargs["primary"] = "foo"
bonding_opts = _get_bonding_opts(kwargs)
expected = [
"downdelay=200",
"miimon=100",
"mode=1",
"primary=foo",
"use_carrier=0",
]
assert bonding_opts == expected, bonding_opts
def test_build_interface_bond_mode_2():
"""
Test that mode 2 bond interfaces are properly built
"""
_test_mode_0_or_2(2)
kwargs = {
"test": True,
"duplex": "full",
"slaves": "eth1 eth2",
"miimon": 100,
"downdelay": 200,
}
for mode in ("balance-xor", 2, "2"):
# Using an invalid hashing algorithm should cause an error
# to be raised.
kwargs["mode"] = mode
kwargs["hashing-algorithm"] = "layer42"
try:
bonding_opts = _get_bonding_opts(kwargs)
except AttributeError as exc:
assert "hashing-algorithm" in str(exc)
else:
raise Exception("AttributeError was not raised")
# Correct the hashing algorithm and re-run
kwargs["hashing-algorithm"] = "layer2"
bonding_opts = _get_bonding_opts(kwargs)
expected = [
"downdelay=200",
"miimon=100",
"mode=2",
"use_carrier=0",
"xmit_hash_policy=layer2",
]
assert bonding_opts == expected, bonding_opts
def test_build_interface_bond_mode_3():
"""
Test that mode 3 bond interfaces are properly built
"""
kwargs = {
"test": True,
"duplex": "full",
"slaves": "eth1 eth2",
"miimon": 100,
"downdelay": 200,
}
for mode in ("broadcast", 3, "3"):
kwargs["mode"] = mode
_validate_miimon_conf(kwargs)
bonding_opts = _get_bonding_opts(kwargs)
expected = [
"downdelay=200",
"miimon=100",
"mode=3",
"use_carrier=0",
]
assert bonding_opts == expected, bonding_opts
def test_build_interface_bond_mode_4():
"""
Test that mode 4 bond interfaces are properly built
"""
kwargs = {
"test": True,
"duplex": "full",
"slaves": "eth1 eth2",
"miimon": 100,
"downdelay": 200,
}
valid_lacp_rate = ("fast", "slow", "1", "0")
valid_ad_select = ("0",)
for mode in ("802.3ad", 4, "4"):
kwargs["mode"] = mode
_validate_miimon_conf(kwargs)
for lacp_rate in valid_lacp_rate + ("2", "speedy"):
for ad_select in valid_ad_select + ("foo",):
kwargs["lacp_rate"] = lacp_rate
kwargs["ad_select"] = ad_select
try:
bonding_opts = _get_bonding_opts(kwargs)
except AttributeError as exc:
error = str(exc)
# Re-raise the exception only if it was
# unexpected. It should not be expected when
# the lacp_rate or ad_select is valid.
if "lacp_rate" in error:
if lacp_rate in valid_lacp_rate:
raise
elif "ad_select" in error:
if ad_select in valid_ad_select:
raise
else:
raise
else:
expected = [
"ad_select={}".format(ad_select),
"downdelay=200",
"lacp_rate={}".format(
"1"
if lacp_rate == "fast"
else "0"
if lacp_rate == "slow"
else lacp_rate
),
"miimon=100",
"mode=4",
"use_carrier=0",
]
assert bonding_opts == expected, bonding_opts
def test_build_interface_bond_mode_5():
"""
Test that mode 5 bond interfaces are properly built
"""
kwargs = {
"test": True,
"duplex": "full",
"slaves": "eth1 eth2",
"miimon": 100,
"downdelay": 200,
}
for mode in ("balance-tlb", 5, "5"):
kwargs.pop("primary", None)
kwargs["mode"] = mode
_validate_miimon_conf(kwargs)
bonding_opts = _get_bonding_opts(kwargs)
expected = [
"downdelay=200",
"miimon=100",
"mode=5",
"use_carrier=0",
]
assert bonding_opts == expected, bonding_opts
# Add a "primary" option and confirm that it shows up in
# the bonding opts.
kwargs["primary"] = "foo"
bonding_opts = _get_bonding_opts(kwargs)
expected = [
"downdelay=200",
"miimon=100",
"mode=5",
"primary=foo",
"use_carrier=0",
]
assert bonding_opts == expected, bonding_opts
def test_build_interface_bond_mode_6():
"""
Test that mode 6 bond interfaces are properly built
"""
kwargs = {
"test": True,
"duplex": "full",
"slaves": ["eth1", "eth2"],
"miimon": 100,
"downdelay": 200,
}
for mode in ("balance-alb", 6, "6"):
kwargs.pop("primary", None)
kwargs["mode"] = mode
_validate_miimon_conf(kwargs)
bonding_opts = _get_bonding_opts(kwargs)
expected = [
"downdelay=200",
"miimon=100",
"mode=6",
"use_carrier=0",
]
assert bonding_opts == expected, bonding_opts
# Add a "primary" option and confirm that it shows up in
# the bonding opts.
kwargs["primary"] = "foo"
bonding_opts = _get_bonding_opts(kwargs)
expected = [
"downdelay=200",
"miimon=100",
"mode=6",
"primary=foo",
"use_carrier=0",
]
assert bonding_opts == expected, bonding_opts
def test_build_interface_bond_slave():
"""
Test that bond slave interfaces are properly built
"""
results = sorted(
suse_ip.build_interface(
"eth1",
"slave",
enabled=True,
test=True,
master="bond0",
)
)
expected = [
"BOOTPROTO='none'",
"STARTMODE='auto'",
]
assert results == expected, results