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 d4939dd3..078217c4 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 83d00545..b0508c61 100644 --- a/library/network_connections.py +++ b/library/network_connections.py @@ -510,6 +510,12 @@ def ifcfg_create( 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 @@ def connection_create(self, connections, idx, connection_current=None): 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 b62457b8..189e0b27 100644 --- a/module_utils/network_lsr/argument_validator.py +++ b/module_utils/network_lsr/argument_validator.py @@ -578,6 +578,7 @@ def __init__(self): nested=ArgValidatorIPAddr("address[?]"), default_value=list, ), + ArgValidatorBool("auto_gateway", default_value=None), ArgValidatorList( "route", nested=ArgValidatorIPRoute("route[?]"), default_value=list ), @@ -611,6 +612,7 @@ def __init__(self): "gateway6": None, "route_metric6": None, "address": [], + "auto_gateway": None, "route": [], "route_append_only": False, "rule_append_only": False, @@ -665,6 +667,20 @@ def _validate_post(self, value, name, result): 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 00000000..1972a9cd --- /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 00000000..2caaf6b1 --- /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 00000000..f55f18fd --- /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 0d8bd5dc..3b578681 100644 --- a/tests/unit/test_network_connections.py +++ b/tests/unit/test_network_connections.py @@ -174,6 +174,7 @@ def setUp(self): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -469,6 +470,7 @@ def test_ethernet_two_defaults(self): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -523,6 +525,7 @@ def test_up_ethernet(self): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -571,6 +574,7 @@ def test_up_ethernet_no_autoconnect(self): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -667,6 +671,7 @@ def test_up_ethernet_mac_mtu_static_ip(self): "address": "192.168.174.5", } ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -732,6 +737,7 @@ def test_up_single_v4_dns(self): "address": "192.168.174.5", } ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -799,6 +805,7 @@ def test_ipv6_static(self): "prefix": 32, }, ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -892,6 +899,7 @@ def test_routes(self): "address": "192.168.177.5", }, ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -951,6 +959,7 @@ def test_routes(self): "address": "a:b:c::6", }, ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [ @@ -1008,6 +1017,174 @@ def test_routes(self): ], ) + 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 @@ def test_vlan(self): "address": "192.168.177.5", }, ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -1096,6 +1274,7 @@ def test_vlan(self): "address": "a:b:c::6", }, ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [ @@ -1176,6 +1355,7 @@ def test_macvlan(self): "address": "192.168.122.3", } ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -1230,6 +1410,7 @@ def test_macvlan(self): "address": "192.168.244.1", } ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [ @@ -1284,6 +1465,7 @@ def test_macvlan(self): "address": "192.168.245.7", } ], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [ @@ -1371,6 +1553,7 @@ def test_bridge_no_dhcp4_auto6(self): "interface_name": "bridge2", "ip": { "address": [], + "auto_gateway": None, "auto6": False, "dhcp4": False, "dhcp4_send_hostname": None, @@ -1411,6 +1594,7 @@ def test_bridge_no_dhcp4_auto6(self): "interface_name": "eth1", "ip": { "address": [], + "auto_gateway": None, "auto6": True, "dhcp4": True, "dhcp4_send_hostname": None, @@ -1486,6 +1670,7 @@ def test_bond(self): "ipv6_disabled": False, "dns": [], "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -1535,6 +1720,7 @@ def test_bond_active_backup(self): "ipv6_disabled": False, "dns": [], "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -1583,6 +1769,7 @@ def test_ethernet_mac_address(self): "interface_name": None, "ip": { "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -1636,6 +1823,7 @@ def test_ethernet_speed_settings(self): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -1705,6 +1893,7 @@ def test_bridge2(self): "interface_name": "6643-controller", "ip": { "address": [], + "auto_gateway": None, "auto6": True, "ipv6_disabled": False, "dhcp4": True, @@ -1745,6 +1934,7 @@ def test_bridge2(self): "interface_name": "6643", "ip": { "address": [], + "auto_gateway": None, "auto6": True, "dhcp4_send_hostname": None, "dhcp4": True, @@ -1801,6 +1991,7 @@ def test_infiniband(self): "interface_name": None, "ip": { "address": [], + "auto_gateway": None, "auto6": True, "dhcp4": True, "dhcp4_send_hostname": None, @@ -1874,6 +2065,7 @@ def test_infiniband2(self): "interface_name": None, "ip": { "address": [], + "auto_gateway": None, "auto6": True, "dhcp4": True, "dhcp4_send_hostname": None, @@ -1960,6 +2152,7 @@ def test_route_metric_prefix(self): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [ @@ -2055,6 +2248,7 @@ def test_route_v6(self): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": True, "rule_append_only": False, "route": [ @@ -2192,6 +2386,7 @@ def test_802_1x_1(self): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -2269,6 +2464,7 @@ def test_802_1x_2(self): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -2346,6 +2542,7 @@ def test_802_1x_3(self): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -2421,6 +2618,7 @@ def test_wireless_psk(self): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [], @@ -2486,6 +2684,7 @@ def test_wireless_eap(self): "ipv6_disabled": False, "dhcp4": True, "address": [], + "auto_gateway": None, "route_append_only": False, "rule_append_only": False, "route": [],