From 6b32b371bfd37759ddce3d7f29d15546500698e6 Mon Sep 17 00:00:00 2001 From: Ani Sinha Date: Thu, 20 Jun 2024 22:27:03 +0530 Subject: [PATCH 1/6] feat(sysconfig): Add DNS from interface config to resolv.conf (#5401) RH-Author: Ani Sinha RH-MergeRequest: 88: feat(sysconfig): Add DNS from interface config to resolv.conf (#5401) RH-Jira: RHEL-17961 RH-Acked-by: Emanuele Giuseppe Esposito RH-Acked-by: Miroslav Rezanina RH-Commit: [1/1] f353b73cc0f4bb9e1aee037708a1d3cb23b83cc3 (anisinha/cloud-init) sysconfig renderer currently only uses global dns and search domain configuration in order to populate /etc/resolv.conf. This means it ignores interface specific dns configuration completely. This means, when global dns information is absent and only interface specific dns configuration is present, /etc/resolv.conf will not have complete dns information. Fix this so that per interface dns information is also taken into account along with global dns configuration in order to populate /etc/resolv.conf. Fixes: GH-5400 Signed-off-by: Ani Sinha (cherry picked from commit 1b8030e0c7fd6fbff7e38ad1e3e6266ae50c83a5) --- cloudinit/net/sysconfig.py | 52 +++++++++- tests/unittests/test_net.py | 184 +++++++++++++++++++++++++++++++++++- 2 files changed, 230 insertions(+), 6 deletions(-) diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index d39f4fe3..7eb430ed 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -825,20 +825,62 @@ class Renderer(renderer.Renderer): @staticmethod def _render_dns(network_state, existing_dns_path=None): - # skip writing resolv.conf if network_state doesn't include any input. + + found_nameservers = [] + found_dns_search = [] + + for iface in network_state.iter_interfaces(): + for subnet in iface["subnets"]: + # Add subnet-level DNS + if "dns_nameservers" in subnet: + found_nameservers.extend(subnet["dns_nameservers"]) + if "dns_search" in subnet: + found_dns_search.extend(subnet["dns_search"]) + + # Add interface-level DNS + if "dns" in iface: + found_nameservers += [ + dns + for dns in iface["dns"]["nameservers"] + if dns not in found_nameservers + ] + found_dns_search += [ + search + for search in iface["dns"]["search"] + if search not in found_dns_search + ] + + # When both global and interface specific entries are present, + # use them both to generate /etc/resolv.conf eliminating duplicate + # entries. Otherwise use global or interface specific entries whichever + # is provided. + if network_state.dns_nameservers: + found_nameservers += [ + nameserver + for nameserver in network_state.dns_nameservers + if nameserver not in found_nameservers + ] + if network_state.dns_searchdomains: + found_dns_search += [ + search + for search in network_state.dns_searchdomains + if search not in found_dns_search + ] + + # skip writing resolv.conf if no dns information is provided in conf. if not any( [ - len(network_state.dns_nameservers), - len(network_state.dns_searchdomains), + len(found_nameservers), + len(found_dns_search), ] ): return None content = resolv_conf.ResolvConf("") if existing_dns_path and os.path.isfile(existing_dns_path): content = resolv_conf.ResolvConf(util.load_file(existing_dns_path)) - for nameserver in network_state.dns_nameservers: + for nameserver in found_nameservers: content.add_nameserver(nameserver) - for searchdomain in network_state.dns_searchdomains: + for searchdomain in found_dns_search: content.add_search_domain(searchdomain) header = _make_header(";") content_str = str(content) diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index d7c9a414..2d716f4b 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -516,6 +516,8 @@ OS_SAMPLES = [ } ], "ip_address": "172.19.1.34", + "dns_search": ["testweb.com"], + "dns_nameservers": ["172.19.0.13"], "id": "network0", } ], @@ -550,7 +552,9 @@ STARTMODE=auto """ ; Created by cloud-init automatically, do not edit. ; +nameserver 172.19.0.13 nameserver 172.19.0.12 +search testweb.com """.lstrip(), ), ( @@ -582,6 +586,8 @@ AUTOCONNECT_PRIORITY=120 BOOTPROTO=none DEFROUTE=yes DEVICE=eth0 +DNS1=172.19.0.13 +DOMAIN=testweb.com GATEWAY=172.19.3.254 HWADDR=fa:16:3e:ed:9a:59 IPADDR=172.19.1.34 @@ -596,7 +602,174 @@ USERCTL=no """ ; Created by cloud-init automatically, do not edit. ; +nameserver 172.19.0.13 nameserver 172.19.0.12 +search testweb.com +""".lstrip(), + ), + ( + "etc/NetworkManager/conf.d/99-cloud-init.conf", + """ +# Created by cloud-init automatically, do not edit. +# +[main] +dns = none +""".lstrip(), + ), + ( + "etc/udev/rules.d/70-persistent-net.rules", + "".join( + [ + 'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ', + 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n', + ] + ), + ), + ], + "expected_network_manager": [ + ( + "".join( + [ + "etc/NetworkManager/system-connections", + "/cloud-init-eth0.nmconnection", + ] + ), + """ +# Generated by cloud-init. Changes will be lost. + +[connection] +id=cloud-init eth0 +uuid=1dd9a779-d327-56e1-8454-c65e2556c12c +autoconnect-priority=120 +type=ethernet + +[user] +org.freedesktop.NetworkManager.origin=cloud-init + +[ethernet] +mac-address=FA:16:3E:ED:9A:59 + +[ipv4] +method=manual +may-fail=false +address1=172.19.1.34/22 +route1=0.0.0.0/0,172.19.3.254 +dns=172.19.0.13; +dns-search=testweb.com; + +""".lstrip(), + ), + ], + }, + { + "in_data": { + "services": [ + { + "type": "dns", + "address": "172.19.0.12", + "search": ["example1.com", "example2.com"], + } + ], + "networks": [ + { + "network_id": "dacd568d-5be6-4786-91fe-750c374b78b4", + "type": "ipv4", + "netmask": "255.255.252.0", + "link": "eth0", + "routes": [ + { + "netmask": "0.0.0.0", + "network": "0.0.0.0", + "gateway": "172.19.3.254", + } + ], + "ip_address": "172.19.1.34", + "dns_search": ["example3.com"], + "dns_nameservers": ["172.19.0.12"], + "id": "network0", + } + ], + "links": [ + { + "ethernet_mac_address": "fa:16:3e:ed:9a:59", + "mtu": None, + "type": "physical", + "id": "eth0", + }, + ], + }, + "in_macs": { + "fa:16:3e:ed:9a:59": "eth0", + }, + "out_sysconfig_opensuse": [ + ( + "etc/sysconfig/network/ifcfg-eth0", + """ +# Created by cloud-init automatically, do not edit. +# +BOOTPROTO=static +IPADDR=172.19.1.34 +LLADDR=fa:16:3e:ed:9a:59 +NETMASK=255.255.252.0 +STARTMODE=auto +""".lstrip(), + ), + ( + "etc/resolv.conf", + """ +; Created by cloud-init automatically, do not edit. +; +nameserver 172.19.0.12 +search example3.com example1.com example2.com +""".lstrip(), + ), + ( + "etc/NetworkManager/conf.d/99-cloud-init.conf", + """ +# Created by cloud-init automatically, do not edit. +# +[main] +dns = none +""".lstrip(), + ), + ( + "etc/udev/rules.d/85-persistent-net-cloud-init.rules", + "".join( + [ + 'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ', + 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n', + ] + ), + ), + ], + "out_sysconfig_rhel": [ + ( + "etc/sysconfig/network-scripts/ifcfg-eth0", + """ +# Created by cloud-init automatically, do not edit. +# +AUTOCONNECT_PRIORITY=120 +BOOTPROTO=none +DEFROUTE=yes +DEVICE=eth0 +DNS1=172.19.0.12 +DOMAIN=example3.com +GATEWAY=172.19.3.254 +HWADDR=fa:16:3e:ed:9a:59 +IPADDR=172.19.1.34 +NETMASK=255.255.252.0 +ONBOOT=yes +TYPE=Ethernet +USERCTL=no +""".lstrip(), + ), + ( + "etc/resolv.conf", + """ +; Created by cloud-init automatically, do not edit. +; +nameserver 172.19.0.12 +search example3.com example1.com example2.com """.lstrip(), ), ( @@ -647,6 +820,7 @@ may-fail=false address1=172.19.1.34/22 route1=0.0.0.0/0,172.19.3.254 dns=172.19.0.12; +dns-search=example3.com; """.lstrip(), ), @@ -654,7 +828,13 @@ dns=172.19.0.12; }, { "in_data": { - "services": [{"type": "dns", "address": "172.19.0.12"}], + "services": [ + { + "type": "dns", + "address": "172.19.0.12", + "search": "example.com", + } + ], "networks": [ { "network_id": "public-ipv4", @@ -715,6 +895,7 @@ STARTMODE=auto ; Created by cloud-init automatically, do not edit. ; nameserver 172.19.0.12 +search example.com """.lstrip(), ), ( @@ -763,6 +944,7 @@ USERCTL=no ; Created by cloud-init automatically, do not edit. ; nameserver 172.19.0.12 +search example.com """.lstrip(), ), ( -- 2.39.3