diff --git a/README.md b/README.md index d4939dd..078217c 100644 --- a/README.md +++ b/README.md @@ -341,6 +341,18 @@ The IP configuration supports the following options: Manual addressing can be specified via a list of addresses under the `address` option. +- `auto_gateway` + + If enabled, a default route will be configured using the default gateway. If disabled, + the default route will be removed. + + If this variable is not specified, the role will use the default behavior of the + `network_provider` selected. + + Setting this option to `no` is equivalent to: + - `DEFROUTE = no` in initscripts, or + - `ipv4.never-default/ipv6.never-default yes` in nmcli + - `dhcp4`, `auto6`, and `ipv6_disabled` Also, manual addressing can be specified by setting either `dhcp4` or `auto6`. diff --git a/library/network_connections.py b/library/network_connections.py index 83d0054..b0508c6 100644 --- a/library/network_connections.py +++ b/library/network_connections.py @@ -510,6 +510,12 @@ class IfcfgUtil: if ip["gateway6"] is not None: ifcfg["IPV6_DEFAULTGW"] = ip["gateway6"] + if ip["auto_gateway"] is not None: + if ip["auto_gateway"]: + ifcfg["DEFROUTE"] = "yes" + else: + ifcfg["DEFROUTE"] = "no" + route4 = [] route6 = [] for r in ip["route"]: @@ -1056,6 +1062,15 @@ class NMUtil: s_ip6.set_property( NM.SETTING_IP_CONFIG_ROUTE_METRIC, ip["route_metric6"] ) + + if ip["auto_gateway"] is not None: + if ip["auto_gateway"]: + s_ip6.set_property(NM.SETTING_IP_CONFIG_NEVER_DEFAULT, False) + s_ip4.set_property(NM.SETTING_IP_CONFIG_NEVER_DEFAULT, False) + else: + s_ip6.set_property(NM.SETTING_IP_CONFIG_NEVER_DEFAULT, True) + s_ip4.set_property(NM.SETTING_IP_CONFIG_NEVER_DEFAULT, True) + for nameserver in ip["dns"]: if nameserver["family"] == socket.AF_INET6: s_ip6.add_dns(nameserver["address"]) diff --git a/module_utils/network_lsr/argument_validator.py b/module_utils/network_lsr/argument_validator.py index b62457b..189e0b2 100644 --- a/module_utils/network_lsr/argument_validator.py +++ b/module_utils/network_lsr/argument_validator.py @@ -578,6 +578,7 @@ class ArgValidator_DictIP(ArgValidatorDict): nested=ArgValidatorIPAddr("address[?]"), default_value=list, ), + ArgValidatorBool("auto_gateway", default_value=None), ArgValidatorList( "route", nested=ArgValidatorIPRoute("route[?]"), default_value=list ), @@ -611,6 +612,7 @@ class ArgValidator_DictIP(ArgValidatorDict): "gateway6": None, "route_metric6": None, "address": [], + "auto_gateway": None, "route": [], "route_append_only": False, "rule_append_only": False, @@ -665,6 +667,20 @@ class ArgValidator_DictIP(ArgValidatorDict): raise ValidationError( name, "'dhcp4_send_hostname' is only valid if 'dhcp4' is enabled" ) + + ipv4_gw_defined = result["gateway4"] is not None + ipv6_gw_defined = result["gateway6"] is not None + dhcp_enabled = result["dhcp4"] or result["auto6"] + + if result["auto_gateway"] and not ( + ipv4_gw_defined or ipv6_gw_defined or dhcp_enabled + ): + raise ValidationError( + name, + "must define 'gateway4', 'gateway6', or use dhcp " + "if 'auto_gateway' is enabled", + ) + return result diff --git a/tests/playbooks/tests_auto_gateway.yml b/tests/playbooks/tests_auto_gateway.yml new file mode 100644 index 0000000..1972a9c --- /dev/null +++ b/tests/playbooks/tests_auto_gateway.yml @@ -0,0 +1,133 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- hosts: all + vars: + type: veth + interface: veth0 + tasks: + - include_tasks: tasks/show_interfaces.yml + - include_tasks: tasks/manage_test_interface.yml + vars: + state: present + - include_tasks: tasks/assert_device_present.yml + - name: >- + TEST: I can configure an interface with auto_gateway enabled + debug: + msg: "##################################################" + - import_role: + name: linux-system-roles.network + vars: + network_connections: + - name: "{{ interface }}" + type: ethernet + state: up + ip: + auto_gateway: yes + dhcp4: no + auto6: no + address: + - "2001:db8::2/64" + - "203.0.113.2/24" + gateway6: "2001:db8::1" + gateway4: "203.0.113.1" + - include_tasks: tasks/assert_device_present.yml + - include_tasks: tasks/assert_profile_present.yml + vars: + profile: "{{ interface }}" + - name: "Show ipv4 routes" + command: "ip route" + register: ipv4_routes + changed_when: false + - name: "Assert default ipv4 route is present" + assert: + that: + - >- + "default via 203.0.113.1 dev {{ interface }}" + in ipv4_routes.stdout + - name: "Get ipv6 routes" + command: "ip -6 route" + register: ipv6_route + changed_when: false + - name: "Assert default ipv6 route is present" + assert: + that: + - >- + "default via 2001:db8::1 dev {{ interface }}" + in ipv6_route.stdout + when: network_provider == "nm" + - name: "TEARDOWN: remove profiles." + debug: + msg: "##################################################" + - 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 + - name: >- + TEST: I can configure an interface with auto_gateway disabled + debug: + msg: "##################################################" + - include_tasks: tasks/manage_test_interface.yml + vars: + state: present + - import_role: + name: linux-system-roles.network + vars: + network_connections: + - name: "{{ interface }}" + type: ethernet + state: up + ip: + auto_gateway: no + dhcp4: no + auto6: no + address: + - "2001:db8::2/64" + - "203.0.113.2/24" + gateway6: "2001:db8::1" + gateway4: "203.0.113.1" + - include_tasks: tasks/assert_device_present.yml + - include_tasks: tasks/assert_profile_present.yml + vars: + profile: "{{ interface }}" + - name: "Show ipv4 routes" + command: "ip route" + register: ipv4_routes + changed_when: false + - name: "Assert default ipv4 route is absent" + assert: + that: + - >- + "default via 203.0.113.1 dev {{ interface }}" + not in ipv4_routes.stdout + - name: "Get ipv6 routes" + command: "ip -6 route" + register: ipv6_route + changed_when: false + - name: "Assert default ipv6 route is absent" + assert: + that: + - >- + "default via 2001:db8::1 dev {{ interface }}" + not in ipv6_route.stdout + when: network_provider == "nm" + - name: "TEARDOWN: remove profiles." + debug: + msg: "##################################################" + - 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 diff --git a/tests/tests_auto_gateway_initscripts.yml b/tests/tests_auto_gateway_initscripts.yml new file mode 100644 index 0000000..2caaf6b --- /dev/null +++ b/tests/tests_auto_gateway_initscripts.yml @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-3-Clause +# This file was generated by ensure_provider_tests.py +--- +- hosts: all + name: > + Run playbook 'playbooks/tests_auto_gateway.yml' with + initscripts as provider + tasks: + - name: Set network provider to 'initscripts' + set_fact: + network_provider: initscripts + tags: + - always + +- import_playbook: playbooks/tests_auto_gateway.yml diff --git a/tests/tests_auto_gateway_nm.yml b/tests/tests_auto_gateway_nm.yml new file mode 100644 index 0000000..f55f18f --- /dev/null +++ b/tests/tests_auto_gateway_nm.yml @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: BSD-3-Clause +# This file was generated by ensure_provider_tests.py +--- +# set network provider and gather facts +- hosts: all + name: Run playbook 'playbooks/tests_auto_gateway.yml' with nm as provider + tasks: + - name: Set network provider to 'nm' + set_fact: + network_provider: nm + tags: + - always + + +# The test requires or should run with NetworkManager, therefore it cannot run +# on RHEL/CentOS 6 +- import_playbook: playbooks/tests_auto_gateway.yml + when: + - ansible_distribution_major_version != '6' diff --git a/tests/unit/test_network_connections.py b/tests/unit/test_network_connections.py index 0d8bd5d..3b57868 100644 --- a/tests/unit/test_network_connections.py +++ b/tests/unit/test_network_connections.py @@ -174,6 +174,7 @@ class TestValidator(unittest.TestCase): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -469,6 +470,7 @@ class TestValidator(unittest.TestCase): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -523,6 +525,7 @@ class TestValidator(unittest.TestCase): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -571,6 +574,7 @@ class TestValidator(unittest.TestCase): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -667,6 +671,7 @@ class TestValidator(unittest.TestCase): "address": "192.168.174.5", } ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -732,6 +737,7 @@ class TestValidator(unittest.TestCase): "address": "192.168.174.5", } ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -799,6 +805,7 @@ class TestValidator(unittest.TestCase): "prefix": 32, }, ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -892,6 +899,7 @@ class TestValidator(unittest.TestCase): "address": "192.168.177.5", }, ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -951,6 +959,7 @@ class TestValidator(unittest.TestCase): "address": "a:b:c::6", }, ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [ @@ -1008,6 +1017,174 @@ class TestValidator(unittest.TestCase): ], ) + def test_auto_gateway_true(self): + self.maxDiff = None + self.do_connections_validate( + [ + { + "actions": ["present", "up"], + "autoconnect": True, + "check_iface_exists": True, + "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, + "force_state_change": None, + "ignore_errors": None, + "interface_name": "prod1", + "ip": { + "dhcp4": True, + "route_metric6": None, + "route_metric4": None, + "dns_options": [], + "dns_search": [], + "dhcp4_send_hostname": None, + "gateway6": None, + "gateway4": None, + "auto6": True, + "ipv6_disabled": False, + "dns": [], + "address": [], + "auto_gateway": True, + "route_append_only": False, + "rule_append_only": False, + "route": [], + }, + "mac": None, + "controller": None, + "ieee802_1x": None, + "wireless": None, + "mtu": None, + "name": "prod1", + "parent": None, + "persistent_state": "present", + "port_type": None, + "state": "up", + "type": "ethernet", + "wait": None, + "zone": None, + } + ], + [ + { + "name": "prod1", + "state": "up", + "type": "ethernet", + "ip": {"auto_gateway": True}, + } + ], + initscripts_dict_expected=[ + { + "ifcfg": { + "BOOTPROTO": "dhcp", + "DEFROUTE": "yes", + "IPV6INIT": "yes", + "IPV6_AUTOCONF": "yes", + "NM_CONTROLLED": "no", + "ONBOOT": "yes", + "DEVICE": "prod1", + "TYPE": "Ethernet", + }, + "keys": None, + "route": None, + "route6": None, + "rule": None, + "rule6": None, + } + ], + ) + + def test_auto_gateway_false(self): + self.maxDiff = None + self.do_connections_validate( + [ + { + "actions": ["present", "up"], + "autoconnect": True, + "check_iface_exists": True, + "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, + "force_state_change": None, + "ignore_errors": None, + "interface_name": "prod1", + "ip": { + "dhcp4": True, + "route_metric6": None, + "route_metric4": None, + "dns_options": [], + "dns_search": [], + "dhcp4_send_hostname": None, + "gateway6": None, + "gateway4": None, + "auto6": True, + "ipv6_disabled": False, + "dns": [], + "address": [], + "auto_gateway": False, + "route_append_only": False, + "rule_append_only": False, + "route": [], + }, + "mac": None, + "controller": None, + "ieee802_1x": None, + "wireless": None, + "mtu": None, + "name": "prod1", + "parent": None, + "persistent_state": "present", + "port_type": None, + "state": "up", + "type": "ethernet", + "wait": None, + "zone": None, + } + ], + [ + { + "name": "prod1", + "state": "up", + "type": "ethernet", + "ip": {"auto_gateway": False}, + } + ], + initscripts_dict_expected=[ + { + "ifcfg": { + "BOOTPROTO": "dhcp", + "DEFROUTE": "no", + "IPV6INIT": "yes", + "IPV6_AUTOCONF": "yes", + "NM_CONTROLLED": "no", + "ONBOOT": "yes", + "DEVICE": "prod1", + "TYPE": "Ethernet", + }, + "keys": None, + "route": None, + "route6": None, + "rule": None, + "rule6": None, + } + ], + ) + + def test_auto_gateway_no_gateway(self): + self.maxDiff = None + self.do_connections_check_invalid( + [ + { + "name": "eth0", + "state": "up", + "type": "ethernet", + "ip": { + "dhcp4": "no", + "auto6": "no", + "auto_gateway": "true", + "address": "192.168.176.5/24", + }, + } + ] + ) + def test_vlan(self): self.maxDiff = None self.do_connections_validate( @@ -1036,6 +1213,7 @@ class TestValidator(unittest.TestCase): "address": "192.168.177.5", }, ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -1096,6 +1274,7 @@ class TestValidator(unittest.TestCase): "address": "a:b:c::6", }, ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [ @@ -1176,6 +1355,7 @@ class TestValidator(unittest.TestCase): "address": "192.168.122.3", } ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -1230,6 +1410,7 @@ class TestValidator(unittest.TestCase): "address": "192.168.244.1", } ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [ @@ -1284,6 +1465,7 @@ class TestValidator(unittest.TestCase): "address": "192.168.245.7", } ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [ @@ -1371,6 +1553,7 @@ class TestValidator(unittest.TestCase): "interface_name": "bridge2", "ip": { "address": [], + "auto_gateway": None, "auto6": False, "dhcp4": False, "dhcp4_send_hostname": None, @@ -1411,6 +1594,7 @@ class TestValidator(unittest.TestCase): "interface_name": "eth1", "ip": { "address": [], + "auto_gateway": None, "auto6": True, "dhcp4": True, "dhcp4_send_hostname": None, @@ -1486,6 +1670,7 @@ class TestValidator(unittest.TestCase): "ipv6_disabled": False, "dns": [], "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -1535,6 +1720,7 @@ class TestValidator(unittest.TestCase): "ipv6_disabled": False, "dns": [], "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -1583,6 +1769,7 @@ class TestValidator(unittest.TestCase): "interface_name": None, "ip": { "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -1636,6 +1823,7 @@ class TestValidator(unittest.TestCase): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -1705,6 +1893,7 @@ class TestValidator(unittest.TestCase): "interface_name": "6643-controller", "ip": { "address": [], + "auto_gateway": None, "auto6": True, "ipv6_disabled": False, "dhcp4": True, @@ -1745,6 +1934,7 @@ class TestValidator(unittest.TestCase): "interface_name": "6643", "ip": { "address": [], + "auto_gateway": None, "auto6": True, "dhcp4_send_hostname": None, "dhcp4": True, @@ -1801,6 +1991,7 @@ class TestValidator(unittest.TestCase): "interface_name": None, "ip": { "address": [], + "auto_gateway": None, "auto6": True, "dhcp4": True, "dhcp4_send_hostname": None, @@ -1874,6 +2065,7 @@ class TestValidator(unittest.TestCase): "interface_name": None, "ip": { "address": [], + "auto_gateway": None, "auto6": True, "dhcp4": True, "dhcp4_send_hostname": None, @@ -1960,6 +2152,7 @@ class TestValidator(unittest.TestCase): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [ @@ -2055,6 +2248,7 @@ class TestValidator(unittest.TestCase): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": True, "rule_append_only": False, "route": [ @@ -2192,6 +2386,7 @@ class TestValidator(unittest.TestCase): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -2269,6 +2464,7 @@ class TestValidator(unittest.TestCase): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -2346,6 +2542,7 @@ class TestValidator(unittest.TestCase): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -2421,6 +2618,7 @@ class TestValidator(unittest.TestCase): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -2486,6 +2684,7 @@ class TestValidator(unittest.TestCase): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [],