From 70aa9875aa2e61be05872878667d05c324f7d63e Mon Sep 17 00:00:00 2001 From: Till Maas Date: Thu, 6 Jun 2019 21:48:05 +0200 Subject: [PATCH] Add support for ethtool features --- README.md | 62 ++++++++++ examples/ethtool-features-default.yml | 14 +++ examples/ethtool-features.yml | 19 +++ library/network_connections.py | 79 ++++++++++++- .../network_lsr/argument_validator.py | 87 ++++++++++++++ module_utils/network_lsr/nm_provider.py | 23 ++++ tests/ensure_non_running_provider.py | 2 + tests/playbooks/tests_ethtool_features.yml | 111 ++++++++++++++++++ tests/tests_ethtool_features_initscripts.yml | 13 ++ tests/tests_ethtool_features_nm.yml | 28 +++++ tests/unit/test_network_connections.py | 82 +++++++++++++ tests/unit/test_nm_provider.py | 31 +++++ 12 files changed, 547 insertions(+), 4 deletions(-) create mode 100644 examples/ethtool-features-default.yml create mode 100644 examples/ethtool-features.yml create mode 100644 module_utils/network_lsr/nm_provider.py create mode 100644 tests/playbooks/tests_ethtool_features.yml create mode 100644 tests/tests_ethtool_features_initscripts.yml create mode 100644 tests/tests_ethtool_features_nm.yml create mode 100644 tests/unit/test_nm_provider.py diff --git a/README.md b/README.md index 32d8b37..2830b4e 100644 --- a/README.md +++ b/README.md @@ -339,6 +339,68 @@ The IP configuration supports the following options: **Note:** Slaves to the bridge, bond or team devices cannot specify `ip` settings. +### `ethtool` + +The ethtool settings allow to enable or disable varios features. The names +correspond to the names used by the `ethtool` utility. Depending on the actual +kernel and device, changing some features might not be supported. + +```yaml + ethtool: + features: + esp-hw-offload: yes|no # optional + esp-tx-csum-hw-offload: yes|no # optional + fcoe-mtu: yes|no # optional + gro: yes|no # optional + gso: yes|no # optional + highdma: yes|no # optional + hw-tc-offload: yes|no # optional + l2-fwd-offload: yes|no # optional + loopback: yes|no # optional + lro: yes|no # optional + ntuple: yes|no # optional + rx: yes|no # optional + rx-all: yes|no # optional + rx-fcs: yes|no # optional + rx-gro-hw: yes|no # optional + rx-udp_tunnel-port-offload: yes|no # optional + rx-vlan-filter: yes|no # optional + rx-vlan-stag-filter: yes|no # optional + rx-vlan-stag-hw-parse: yes|no # optional + rxhash: yes|no # optional + rxvlan: yes|no # optional + sg: yes|no # optional + tls-hw-record: yes|no # optional + tls-hw-tx-offload: yes|no # optional + tso: yes|no # optional + tx: yes|no # optional + tx-checksum-fcoe-crc: yes|no # optional + tx-checksum-ip-generic: yes|no # optional + tx-checksum-ipv4: yes|no # optional + tx-checksum-ipv6: yes|no # optional + tx-checksum-sctp: yes|no # optional + tx-esp-segmentation: yes|no # optional + tx-fcoe-segmentation: yes|no # optional + tx-gre-csum-segmentation: yes|no # optional + tx-gre-segmentation: yes|no # optional + tx-gso-partial: yes|no # optional + tx-gso-robust: yes|no # optional + tx-ipxip4-segmentation: yes|no # optional + tx-ipxip6-segmentation: yes|no # optional + tx-nocache-copy: yes|no # optional + tx-scatter-gather: yes|no # optional + tx-scatter-gather-fraglist: yes|no # optional + tx-sctp-segmentation: yes|no # optional + tx-tcp-ecn-segmentation: yes|no # optional + tx-tcp-mangleid-segmentation: yes|no # optional + tx-tcp-segmentation: yes|no # optional + tx-tcp6-segmentation: yes|no # optional + tx-udp-segmentation: yes|no # optional + tx-udp_tnl-csum-segmentation: yes|no # optional + tx-udp_tnl-segmentation: yes|no # optional + tx-vlan-stag-hw-insert: yes|no # optional + txvlan: yes|no # optional +``` Examples of Options ------------------- diff --git a/examples/ethtool-features-default.yml b/examples/ethtool-features-default.yml new file mode 100644 index 0000000..78965e6 --- /dev/null +++ b/examples/ethtool-features-default.yml @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- hosts: all + tasks: + - include_role: + name: linux-system-roles.network + vars: + network_connections: + - name: "{{ network_interface_name1 }}" + state: up + type: ethernet + ip: + dhcp4: "no" + auto6: "no" diff --git a/examples/ethtool-features.yml b/examples/ethtool-features.yml new file mode 100644 index 0000000..d8842c2 --- /dev/null +++ b/examples/ethtool-features.yml @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- hosts: all + tasks: + - include_role: + name: linux-system-roles.network + vars: + network_connections: + - name: "{{ network_interface_name1 }}" + state: up + type: ethernet + ip: + dhcp4: "no" + auto6: "no" + ethtool: + features: + gro: "no" + gso: "yes" + tx-sctp-segmentation: "no" diff --git a/library/network_connections.py b/library/network_connections.py index 25a7882..39e81e8 100644 --- a/library/network_connections.py +++ b/library/network_connections.py @@ -20,6 +20,7 @@ from ansible.module_utils.network_lsr.argument_validator import ( # pylint: disable=import-error from ansible.module_utils.network_lsr.utils import Util +from ansible.module_utils.network_lsr import nm_provider DOCUMENTATION = """ --- @@ -361,16 +362,39 @@ class IfcfgUtil: if connection["mtu"]: ifcfg["MTU"] = str(connection["mtu"]) + ethtool_options = "" if "ethernet" in connection: if connection["ethernet"]["autoneg"] is not None: if connection["ethernet"]["autoneg"]: - s = "autoneg on" + ethtool_options = "autoneg on" else: - s = "autoneg off speed %s duplex %s" % ( + ethtool_options = "autoneg off speed %s duplex %s" % ( connection["ethernet"]["speed"], connection["ethernet"]["duplex"], ) - ifcfg["ETHTOOL_OPTS"] = s + + ethtool_features = connection["ethtool"]["features"] + configured_features = [] + for feature, setting in ethtool_features.items(): + value = "" + if setting: + value = "on" + elif setting is not None: + value = "off" + + if value: + configured_features.append("%s %s" % (feature, value)) + + if configured_features: + if ethtool_options: + ethtool_options += " ; " + ethtool_options += "-K %s %s" % ( + connection["interface_name"], + " ".join(configured_features), + ) + + if ethtool_options: + ifcfg["ETHTOOL_OPTS"] = ethtool_options if connection["master"] is not None: m = ArgUtil.connection_find_master(connection["master"], connections, idx) @@ -839,6 +863,20 @@ class NMUtil: NM.SETTING_WIRED_SPEED, connection["ethernet"]["speed"] ) + if hasattr(NM, "SettingEthtool"): + s_ethtool = self.connection_ensure_setting(con, NM.SettingEthtool) + + for feature, setting in connection["ethtool"]["features"].items(): + nm_feature = nm_provider.get_nm_ethtool_feature(feature) + + if setting is None: + if nm_feature: + s_ethtool.set_feature(nm_feature, NM.Ternary.DEFAULT) + elif setting: + s_ethtool.set_feature(nm_feature, NM.Ternary.TRUE) + else: + s_ethtool.set_feature(nm_feature, NM.Ternary.FALSE) + if connection["mtu"]: if connection["type"] == "infiniband": s_infiniband = self.connection_ensure_setting(con, NM.SettingInfiniband) @@ -1830,7 +1868,9 @@ class Cmd_nm(Cmd): Cmd.run_prepare(self) names = {} - for connection in self.connections: + for idx, connection in enumerate(self.connections): + self._check_ethtool_setting_support(idx, connection) + name = connection["name"] if not name: assert connection["persistent_state"] == "absent" @@ -1872,6 +1912,37 @@ class Cmd_nm(Cmd): finally: self._checkpoint = None + def _check_ethtool_setting_support(self, idx, connection): + """ Check if SettingEthtool support is needed and available + + If any feature is specified, the SettingEthtool setting needs to be + available. Also NM needs to know about each specified setting. Do not + check if NM knows about any defaults. + + """ + NM = Util.NM() + + # If the profile is not completely specified, for example if only the + # runtime change is specified, the ethtool subtree might be missing. + # Then no checks are required. + if "ethtool" not in connection: + return + + ethtool_features = connection["ethtool"]["features"] + specified_features = dict( + [(k, v) for k, v in ethtool_features.items() if v is not None] + ) + + if specified_features and not hasattr(NM, "SettingEthtool"): + self.log_fatal(idx, "ethtool.features specified but not supported by NM") + + for feature, setting in specified_features.items(): + nm_feature = nm_provider.get_nm_ethtool_feature(feature) + if not nm_feature: + self.log_fatal( + idx, "ethtool feature %s specified but not support by NM" % feature + ) + def run_action_absent(self, idx): seen = set() name = self.connections[idx]["name"] diff --git a/module_utils/network_lsr/argument_validator.py b/module_utils/network_lsr/argument_validator.py index 85ebcb5..b58ec67 100644 --- a/module_utils/network_lsr/argument_validator.py +++ b/module_utils/network_lsr/argument_validator.py @@ -521,6 +521,92 @@ class ArgValidator_DictEthernet(ArgValidatorDict): return result +class ArgValidator_DictEthtool(ArgValidatorDict): + def __init__(self): + ArgValidatorDict.__init__( + self, + name="ethtool", + nested=[ArgValidator_DictEthtoolFeatures()], + default_value=ArgValidator.MISSING, + ) + + self.default_value = dict( + [(k, v.default_value) for k, v in self.nested.items()] + ) + + +class ArgValidator_DictEthtoolFeatures(ArgValidatorDict): + # List of features created with: + # nmcli connection modify "virbr0" ethtool.feature- on |& \ + # sed -e 's_[,:]_\n_g' | \ # split output in newlines + # grep ^\ f | \ # select only lines starting with " f" + # tr -d " ." | \ # remove spaces and fullstops + # sed -e 's,feature-,ArgValidatorBool(",' \ # add Python code + # -e 's/$/", default_value=None)],/' + def __init__(self): + ArgValidatorDict.__init__( + self, + name="features", + nested=[ + ArgValidatorBool("esp-hw-offload", default_value=None), + ArgValidatorBool("esp-tx-csum-hw-offload", default_value=None), + ArgValidatorBool("fcoe-mtu", default_value=None), + ArgValidatorBool("gro", default_value=None), + ArgValidatorBool("gso", default_value=None), + ArgValidatorBool("highdma", default_value=None), + ArgValidatorBool("hw-tc-offload", default_value=None), + ArgValidatorBool("l2-fwd-offload", default_value=None), + ArgValidatorBool("loopback", default_value=None), + ArgValidatorBool("lro", default_value=None), + ArgValidatorBool("ntuple", default_value=None), + ArgValidatorBool("rx", default_value=None), + ArgValidatorBool("rxhash", default_value=None), + ArgValidatorBool("rxvlan", default_value=None), + ArgValidatorBool("rx-all", default_value=None), + ArgValidatorBool("rx-fcs", default_value=None), + ArgValidatorBool("rx-gro-hw", default_value=None), + ArgValidatorBool("rx-udp_tunnel-port-offload", default_value=None), + ArgValidatorBool("rx-vlan-filter", default_value=None), + ArgValidatorBool("rx-vlan-stag-filter", default_value=None), + ArgValidatorBool("rx-vlan-stag-hw-parse", default_value=None), + ArgValidatorBool("sg", default_value=None), + ArgValidatorBool("tls-hw-record", default_value=None), + ArgValidatorBool("tls-hw-tx-offload", default_value=None), + ArgValidatorBool("tso", default_value=None), + ArgValidatorBool("tx", default_value=None), + ArgValidatorBool("txvlan", default_value=None), + ArgValidatorBool("tx-checksum-fcoe-crc", default_value=None), + ArgValidatorBool("tx-checksum-ipv4", default_value=None), + ArgValidatorBool("tx-checksum-ipv6", default_value=None), + ArgValidatorBool("tx-checksum-ip-generic", default_value=None), + ArgValidatorBool("tx-checksum-sctp", default_value=None), + ArgValidatorBool("tx-esp-segmentation", default_value=None), + ArgValidatorBool("tx-fcoe-segmentation", default_value=None), + ArgValidatorBool("tx-gre-csum-segmentation", default_value=None), + ArgValidatorBool("tx-gre-segmentation", default_value=None), + ArgValidatorBool("tx-gso-partial", default_value=None), + ArgValidatorBool("tx-gso-robust", default_value=None), + ArgValidatorBool("tx-ipxip4-segmentation", default_value=None), + ArgValidatorBool("tx-ipxip6-segmentation", default_value=None), + ArgValidatorBool("tx-nocache-copy", default_value=None), + ArgValidatorBool("tx-scatter-gather", default_value=None), + ArgValidatorBool("tx-scatter-gather-fraglist", default_value=None), + ArgValidatorBool("tx-sctp-segmentation", default_value=None), + ArgValidatorBool("tx-tcp6-segmentation", default_value=None), + ArgValidatorBool("tx-tcp-ecn-segmentation", default_value=None), + ArgValidatorBool("tx-tcp-mangleid-segmentation", default_value=None), + ArgValidatorBool("tx-tcp-segmentation", default_value=None), + ArgValidatorBool("tx-udp-segmentation", default_value=None), + ArgValidatorBool("tx-udp_tnl-csum-segmentation", default_value=None), + ArgValidatorBool("tx-udp_tnl-segmentation", default_value=None), + ArgValidatorBool("tx-vlan-stag-hw-insert", default_value=None), + ], + ) + self.default_value = dict( + [(k, v.default_value) for k, v in self.nested.items()] + ) + + class ArgValidator_DictBond(ArgValidatorDict): VALID_MODES = [ @@ -668,6 +754,7 @@ class ArgValidator_DictConnection(ArgValidatorDict): ArgValidatorBool("ignore_errors", default_value=None), ArgValidator_DictIP(), ArgValidator_DictEthernet(), + ArgValidator_DictEthtool(), ArgValidator_DictBond(), ArgValidator_DictInfiniband(), ArgValidator_DictVlan(), diff --git a/module_utils/network_lsr/nm_provider.py b/module_utils/network_lsr/nm_provider.py new file mode 100644 index 0000000..9f9b028 --- /dev/null +++ b/module_utils/network_lsr/nm_provider.py @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: BSD-3-Clause +""" Support for NetworkManager aka the NM provider """ + +# pylint: disable=import-error, no-name-in-module +from ansible.module_utils.network_lsr.utils import Util + +ETHTOOL_FEATURE_PREFIX = "ETHTOOL_OPTNAME_FEATURE_" + + +def get_nm_ethtool_feature(name): + """ + Translate ethtool feature into Network Manager name + + :param name: Name of the feature + :type name: str + :returns: Name of the feature to be used by `NM.SettingEthtool.set_feature()` + :rtype: str + """ + + name = ETHTOOL_FEATURE_PREFIX + name.upper().replace("-", "_") + + feature = getattr(Util.NM(), name, None) + return feature diff --git a/tests/ensure_non_running_provider.py b/tests/ensure_non_running_provider.py index 37df6d9..9048c90 100755 --- a/tests/ensure_non_running_provider.py +++ b/tests/ensure_non_running_provider.py @@ -19,6 +19,8 @@ IGNORE = [ "tests_unit.yml", "tests_vlan_mtu_initscripts.yml", "tests_vlan_mtu_nm.yml", + "tests_ethtool_features_initscripts.yml", + "tests_ethtool_features_nm.yml", ] OTHER_PLAYBOOK = """ diff --git a/tests/playbooks/tests_ethtool_features.yml b/tests/playbooks/tests_ethtool_features.yml new file mode 100644 index 0000000..ba0c6c3 --- /dev/null +++ b/tests/playbooks/tests_ethtool_features.yml @@ -0,0 +1,111 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- hosts: all + vars: + interface: lsrfeat1 + type: veth + tasks: + - name: "INIT: Ethtool feeatures tests" + debug: + msg: "##################################################" + - include_tasks: tasks/show-interfaces.yml + - include_tasks: tasks/manage-test-interface.yml + vars: + state: present + - include_tasks: tasks/assert-device_present.yml + - name: Install ethtool (test dependency) + package: + name: ethtool + state: present + - block: + - name: "TEST: I can create a profile without changing the ethtool features." + debug: + msg: "##################################################" + - name: Get current device features + command: "ethtool --show-features {{ interface }}" + register: original_ethtool_features + - import_role: + name: linux-system-roles.network + vars: + network_connections: + - name: "{{ interface }}" + state: up + type: ethernet + ip: + dhcp4: "no" + auto6: "no" + - name: Get current device features + command: "ethtool --show-features {{ interface }}" + register: ethtool_features + - name: "ASSERT: The profile does not change the ethtool features" + assert: + that: + - original_ethtool_features.stdout == ethtool_features.stdout + - name: "TEST: I can disable gro and tx-tcp-segmentation and enable gso." + debug: + msg: "##################################################" + - import_role: + name: linux-system-roles.network + vars: + network_connections: + - name: "{{ interface }}" + state: up + type: ethernet + ip: + dhcp4: "no" + auto6: "no" + ethtool: + features: + gro: "no" + gso: "yes" + tx-tcp-segmentation: "no" + - name: Get current device features + command: "ethtool --show-features {{ interface }}" + register: ethtool_features + - name: + debug: + var: ethtool_features.stdout_lines + - name: Assert device features + assert: + that: + - "'generic-receive-offload: off' in ethtool_features.stdout_lines" + - "'generic-segmentation-offload: on' in ethtool_features.stdout_lines" + - "'tx-tcp-segmentation: off' in ethtool_features.stdout_lines | map('trim')" + - name: "TEST: I can reset features to their original value." + debug: + msg: "##################################################" + - import_role: + name: linux-system-roles.network + vars: + network_connections: + - name: "{{ interface }}" + state: up + type: ethernet + ip: + dhcp4: "no" + auto6: "no" + - name: Get current device features + command: "ethtool --show-features {{ interface }}" + register: ethtool_features + # Resetting the ethtools only works with NetworkManager + - name: "ASSERT: The profile does not change the ethtool features" + assert: + that: + - original_ethtool_features.stdout == ethtool_features.stdout + when: + network_provider == 'nm' + always: + - block: + - import_role: + name: linux-system-roles.network + vars: + network_connections: + - name: "{{ interface }}" + persistent_state: absent + state: down + ignore_errors: true + - include_tasks: tasks/manage-test-interface.yml + vars: + state: absent + tags: + - "tests::cleanup" diff --git a/tests/tests_ethtool_features_initscripts.yml b/tests/tests_ethtool_features_initscripts.yml new file mode 100644 index 0000000..6aea73b --- /dev/null +++ b/tests/tests_ethtool_features_initscripts.yml @@ -0,0 +1,13 @@ +--- +# set network provider and gather facts +- hosts: all + tasks: + - name: Set network provider to 'initscripts' + set_fact: + network_provider: initscripts + +# workaround for: https://github.com/ansible/ansible/issues/27973 +# There is no way in Ansible to abort a playbook hosts with specific OS +# releases Therefore we include the playbook with the tests only if the hosts +# would support it. +- import_playbook: playbooks/tests_ethtool_features.yml diff --git a/tests/tests_ethtool_features_nm.yml b/tests/tests_ethtool_features_nm.yml new file mode 100644 index 0000000..12e5042 --- /dev/null +++ b/tests/tests_ethtool_features_nm.yml @@ -0,0 +1,28 @@ +--- +# set network provider and gather facts +- hosts: all + tasks: + - name: Set network provider to 'nm' + set_fact: + network_provider: nm + - name: Install NetworkManager + package: + name: NetworkManager + state: present + - name: Get NetworkManager version + command: rpm -q --qf "%{version}" NetworkManager + args: + warn: "no" + when: true + register: NetworkManager_version + +# workaround for: https://github.com/ansible/ansible/issues/27973 +# There is no way in Ansible to abort a playbook hosts with specific OS +# releases Therefore we include the playbook with the tests only if the hosts +# would support it. +# The test should run with NetworkManager, therefore it cannot run on RHEL 6 or CentOS 6. +- import_playbook: playbooks/tests_ethtool_features.yml + when: + - ansible_distribution_major_version != '6' + # NetworkManager 1.20.0 introduced ethtool settings support + - NetworkManager_version.stdout is version('1.20.0', '>=') diff --git a/tests/unit/test_network_connections.py b/tests/unit/test_network_connections.py index d9918f6..9c2f0ed 100755 --- a/tests/unit/test_network_connections.py +++ b/tests/unit/test_network_connections.py @@ -67,6 +67,63 @@ def pprint(msg, obj): ARGS_CONNECTIONS = network_lsr.argument_validator.ArgValidator_ListConnections() VALIDATE_ONE_MODE_INITSCRIPTS = ARGS_CONNECTIONS.VALIDATE_ONE_MODE_INITSCRIPTS +ETHTOOL_FEATURES_DEFAULTS = { + "esp-hw-offload": None, + "esp-tx-csum-hw-offload": None, + "fcoe-mtu": None, + "gro": None, + "gso": None, + "highdma": None, + "hw-tc-offload": None, + "l2-fwd-offload": None, + "loopback": None, + "lro": None, + "ntuple": None, + "rx": None, + "rx-all": None, + "rx-fcs": None, + "rx-gro-hw": None, + "rx-udp_tunnel-port-offload": None, + "rx-vlan-filter": None, + "rx-vlan-stag-filter": None, + "rx-vlan-stag-hw-parse": None, + "rxhash": None, + "rxvlan": None, + "sg": None, + "tls-hw-record": None, + "tls-hw-tx-offload": None, + "tso": None, + "tx": None, + "tx-checksum-fcoe-crc": None, + "tx-checksum-ip-generic": None, + "tx-checksum-ipv4": None, + "tx-checksum-ipv6": None, + "tx-checksum-sctp": None, + "tx-esp-segmentation": None, + "tx-fcoe-segmentation": None, + "tx-gre-csum-segmentation": None, + "tx-gre-segmentation": None, + "tx-gso-partial": None, + "tx-gso-robust": None, + "tx-ipxip4-segmentation": None, + "tx-ipxip6-segmentation": None, + "tx-nocache-copy": None, + "tx-scatter-gather": None, + "tx-scatter-gather-fraglist": None, + "tx-sctp-segmentation": None, + "tx-tcp-ecn-segmentation": None, + "tx-tcp-mangleid-segmentation": None, + "tx-tcp-segmentation": None, + "tx-tcp6-segmentation": None, + "tx-udp-segmentation": None, + "tx-udp_tnl-csum-segmentation": None, + "tx-udp_tnl-segmentation": None, + "tx-vlan-stag-hw-insert": None, + "txvlan": None, +} + +ETHTOOL_DEFAULTS = {"features": ETHTOOL_FEATURES_DEFAULTS} + ETHERNET_DEFAULTS = {"autoneg": None, "duplex": None, "speed": 0} @@ -77,6 +134,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "ignore_errors": None, "ip": { "gateway6": None, @@ -316,6 +374,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "ignore_errors": None, "interface_name": "5", "ip": { @@ -364,6 +423,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "5", @@ -407,6 +467,7 @@ class TestValidator(unittest.TestCase): "autoconnect": False, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "5", @@ -487,6 +548,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": None, @@ -547,6 +609,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "prod1", @@ -604,6 +667,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": None, @@ -650,6 +714,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "prod.100", @@ -739,6 +804,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": None, @@ -785,6 +851,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "prod.100", @@ -874,6 +941,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "eth0", @@ -914,6 +982,7 @@ class TestValidator(unittest.TestCase): "actions": ["present", "up"], "autoconnect": True, "check_iface_exists": True, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "veth0", @@ -963,6 +1032,7 @@ class TestValidator(unittest.TestCase): "actions": ["present", "up"], "autoconnect": True, "check_iface_exists": True, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "veth1", @@ -1062,6 +1132,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "bridge2", @@ -1097,6 +1168,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "eth1", @@ -1156,6 +1228,7 @@ class TestValidator(unittest.TestCase): "bond": {"mode": "balance-rr", "miimon": None}, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "bond1", @@ -1200,6 +1273,7 @@ class TestValidator(unittest.TestCase): "bond": {"mode": "active-backup", "miimon": None}, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "bond1", @@ -1255,6 +1329,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "ignore_errors": None, "interface_name": None, "ip": { @@ -1296,6 +1371,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": {"autoneg": False, "duplex": "half", "speed": 400}, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "5", @@ -1366,6 +1442,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "6643-master", @@ -1401,6 +1478,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "6643", @@ -1451,6 +1529,7 @@ class TestValidator(unittest.TestCase): "actions": ["present", "up"], "autoconnect": True, "check_iface_exists": True, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "infiniband": {"p_key": -1, "transport_mode": "datagram"}, @@ -1519,6 +1598,7 @@ class TestValidator(unittest.TestCase): "actions": ["present", "up"], "autoconnect": True, "check_iface_exists": True, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "infiniband": {"p_key": 5, "transport_mode": "datagram"}, @@ -1595,6 +1675,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "555", @@ -1685,6 +1766,7 @@ class TestValidator(unittest.TestCase): "autoconnect": True, "check_iface_exists": True, "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, "force_state_change": None, "ignore_errors": None, "interface_name": "e556", diff --git a/tests/unit/test_nm_provider.py b/tests/unit/test_nm_provider.py new file mode 100644 index 0000000..0a2679a --- /dev/null +++ b/tests/unit/test_nm_provider.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +""" Tests for network_connections Ansible module """ +# SPDX-License-Identifier: BSD-3-Clause + +import os +import sys + +TESTS_BASEDIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(1, os.path.join(TESTS_BASEDIR, "../..", "library")) +sys.path.insert(1, os.path.join(TESTS_BASEDIR, "../..", "module_utils")) + +try: + from unittest import mock +except ImportError: # py2 + import mock + +sys.modules["ansible"] = mock.Mock() +sys.modules["ansible.module_utils.basic"] = mock.Mock() +sys.modules["ansible.module_utils"] = mock.Mock() +sys.modules["ansible.module_utils.network_lsr"] = __import__("network_lsr") + +with mock.patch.dict("sys.modules", {"gi": mock.Mock(), "gi.repository": mock.Mock()}): + # pylint: disable=import-error, wrong-import-position + from network_lsr import nm_provider + + +def test_get_nm_ethtool_feature(): + """ Test get_nm_ethtool_feature() """ + with mock.patch.object(nm_provider.Util, "NM") as nm_mock: + nm_feature = nm_provider.get_nm_ethtool_feature("esp-hw-offload") + assert nm_feature == nm_mock.return_value.ETHTOOL_OPTNAME_FEATURE_ESP_HW_OFFLOAD