From 93eb308bd96a5212887a5952f88e6e265b021669 Mon Sep 17 00:00:00 2001 From: eabdullin Date: Thu, 13 Jun 2024 10:30:18 +0300 Subject: [PATCH] - Import from CS git - Fix the release version to match upstream --- ...l-DNS-to-interfaces-in-network-manag.patch | 207 ++++++++++ ...-single-datasource-if-specified-5098.patch | 66 +++ ...-NetworkManager-route-rendering-4637.patch | 391 ++++++++++++++++++ ...-cached-local-ds-if-no-valid-ds-foun.patch | 156 +++++++ ...network-in-schema-route-definition-5.patch | 42 ++ SPECS/cloud-init.spec | 31 +- 6 files changed, 892 insertions(+), 1 deletion(-) create mode 100644 SOURCES/ci-feat-apply-global-DNS-to-interfaces-in-network-manag.patch create mode 100644 SOURCES/ci-fix-Always-use-single-datasource-if-specified-5098.patch create mode 100644 SOURCES/ci-fix-Correct-v2-NetworkManager-route-rendering-4637.patch create mode 100644 SOURCES/ci-fix-Fall-back-to-cached-local-ds-if-no-valid-ds-foun.patch create mode 100644 SOURCES/ci-fix-Undeprecate-network-in-schema-route-definition-5.patch diff --git a/SOURCES/ci-feat-apply-global-DNS-to-interfaces-in-network-manag.patch b/SOURCES/ci-feat-apply-global-DNS-to-interfaces-in-network-manag.patch new file mode 100644 index 0000000..bbef09a --- /dev/null +++ b/SOURCES/ci-feat-apply-global-DNS-to-interfaces-in-network-manag.patch @@ -0,0 +1,207 @@ +From c21351ad9da5aebcb252aa36cbfa92ac16fa9746 Mon Sep 17 00:00:00 2001 +From: Florian Apolloner +Date: Fri, 5 Jan 2024 19:07:12 +0100 +Subject: [PATCH 2/3] feat: apply global DNS to interfaces in network-manager + (#4723) + +RH-Author: Cathy Avery +RH-MergeRequest: 72: Fixes for cloud-init fails to configure DNS/search domains for network-config v1 +RH-Jira: RHEL-20964 +RH-Acked-by: Ani Sinha +RH-Acked-by: Emanuele Giuseppe Esposito +RH-Commit: [2/2] 1d2b10133ec2558e9665f21f53e4b1a898e283a8 (cavery/cloud-init-c-9-s) + +Sometimes DNS settings in cloud configs are specified globally and +not per interface / subnet. This results in a configuration without +proper nameservers. This was fixed for netplan in d29eeccd and is +now also applied to the network-manager renderer. + +Co-authored-by: James Falcon +(cherry picked from commit 0d787d0a262f70ff848b315633742aa8fc45a1de) +Signed-off-by: Cathy Avery +--- + cloudinit/net/network_manager.py | 52 ++++++++++++++--------- + tests/unittests/net/test_net_rendering.py | 3 ++ + tests/unittests/test_net.py | 11 +++++ + tools/.github-cla-signers | 1 + + 4 files changed, 47 insertions(+), 20 deletions(-) + +diff --git a/cloudinit/net/network_manager.py b/cloudinit/net/network_manager.py +index bd6e6d75..0ba210b7 100644 +--- a/cloudinit/net/network_manager.py ++++ b/cloudinit/net/network_manager.py +@@ -246,7 +246,7 @@ class NMConnection: + """ + return addr.replace("-", ":").upper() + +- def render_interface(self, iface, renderer): ++ def render_interface(self, iface, network_state, renderer): + """ + Integrate information from network state interface information + into the connection. Most of the work is done here. +@@ -311,7 +311,6 @@ class NMConnection: + found_dns_search = [] + + # Deal with Layer 3 configuration +- use_top_level_dns = "dns" in iface + for subnet in iface["subnets"]: + family = "ipv6" if subnet_is_ipv6(subnet) else "ipv4" + +@@ -322,26 +321,39 @@ class NMConnection: + self.config[family]["gateway"] = subnet["gateway"] + for route in subnet["routes"]: + self._add_route(route) +- if not use_top_level_dns and "dns_nameservers" in subnet: +- for nameserver in subnet["dns_nameservers"]: +- found_nameservers.append(nameserver) +- if not use_top_level_dns and "dns_search" in subnet: +- found_dns_search.append(subnet["dns_search"]) ++ # 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"]) + if family == "ipv4" and "mtu" in subnet: + ipv4_mtu = subnet["mtu"] + +- # Now add our DNS search domains. We add them later because we +- # only want them if an IP family has already been defined +- if use_top_level_dns: +- for nameserver in iface["dns"]["nameservers"]: +- self._add_nameserver(nameserver) +- if iface["dns"]["search"]: +- self._add_dns_search(iface["dns"]["search"]) +- else: +- for nameserver in found_nameservers: +- self._add_nameserver(nameserver) +- for dns_search in found_dns_search: +- self._add_dns_search(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 ++ ] ++ ++ # We prefer any interface-specific DNS entries, but if we do not ++ # have any, add the global DNS to the connection ++ if not found_nameservers and network_state.dns_nameservers: ++ found_nameservers = network_state.dns_nameservers ++ if not found_dns_search and network_state.dns_searchdomains: ++ found_dns_search = network_state.dns_searchdomains ++ ++ # Write out all DNS entries to the connection ++ for nameserver in found_nameservers: ++ self._add_nameserver(nameserver) ++ if found_dns_search: ++ self._add_dns_search(found_dns_search) + + # we do not want to set may-fail to false for both ipv4 and ipv6 dhcp + # at the at the same time. This will make the network configuration +@@ -457,7 +469,7 @@ class Renderer(renderer.Renderer): + # Now render the actual interface configuration + for iface in network_state.iter_interfaces(): + conn = self.connections[iface["name"]] +- conn.render_interface(iface, self) ++ conn.render_interface(iface, network_state, self) + + # And finally write the files + for con_id, conn in self.connections.items(): +diff --git a/tests/unittests/net/test_net_rendering.py b/tests/unittests/net/test_net_rendering.py +index 06feab89..f340ffc1 100644 +--- a/tests/unittests/net/test_net_rendering.py ++++ b/tests/unittests/net/test_net_rendering.py +@@ -88,6 +88,9 @@ def _check_network_manager(network_state: NetworkState, tmp_path: Path): + "test_name, renderers", + [("no_matching_mac_v2", Renderer.Netplan | Renderer.NetworkManager)], + ) ++@pytest.mark.xfail( ++ reason="v2 interface-specific DNS errantly gets applied globally" ++) + def test_convert(test_name, renderers, tmp_path): + network_config = safeyaml.load( + Path(ARTIFACT_DIR, f"{test_name}.yaml").read_text() +diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py +index 2a99f150..d7c9a414 100644 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -646,6 +646,7 @@ 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.12; + + """.lstrip(), + ), +@@ -2797,6 +2798,8 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + [ipv4] + method=auto + may-fail=false ++ dns=8.8.8.8;4.4.4.4;8.8.4.4; ++ dns-search=barley.maas;wark.maas;foobar.maas; + + """ + ), +@@ -2822,6 +2825,8 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + method=manual + may-fail=false + address1=192.168.200.7/24 ++ dns=8.8.8.8;4.4.4.4;8.8.4.4; ++ dns-search=barley.maas;wark.maas;foobar.maas; + + """ + ), +@@ -2846,6 +2851,8 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + [ipv4] + method=auto + may-fail=false ++ dns=8.8.8.8;4.4.4.4;8.8.4.4; ++ dns-search=barley.maas;wark.maas;foobar.maas; + + """ + ), +@@ -2930,12 +2937,15 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + method=manual + may-fail=false + address1=192.168.14.2/24 ++ dns=8.8.8.8;4.4.4.4;8.8.4.4; ++ dns-search=barley.maas;wark.maas;foobar.maas; + + [ipv6] + method=manual + may-fail=false + address1=2001:1::1/64 + route1=::/0,2001:4800:78ff:1b::1 ++ dns-search=barley.maas;wark.maas;foobar.maas; + + """ + ), +@@ -2990,6 +3000,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + [ipv6] + method=auto + may-fail=false ++ dns-search=barley.maas;wark.maas;foobar.maas; + + """ + ), +diff --git a/tools/.github-cla-signers b/tools/.github-cla-signers +index dbdb9cfa..f4da0989 100644 +--- a/tools/.github-cla-signers ++++ b/tools/.github-cla-signers +@@ -13,6 +13,7 @@ andrewbogott + andrewlukoshko + ani-sinha + antonyc ++apollo13 + aswinrajamannar + bdrung + beantaxi +-- +2.39.3 + diff --git a/SOURCES/ci-fix-Always-use-single-datasource-if-specified-5098.patch b/SOURCES/ci-fix-Always-use-single-datasource-if-specified-5098.patch new file mode 100644 index 0000000..bcb65c9 --- /dev/null +++ b/SOURCES/ci-fix-Always-use-single-datasource-if-specified-5098.patch @@ -0,0 +1,66 @@ +From 62cec1e38e117fe6b24888862576ac57be14bbda Mon Sep 17 00:00:00 2001 +From: James Falcon +Date: Tue, 26 Mar 2024 15:55:50 -0500 +Subject: [PATCH] fix: Always use single datasource if specified (#5098) + +RH-Author: Ani Sinha +RH-MergeRequest: 82: fix: Always use single datasource if specified (#5098) +RH-Jira: RHEL-36255 +RH-Acked-by: Cathy Avery +RH-Acked-by: Miroslav Rezanina +RH-Commit: [1/1] 068e97fcc18dd99f1112a9109acdb30fe2880f6e (anisinha/cloud-init) + +This change may require a user to add `None` to the `datasource_list` +defined in `/etc/cloud/cloud.cfg[.d]` if they have a customized +datasource_list and want the DataSourceNone fallback behavior. + +ds-identify would automatically append "None" to the datasource_list +if a single entry was provided in /etc/cloud/cloud.cfg[.d]. +This wasn't a problem in the past as the python code would detect +a single datasource along with None as an indication to automatically +use that datasource. Since the python code no longer does that, +we should ensure that one specified datasource results in one specified +datasource after ds-identify has run. + +Fixes GH-5091 + +(cherry picked from commit cdbbd17ae400e432d13f674c18a6f5c873fa328b) +Signed-off-by: Ani Sinha +--- + tests/unittests/test_ds_identify.py | 2 +- + tools/ds-identify | 6 +++++- + 2 files changed, 6 insertions(+), 2 deletions(-) + +diff --git a/tests/unittests/test_ds_identify.py b/tests/unittests/test_ds_identify.py +index ba0bf779..acbf3f03 100644 +--- a/tests/unittests/test_ds_identify.py ++++ b/tests/unittests/test_ds_identify.py +@@ -522,7 +522,7 @@ class TestDsIdentify(DsIdentifyBase): + mydata = copy.deepcopy(VALID_CFG["Ec2-hvm"]) + cfgpath = "etc/cloud/cloud.cfg.d/myds.cfg" + mydata["files"][cfgpath] = 'datasource_list: ["NoCloud"]\n' +- self._check_via_dict(mydata, rc=RC_FOUND, dslist=["NoCloud", DS_NONE]) ++ self._check_via_dict(mydata, rc=RC_FOUND, dslist=["NoCloud"]) + + def test_configured_list_with_none(self): + """When datasource_list already contains None, None is not added. +diff --git a/tools/ds-identify b/tools/ds-identify +index ec2cc18a..6e49ded3 100755 +--- a/tools/ds-identify ++++ b/tools/ds-identify +@@ -1865,7 +1865,11 @@ _main() { + # if there is only a single entry in $DI_DSLIST + if [ $# -eq 1 ] || [ $# -eq 2 -a "$2" = "None" ] ; then + debug 1 "single entry in datasource_list ($DI_DSLIST) use that." +- found "$@" ++ if [ $# -eq 1 ]; then ++ write_result "datasource_list: [ $1 ]" ++ else ++ found "$@" ++ fi + return + fi + +-- +2.39.3 + diff --git a/SOURCES/ci-fix-Correct-v2-NetworkManager-route-rendering-4637.patch b/SOURCES/ci-fix-Correct-v2-NetworkManager-route-rendering-4637.patch new file mode 100644 index 0000000..6d13dc7 --- /dev/null +++ b/SOURCES/ci-fix-Correct-v2-NetworkManager-route-rendering-4637.patch @@ -0,0 +1,391 @@ +From aaf1d063f198ce09f0d539a85e1a1a2bb834520b Mon Sep 17 00:00:00 2001 +From: James Falcon +Date: Tue, 2 Jan 2024 11:29:17 -0600 +Subject: [PATCH 1/3] fix: Correct v2 NetworkManager route rendering (#4637) + +RH-Author: Cathy Avery +RH-MergeRequest: 72: Fixes for cloud-init fails to configure DNS/search domains for network-config v1 +RH-Jira: RHEL-20964 +RH-Acked-by: Ani Sinha +RH-Acked-by: Emanuele Giuseppe Esposito +RH-Commit: [1/2] fb865987dbcf506a674eb9798f9c06859539a696 (cavery/cloud-init-c-9-s) + +fix: Correct v2 NetworkManager route rendering + +Because network v2 route defintions can have mixed v4 and v6 routes, we +need to determine the IP family per route rather than per subnet. + +Similar, ensure dns-search is rendered correctly. + +Fixes GH-4518 + +(cherry picked from commit c2c100e8c9fd8709539b3ab2b0ee34c66ba3f2f7) +Signed-off-by: Cathy Avery +--- + cloudinit/net/__init__.py | 2 + + cloudinit/net/network_manager.py | 87 +++++++++------- + tests/unittests/test_net.py | 165 ++++++++++++++++++++++++++++++- + 3 files changed, 219 insertions(+), 35 deletions(-) + +diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py +index c0888f52..65e7ff33 100644 +--- a/cloudinit/net/__init__.py ++++ b/cloudinit/net/__init__.py +@@ -1287,6 +1287,8 @@ def subnet_is_ipv6(subnet) -> bool: + """Common helper for checking network_state subnets for ipv6.""" + # 'static6', 'dhcp6', 'ipv6_dhcpv6-stateful', 'ipv6_dhcpv6-stateless' or + # 'ipv6_slaac' ++ # This function is inappropriate for v2-based routes as routes defined ++ # under v2 subnets can contain ipv4 and ipv6 simultaneously + if subnet["type"].endswith("6") or subnet["type"] in IPV6_DYNAMIC_TYPES: + # This is a request either static6 type or DHCPv6. + return True +diff --git a/cloudinit/net/network_manager.py b/cloudinit/net/network_manager.py +index 76a0ac15..bd6e6d75 100644 +--- a/cloudinit/net/network_manager.py ++++ b/cloudinit/net/network_manager.py +@@ -12,10 +12,15 @@ import itertools + import logging + import os + import uuid +-from typing import Optional ++from typing import List, Optional + + from cloudinit import subp, util +-from cloudinit.net import is_ipv6_address, renderer, subnet_is_ipv6 ++from cloudinit.net import ( ++ is_ipv6_address, ++ is_ipv6_network, ++ renderer, ++ subnet_is_ipv6, ++) + from cloudinit.net.network_state import NetworkState + from cloudinit.net.sysconfig import available_nm_ifcfg_rh + +@@ -158,11 +163,11 @@ class NMConnection: + if self.config[family]["method"] == "auto" and method == "manual": + return + +- if ( +- subnet_type == "ipv6_dhcpv6-stateful" +- or subnet_type == "ipv6_dhcpv6-stateless" +- or subnet_type == "ipv6_slaac" +- ): ++ if subnet_type in [ ++ "ipv6_dhcpv6-stateful", ++ "ipv6_dhcpv6-stateless", ++ "ipv6_slaac", ++ ]: + # set ipv4 method to 'disabled' to align with sysconfig renderer. + self._set_default("ipv4", "method", "disabled") + +@@ -174,7 +179,8 @@ class NMConnection: + Adds a numbered property, such as address or route, ensuring + the appropriate value gets used for . + """ +- ++ if not self.config.has_section(section): ++ self.config[section] = {} + for index in itertools.count(1): + key = f"{key_prefix}{index}" + if not self.config.has_option(section, key): +@@ -189,40 +195,37 @@ class NMConnection: + value = subnet["address"] + "/" + str(subnet["prefix"]) + self._add_numbered(family, "address", value) + +- def _add_route(self, family, route): +- """ +- Adds a ipv[46].route property. +- """ +- ++ def _add_route(self, route): ++ """Adds a ipv[46].route property.""" ++ # Because network v2 route definitions can have mixed v4 and v6 ++ # routes, determine the family per route based on the gateway ++ family = "ipv6" if is_ipv6_network(route["gateway"]) else "ipv4" + value = route["network"] + "/" + str(route["prefix"]) + if "gateway" in route: + value = value + "," + route["gateway"] + self._add_numbered(family, "route", value) + +- def _add_nameserver(self, dns): ++ def _add_nameserver(self, dns: str) -> None: + """ + Extends the ipv[46].dns property with a name server. + """ +- +- # FIXME: the subnet contains IPv4 and IPv6 name server mixed +- # together. We might be getting an IPv6 name server while +- # we're dealing with an IPv4 subnet. Sort this out by figuring +- # out the correct family and making sure a valid section exist. + family = "ipv6" if is_ipv6_address(dns) else "ipv4" +- self._set_default(family, "method", "disabled") +- +- self._set_default(family, "dns", "") +- self.config[family]["dns"] = self.config[family]["dns"] + dns + ";" ++ if self.config.has_section(family): ++ self._set_default(family, "dns", "") ++ self.config[family]["dns"] = self.config[family]["dns"] + dns + ";" + +- def _add_dns_search(self, family, dns_search): ++ def _add_dns_search(self, dns_search: List[str]) -> None: + """ + Extends the ipv[46].dns-search property with a name server. + """ +- +- self._set_default(family, "dns-search", "") +- self.config[family]["dns-search"] = ( +- self.config[family]["dns-search"] + ";".join(dns_search) + ";" +- ) ++ for family in ["ipv4", "ipv6"]: ++ if self.config.has_section(family): ++ self._set_default(family, "dns-search", "") ++ self.config[family]["dns-search"] = ( ++ self.config[family]["dns-search"] ++ + ";".join(dns_search) ++ + ";" ++ ) + + def con_uuid(self): + """ +@@ -304,8 +307,11 @@ class NMConnection: + + device_mtu = iface["mtu"] + ipv4_mtu = None ++ found_nameservers = [] ++ found_dns_search = [] + + # Deal with Layer 3 configuration ++ use_top_level_dns = "dns" in iface + for subnet in iface["subnets"]: + family = "ipv6" if subnet_is_ipv6(subnet) else "ipv4" + +@@ -315,15 +321,28 @@ class NMConnection: + if "gateway" in subnet: + self.config[family]["gateway"] = subnet["gateway"] + for route in subnet["routes"]: +- self._add_route(family, route) +- if "dns_nameservers" in subnet: ++ self._add_route(route) ++ if not use_top_level_dns and "dns_nameservers" in subnet: + for nameserver in subnet["dns_nameservers"]: +- self._add_nameserver(nameserver) +- if "dns_search" in subnet: +- self._add_dns_search(family, subnet["dns_search"]) ++ found_nameservers.append(nameserver) ++ if not use_top_level_dns and "dns_search" in subnet: ++ found_dns_search.append(subnet["dns_search"]) + if family == "ipv4" and "mtu" in subnet: + ipv4_mtu = subnet["mtu"] + ++ # Now add our DNS search domains. We add them later because we ++ # only want them if an IP family has already been defined ++ if use_top_level_dns: ++ for nameserver in iface["dns"]["nameservers"]: ++ self._add_nameserver(nameserver) ++ if iface["dns"]["search"]: ++ self._add_dns_search(iface["dns"]["search"]) ++ else: ++ for nameserver in found_nameservers: ++ self._add_nameserver(nameserver) ++ for dns_search in found_dns_search: ++ self._add_dns_search(dns_search) ++ + # we do not want to set may-fail to false for both ipv4 and ipv6 dhcp + # at the at the same time. This will make the network configuration + # work only when both ipv4 and ipv6 dhcp succeeds. This may not be +diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py +index d9ef493b..2a99f150 100644 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -2962,9 +2962,9 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + may-fail=false + address1=192.168.0.2/24 + gateway=192.168.0.1 ++ address2=192.168.2.10/24 + dns=192.168.0.10;10.23.23.134; + dns-search=barley.maas;sacchromyces.maas;brettanomyces.maas; +- address2=192.168.2.10/24 + + """ + ), +@@ -4154,6 +4154,148 @@ iface bond0 inet6 static + """ + ), + }, ++ "v2-mixed-routes": { ++ "expected_network_manager": { ++ "cloud-init-eth0.nmconnection": textwrap.dedent( ++ """\ ++ # Generated by cloud-init. Changes will be lost. ++ ++ [connection] ++ id=cloud-init eth0 ++ uuid=1dd9a779-d327-56e1-8454-c65e2556c12c ++ autoconnect-priority=120 ++ type=ethernet ++ interface-name=eth0 ++ ++ [user] ++ org.freedesktop.NetworkManager.origin=cloud-init ++ ++ [ethernet] ++ ++ [ipv4] ++ method=auto ++ may-fail=true ++ route1=169.254.42.42/32,62.210.0.1 ++ route2=169.254.42.43/32,62.210.0.2 ++ address1=192.168.1.20/16 ++ dns=8.8.8.8; ++ dns-search=lab;home; ++ ++ [ipv6] ++ route1=::/0,fe80::dc00:ff:fe20:186 ++ route2=fe80::dc00:ff:fe20:188/64,fe80::dc00:ff:fe20:187 ++ method=auto ++ may-fail=true ++ address1=2001:bc8:1210:232:dc00:ff:fe20:185/64 ++ dns=FEDC::1; ++ dns-search=lab;home; ++ ++ """ ++ ) ++ }, ++ "yaml": textwrap.dedent( ++ """\ ++ version: 2 ++ ethernets: ++ eth0: ++ dhcp4: true ++ dhcp6: true ++ nameservers: ++ search: [lab, home] ++ addresses: [8.8.8.8, "FEDC::1"] ++ routes: ++ - to: 169.254.42.42/32 ++ via: 62.210.0.1 ++ - via: fe80::dc00:ff:fe20:186 ++ to: ::/0 ++ - to: 169.254.42.43/32 ++ via: 62.210.0.2 ++ - via: fe80::dc00:ff:fe20:187 ++ to: fe80::dc00:ff:fe20:188 ++ addresses: ++ - 192.168.1.20/16 ++ - 2001:bc8:1210:232:dc00:ff:fe20:185/64 ++ """ ++ ), ++ }, ++ "v2-dns-no-if-ips": { ++ "expected_network_manager": { ++ "cloud-init-eth0.nmconnection": textwrap.dedent( ++ """\ ++ # Generated by cloud-init. Changes will be lost. ++ ++ [connection] ++ id=cloud-init eth0 ++ uuid=1dd9a779-d327-56e1-8454-c65e2556c12c ++ autoconnect-priority=120 ++ type=ethernet ++ interface-name=eth0 ++ ++ [user] ++ org.freedesktop.NetworkManager.origin=cloud-init ++ ++ [ethernet] ++ ++ [ipv4] ++ method=auto ++ may-fail=true ++ dns=8.8.8.8; ++ dns-search=lab;home; ++ ++ [ipv6] ++ method=auto ++ may-fail=true ++ dns=FEDC::1; ++ dns-search=lab;home; ++ ++ """ ++ ) ++ }, ++ "yaml": textwrap.dedent( ++ """\ ++ version: 2 ++ ethernets: ++ eth0: ++ dhcp4: true ++ dhcp6: true ++ nameservers: ++ search: [lab, home] ++ addresses: [8.8.8.8, "FEDC::1"] ++ """ ++ ), ++ }, ++ "v2-dns-no-dhcp": { ++ "expected_network_manager": { ++ "cloud-init-eth0.nmconnection": textwrap.dedent( ++ """\ ++ # Generated by cloud-init. Changes will be lost. ++ ++ [connection] ++ id=cloud-init eth0 ++ uuid=1dd9a779-d327-56e1-8454-c65e2556c12c ++ autoconnect-priority=120 ++ type=ethernet ++ interface-name=eth0 ++ ++ [user] ++ org.freedesktop.NetworkManager.origin=cloud-init ++ ++ [ethernet] ++ ++ """ ++ ) ++ }, ++ "yaml": textwrap.dedent( ++ """\ ++ version: 2 ++ ethernets: ++ eth0: ++ nameservers: ++ search: [lab, home] ++ addresses: [8.8.8.8, "FEDC::1"] ++ """ ++ ), ++ }, + } + + +@@ -6267,6 +6409,27 @@ class TestNetworkManagerRendering(CiTestCase): + entry[self.expected_name], self.expected_conf_d, found + ) + ++ def test_v2_mixed_routes(self): ++ entry = NETWORK_CONFIGS["v2-mixed-routes"] ++ found = self._render_and_read(network_config=yaml.load(entry["yaml"])) ++ self._compare_files_to_expected( ++ entry[self.expected_name], self.expected_conf_d, found ++ ) ++ ++ def test_v2_dns_no_ips(self): ++ entry = NETWORK_CONFIGS["v2-dns-no-if-ips"] ++ found = self._render_and_read(network_config=yaml.load(entry["yaml"])) ++ self._compare_files_to_expected( ++ entry[self.expected_name], self.expected_conf_d, found ++ ) ++ ++ def test_v2_dns_no_dhcp(self): ++ entry = NETWORK_CONFIGS["v2-dns-no-dhcp"] ++ found = self._render_and_read(network_config=yaml.load(entry["yaml"])) ++ self._compare_files_to_expected( ++ entry[self.expected_name], self.expected_conf_d, found ++ ) ++ + + @mock.patch( + "cloudinit.net.is_openvswitch_internal_interface", +-- +2.39.3 + diff --git a/SOURCES/ci-fix-Fall-back-to-cached-local-ds-if-no-valid-ds-foun.patch b/SOURCES/ci-fix-Fall-back-to-cached-local-ds-if-no-valid-ds-foun.patch new file mode 100644 index 0000000..364a100 --- /dev/null +++ b/SOURCES/ci-fix-Fall-back-to-cached-local-ds-if-no-valid-ds-foun.patch @@ -0,0 +1,156 @@ +From cf35040b46abb66c7239d156bd92c7267d7c40f7 Mon Sep 17 00:00:00 2001 +From: PengpengSun <40026211+PengpengSun@users.noreply.github.com> +Date: Fri, 29 Mar 2024 22:39:13 +0800 +Subject: [PATCH] fix: Fall back to cached local ds if no valid ds found + (#4997) + +RH-Author: Ani Sinha +RH-MergeRequest: 75: fix: Fall back to cached local ds if no valid ds found (#4997) +RH-Jira: RHEL-32846 +RH-Acked-by: Cathy Avery +RH-Acked-by: Miroslav Rezanina +RH-Commit: [1/1] 408c41fd8009255d98c31210ef936f2e68dfde75 (anisinha/cloud-init) + +Rebooting an instance which has finished VMware guest +customization with DataSourceVMware will load +DataSourceNone due to metadata is NOT available. + +This is mostly a re-post of PR#229, few differences are: +1. Let ds decide if fallback is allowed, not always fall back + to previous cached LOCAL ds. +2. No comparing instance-id of cached ds with previous instance-id + due to I think they are always identical. + +Fixes GH-3402 + +(cherry picked from commit 9929a00580d50afc60bf4e0fb9f2f39d4f797b4b) +Signed-off-by: Ani Sinha + +Conflicts: + cloudinit/sources/__init__.py + Conflicts because of changes in upstream source coming from + 30d5e9a3358f4cbaced ("refactor: Use _unpickle rather than hasattr() in sources") +--- + cloudinit/sources/DataSourceVMware.py | 14 +++++++++- + cloudinit/sources/__init__.py | 14 ++++++++++ + cloudinit/stages.py | 40 +++++++++++++++++---------- + 3 files changed, 53 insertions(+), 15 deletions(-) + +diff --git a/cloudinit/sources/DataSourceVMware.py b/cloudinit/sources/DataSourceVMware.py +index 1591121d..2d5d42eb 100644 +--- a/cloudinit/sources/DataSourceVMware.py ++++ b/cloudinit/sources/DataSourceVMware.py +@@ -197,7 +197,7 @@ class DataSourceVMware(sources.DataSource): + break + + if not self.data_access_method: +- LOG.error("failed to find a valid data access method") ++ LOG.debug("failed to find a valid data access method") + return False + + LOG.info("using data access method %s", self._get_subplatform()) +@@ -291,6 +291,18 @@ class DataSourceVMware(sources.DataSource): + self.metadata["instance-id"] = str(id_file.read()).rstrip().lower() + return self.metadata["instance-id"] + ++ def check_if_fallback_is_allowed(self): ++ if ( ++ self.data_access_method ++ and self.data_access_method == DATA_ACCESS_METHOD_IMC ++ and is_vmware_platform() ++ ): ++ LOG.debug( ++ "Cache fallback is allowed for : %s", self._get_subplatform() ++ ) ++ return True ++ return False ++ + def get_public_ssh_keys(self): + for key_name in ( + "public-keys-data", +diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py +index c207b5ed..453801be 100644 +--- a/cloudinit/sources/__init__.py ++++ b/cloudinit/sources/__init__.py +@@ -312,6 +312,10 @@ class DataSource(CloudInitPickleMixin, metaclass=abc.ABCMeta): + self.vendordata2_raw = None + if not hasattr(self, "skip_hotplug_detect"): + self.skip_hotplug_detect = False ++ ++ if not hasattr(self, "check_if_fallback_is_allowed"): ++ setattr(self, "check_if_fallback_is_allowed", lambda: False) ++ + if hasattr(self, "userdata") and self.userdata is not None: + # If userdata stores MIME data, on < python3.6 it will be + # missing the 'policy' attribute that exists on >=python3.6. +@@ -914,6 +918,16 @@ class DataSource(CloudInitPickleMixin, metaclass=abc.ABCMeta): + # quickly (local check only) if self.instance_id is still + return False + ++ def check_if_fallback_is_allowed(self): ++ """check_if_fallback_is_allowed() ++ Checks if a cached ds is allowed to be restored when no valid ds is ++ found in local mode by checking instance-id and searching valid data ++ through ds list. ++ ++ @return True if a ds allows fallback, False otherwise. ++ """ ++ return False ++ + @staticmethod + def _determine_dsmode(candidates, default=None, valid=None): + # return the first candidate that is non None, warn if not valid +diff --git a/cloudinit/stages.py b/cloudinit/stages.py +index 3b6405f5..0b795624 100644 +--- a/cloudinit/stages.py ++++ b/cloudinit/stages.py +@@ -353,20 +353,32 @@ class Init: + LOG.debug(myrep.description) + + if not ds: +- util.del_file(self.paths.instance_link) +- (cfg_list, pkg_list) = self._get_datasources() +- # Deep copy so that user-data handlers can not modify +- # (which will affect user-data handlers down the line...) +- (ds, dsname) = sources.find_source( +- self.cfg, +- self.distro, +- self.paths, +- copy.deepcopy(self.ds_deps), +- cfg_list, +- pkg_list, +- self.reporter, +- ) +- LOG.info("Loaded datasource %s - %s", dsname, ds) ++ try: ++ cfg_list, pkg_list = self._get_datasources() ++ # Deep copy so that user-data handlers can not modify ++ # (which will affect user-data handlers down the line...) ++ ds, dsname = sources.find_source( ++ self.cfg, ++ self.distro, ++ self.paths, ++ copy.deepcopy(self.ds_deps), ++ cfg_list, ++ pkg_list, ++ self.reporter, ++ ) ++ util.del_file(self.paths.instance_link) ++ LOG.info("Loaded datasource %s - %s", dsname, ds) ++ except sources.DataSourceNotFoundException as e: ++ if existing != "check": ++ raise e ++ ds = self._restore_from_cache() ++ if ds and ds.check_if_fallback_is_allowed(): ++ LOG.info( ++ "Restored fallback datasource from checked cache: %s", ++ ds, ++ ) ++ else: ++ raise e + self.datasource = ds + # Ensure we adjust our path members datasource + # now that we have one (thus allowing ipath to be used) +-- +2.39.3 + diff --git a/SOURCES/ci-fix-Undeprecate-network-in-schema-route-definition-5.patch b/SOURCES/ci-fix-Undeprecate-network-in-schema-route-definition-5.patch new file mode 100644 index 0000000..2892e06 --- /dev/null +++ b/SOURCES/ci-fix-Undeprecate-network-in-schema-route-definition-5.patch @@ -0,0 +1,42 @@ +From 332bb23bcfde801edf792e6c629ec350be07b952 Mon Sep 17 00:00:00 2001 +From: James Falcon +Date: Tue, 19 Mar 2024 14:24:11 -0500 +Subject: [PATCH 3/3] fix: Undeprecate 'network' in schema route definition + (#5072) + +RH-Author: Ani Sinha +RH-MergeRequest: 73: fix: Undeprecate 'network' in schema route definition (#5072) +RH-Jira: RHEL-29709 +RH-Acked-by: Emanuele Giuseppe Esposito +RH-Acked-by: Cathy Avery +RH-Commit: [1/1] 61c660be43fd25999bca0cfd66d7b2150fee5a14 (anisinha/cloud-init) + +It is passed through to our v1 schema from OpenStack network_data.json + +Fixes GH-5051 + +(cherry picked from commit ff40d1af8a6de3ee27937382ec4ceea931d80a88) +Signed-off-by: Ani Sinha +--- + cloudinit/config/schemas/schema-network-config-v1.json | 5 +---- + 1 file changed, 1 insertion(+), 4 deletions(-) + +diff --git a/cloudinit/config/schemas/schema-network-config-v1.json b/cloudinit/config/schemas/schema-network-config-v1.json +index 56dc27c9..64c492a4 100644 +--- a/cloudinit/config/schemas/schema-network-config-v1.json ++++ b/cloudinit/config/schemas/schema-network-config-v1.json +@@ -445,10 +445,7 @@ + }, + "network": { + "type": "string", +- "description": "IPv4 network address with CIDR netmask notation or IPv6 with prefix length. Alias for ``destination`` and only read when ``destination`` key is absent.", +- "deprecated": true, +- "deprecated_version": "23.3", +- "deprecated_description": "Use ``destination`` instead." ++ "description": "IPv4 network address with CIDR netmask notation or IPv6 with prefix length. Alias for ``destination`` and only read when ``destination`` key is absent. This exists for OpenStack support. OpenStack route definitions are passed through to v1 config and OpenStack's ``network_data.json`` uses ``network`` instead of ``destination``." + }, + "destination": { + "type": "string", +-- +2.39.3 + diff --git a/SPECS/cloud-init.spec b/SPECS/cloud-init.spec index 88400c8..cc54f3a 100644 --- a/SPECS/cloud-init.spec +++ b/SPECS/cloud-init.spec @@ -1,6 +1,6 @@ Name: cloud-init Version: 23.4 -Release: 7%{?dist} +Release: 7%{?dist}.3 Summary: Cloud instance init scripts License: ASL 2.0 or GPLv3 URL: http://launchpad.net/cloud-init @@ -23,6 +23,16 @@ Patch10: ci-Pin-pythes-8.0.0.patch Patch11: ci-fix-Add-types-to-network-v1-schema-4841.patch # For RHEL-28549 - [RHEL 9.4] cloud-init 23.4 returns 2 on recoverable errors instead of 0 Patch12: ci-Retain-exit-code-in-cloud-init-status-for-recoverabl.patch +# For RHEL-20964 - [rhel-9]cloud-init fails to configure DNS/search domains for network-config v1 +Patch13: ci-fix-Correct-v2-NetworkManager-route-rendering-4637.patch +# For RHEL-20964 - [rhel-9]cloud-init fails to configure DNS/search domains for network-config v1 +Patch14: ci-feat-apply-global-DNS-to-interfaces-in-network-manag.patch +# For RHEL-29709 - Suggest to backport patch ff40d1a to undeprecate 'network' in schema route definition +Patch15: ci-fix-Undeprecate-network-in-schema-route-definition-5.patch +# For RHEL-32846 - [cloud-init][ESXi]VMware datasource resets on every boot causing it to lose network configuration [rhel-9] +Patch16: ci-fix-Fall-back-to-cached-local-ds-if-no-valid-ds-foun.patch +# For RHEL-36255 - [rhel-9.5] DataSourceNoCloudNet not configurable via config files +Patch17: ci-fix-Always-use-single-datasource-if-specified-5098.patch BuildArch: noarch @@ -239,6 +249,25 @@ fi %config(noreplace) %{_sysconfdir}/rsyslog.d/21-cloudinit.conf %changelog +* Thu May 16 2024 Miroslav Rezanina - 23.4-7.3 +- ci-fix-Always-use-single-datasource-if-specified-5098.patch [RHEL-36255] +- Resolves: RHEL-36255 + ([rhel-9.5] DataSourceNoCloudNet not configurable via config files) + +* Mon Apr 22 2024 Miroslav Rezanina - 23.4-7.2 +- ci-fix-Fall-back-to-cached-local-ds-if-no-valid-ds-foun.patch [RHEL-32846] +- Resolves: RHEL-32846 + ([cloud-init][ESXi]VMware datasource resets on every boot causing it to lose network configuration [rhel-9]) + +* Mon Apr 08 2024 Miroslav Rezanina - 23.4-7.1 +- ci-fix-Correct-v2-NetworkManager-route-rendering-4637.patch [RHEL-20964] +- ci-feat-apply-global-DNS-to-interfaces-in-network-manag.patch [RHEL-20964] +- ci-fix-Undeprecate-network-in-schema-route-definition-5.patch [RHEL-29709] +- Resolves: RHEL-20964 + ([rhel-9]cloud-init fails to configure DNS/search domains for network-config v1) +- Resolves: RHEL-29709 + (Suggest to backport patch ff40d1a to undeprecate 'network' in schema route definition) + * Thu Mar 14 2024 Miroslav Rezanina - 23.4-7 - ci-Retain-exit-code-in-cloud-init-status-for-recoverabl.patch [RHEL-28549] - Resolves: RHEL-28549