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.")
|
return (False, "Module linux_ip: Windows systems are not supported.")
|
||||||
if __grains__["os_family"] == "RedHat":
|
if __grains__["os_family"] == "RedHat":
|
||||||
return (False, "Module linux_ip: RedHat systems are not supported.")
|
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":
|
if __grains__["os_family"] == "Debian":
|
||||||
return (False, "Module linux_ip: Debian systems are not supported.")
|
return (False, "Module linux_ip: Debian systems are not supported.")
|
||||||
if __grains__["os_family"] == "NILinuxRT":
|
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"](
|
present_slaves = __salt__["cmd.run"](
|
||||||
["cat", "/sys/class/net/{}/bonding/slaves".format(name)]
|
["cat", "/sys/class/net/{}/bonding/slaves".format(name)]
|
||||||
).split()
|
).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)
|
missing_slaves = set(desired_slaves) - set(present_slaves)
|
||||||
|
|
||||||
# Enslave only slaves missing in master
|
# Enslave only slaves missing in master
|
||||||
if missing_slaves:
|
if missing_slaves:
|
||||||
ifenslave_path = __salt__["cmd.run"](["which", "ifenslave"]).strip()
|
log.debug("Missing slaves of {}: {}".format(name, missing_slaves))
|
||||||
if ifenslave_path:
|
if __grains__["os_family"] != "Suse":
|
||||||
log.info(
|
ifenslave_path = __salt__["cmd.run"](["which", "ifenslave"]).strip()
|
||||||
"Adding slaves '%s' to the master %s",
|
if ifenslave_path:
|
||||||
" ".join(missing_slaves),
|
log.info(
|
||||||
name,
|
"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:
|
else:
|
||||||
log.info(
|
log.info(
|
||||||
"All slaves '%s' are already added to the master %s"
|
"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 = {
|
package_data = {
|
||||||
"salt.templates": [
|
"salt.templates": [
|
||||||
"rh_ip/*.jinja",
|
"rh_ip/*.jinja",
|
||||||
|
"suse_ip/*.jinja",
|
||||||
"debian_ip/*.jinja",
|
"debian_ip/*.jinja",
|
||||||
"virt/*.jinja",
|
"virt/*.jinja",
|
||||||
"git/*",
|
"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