From b368bce8aab8b7dca235f9d2da3b7efb037e0e21 Mon Sep 17 00:00:00 2001 From: Jack Adolph Date: Thu, 29 Oct 2020 12:46:18 +1100 Subject: [PATCH] Add 'auto_gateway' option 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 Signed-off-by: Jack Adolph --- README.md | 12 ++ library/network_connections.py | 15 ++ .../network_lsr/argument_validator.py | 16 ++ tests/playbooks/tests_auto_gateway.yml | 133 ++++++++++++ tests/tests_auto_gateway_initscripts.yml | 15 ++ tests/tests_auto_gateway_nm.yml | 19 ++ tests/unit/test_network_connections.py | 199 ++++++++++++++++++ 7 files changed, 409 insertions(+) create mode 100644 tests/playbooks/tests_auto_gateway.yml create mode 100644 tests/tests_auto_gateway_initscripts.yml create mode 100644 tests/tests_auto_gateway_nm.yml 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": [],