mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
suse_ip module implementation
This commit is contained in:
parent
190ab7cb2f
commit
83c5e0f05a
8 changed files with 1966 additions and 14 deletions
|
@ -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
1166
salt/modules/suse_ip.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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"
|
||||
|
|
34
salt/templates/suse_ip/ifcfg.jinja
Normal file
34
salt/templates/suse_ip/ifcfg.jinja
Normal 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%}
|
8
salt/templates/suse_ip/ifroute.jinja
Normal file
8
salt/templates/suse_ip/ifroute.jinja
Normal 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 -%}
|
30
salt/templates/suse_ip/network.jinja
Normal file
30
salt/templates/suse_ip/network.jinja
Normal 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%}
|
1
setup.py
1
setup.py
|
@ -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/*",
|
||||
|
|
706
tests/pytests/unit/modules/test_suse_ip.py
Normal file
706
tests/pytests/unit/modules/test_suse_ip.py
Normal 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
|
Loading…
Add table
Reference in a new issue