diff --git a/0001-Add-initial-redhat-changes.patch b/0001-Add-initial-redhat-changes.patch index d6d47ca..d8bd385 100644 --- a/0001-Add-initial-redhat-changes.patch +++ b/0001-Add-initial-redhat-changes.patch @@ -1,7 +1,7 @@ From c4d66915520554adedff9be7396f877cd1a5525c Mon Sep 17 00:00:00 2001 From: Emanuele Giuseppe Esposito Date: Mon, 6 Mar 2023 16:37:20 +0100 -Subject: [PATCH 01/10] Add initial redhat changes +Subject: [PATCH] Add initial redhat changes Adding minimal set of changes necessary for successful build of the package on RHEL/CentOS 9 Stream koji. @@ -59,6 +59,3 @@ index 8684d003..edbb217d 100644 "network": {"renderers": None}, }, "vendor_data": {"enabled": True, "prefix": []}, --- -2.40.0 - diff --git a/0002-Do-not-write-NM_CONTROLLED-no-in-generated-interface.patch b/0002-Do-not-write-NM_CONTROLLED-no-in-generated-interface.patch index 5429b58..2d32e2c 100644 --- a/0002-Do-not-write-NM_CONTROLLED-no-in-generated-interface.patch +++ b/0002-Do-not-write-NM_CONTROLLED-no-in-generated-interface.patch @@ -1,8 +1,8 @@ From b3b96bff187e9d0bfcbfefd5fca05c61bd50d368 Mon Sep 17 00:00:00 2001 From: Eduardo Otubo Date: Fri, 7 May 2021 13:36:06 +0200 -Subject: [PATCH 02/10] Do not write NM_CONTROLLED=no in generated interface - config files +Subject: [PATCH] Do not write NM_CONTROLLED=no in generated interface config + files Conflicts 20.3: - Not appplying patch on cloudinit/net/sysconfig.py since it now has a @@ -278,6 +278,3 @@ index 056aaeb6..0f523ff8 100644 ONBOOT=yes TYPE=Ethernet USERCTL=no --- -2.40.0 - diff --git a/0003-Setting-highest-autoconnect-priority-for-network-scr.patch b/0003-Setting-highest-autoconnect-priority-for-network-scr.patch index 8d8e8bc..c9b7740 100644 --- a/0003-Setting-highest-autoconnect-priority-for-network-scr.patch +++ b/0003-Setting-highest-autoconnect-priority-for-network-scr.patch @@ -1,8 +1,7 @@ From c589da20eb92231ef08e10c9724e3e6c663e6ce2 Mon Sep 17 00:00:00 2001 From: Eduardo Otubo Date: Thu, 17 Feb 2022 15:32:35 +0100 -Subject: [PATCH 03/10] Setting highest autoconnect priority for - network-scripts +Subject: [PATCH] Setting highest autoconnect priority for network-scripts RH-Author: Eduardo Otubo RH-MergeRequest: 22: Setting highest autoconnect priority for network-scripts @@ -41,6 +40,3 @@ index a7dbe55b..4262cd48 100644 }, "suse": {"BOOTPROTO": "static", "STARTMODE": "auto"}, } --- -2.40.0 - diff --git a/0004-limit-permissions-on-def_log_file.patch b/0004-limit-permissions-on-def_log_file.patch index 0357daf..e69dd15 100644 --- a/0004-limit-permissions-on-def_log_file.patch +++ b/0004-limit-permissions-on-def_log_file.patch @@ -1,7 +1,7 @@ From dfff374f66904e84fb07ca157ba010fac6b5f1de Mon Sep 17 00:00:00 2001 From: Eduardo Otubo Date: Fri, 7 May 2021 13:36:08 +0200 -Subject: [PATCH 04/10] limit permissions on def_log_file +Subject: [PATCH] limit permissions on def_log_file This sets a default mode of 0600 on def_log_file, and makes this configurable via the def_log_file_mode option in cloud.cfg. @@ -67,6 +67,3 @@ index 15d788f3..b6d16c9c 100644 syslog_fix_perms: syslog:root # you can set passwords for a user or multiple users --- -2.40.0 - diff --git a/0005-Manual-revert-Use-Network-Manager-and-Netplan-as-def.patch b/0005-Manual-revert-Use-Network-Manager-and-Netplan-as-def.patch index 4a7615e..b1d3933 100644 --- a/0005-Manual-revert-Use-Network-Manager-and-Netplan-as-def.patch +++ b/0005-Manual-revert-Use-Network-Manager-and-Netplan-as-def.patch @@ -1,8 +1,8 @@ From ecae81f98ce230266eb99671b74534a4ede660f0 Mon Sep 17 00:00:00 2001 From: Emanuele Giuseppe Esposito Date: Fri, 10 Mar 2023 11:51:48 +0100 -Subject: [PATCH 05/10] Manual revert "Use Network-Manager and Netplan as - default renderers for RHEL and Fedora (#1465)" +Subject: [PATCH] Manual revert "Use Network-Manager and Netplan as default + renderers for RHEL and Fedora (#1465)" This reverts changes done in commit 7703aa98b. Done by hand because the doc file affected by that commit has changed. @@ -90,6 +90,3 @@ index ea331f1c..bc52afa5 100644 Network configuration tools =========================== --- -2.40.0 - diff --git a/0006-Revert-Add-native-NetworkManager-support-1224.patch b/0006-Revert-Add-native-NetworkManager-support-1224.patch index 88a00da..8941797 100644 --- a/0006-Revert-Add-native-NetworkManager-support-1224.patch +++ b/0006-Revert-Add-native-NetworkManager-support-1224.patch @@ -1,7 +1,7 @@ From b1dd14ffafad2d2ca84326c525962b2ca086b292 Mon Sep 17 00:00:00 2001 From: Ani Sinha Date: Wed, 22 Mar 2023 16:31:58 +0530 -Subject: [PATCH 06/10] Revert "Add native NetworkManager support (#1224)" +Subject: [PATCH] Revert "Add native NetworkManager support (#1224)" This reverts commit feda344e6cf9d37b09bc13cf333a717d1654c26c. @@ -1385,6 +1385,3 @@ index afd9056a..b735ea9e 100644 @patch("cloudinit.subp.subp", return_value=("", "")) def test_bring_up_interfaces( --- -2.40.0 - diff --git a/0007-rhel-make-sure-previous-hostname-file-ends-with-a-ne.patch b/0007-rhel-make-sure-previous-hostname-file-ends-with-a-ne.patch index 3800f60..7422b7e 100644 --- a/0007-rhel-make-sure-previous-hostname-file-ends-with-a-ne.patch +++ b/0007-rhel-make-sure-previous-hostname-file-ends-with-a-ne.patch @@ -1,8 +1,8 @@ From ac0cf308318d423162ce3b7be32dcbf88f20ff50 Mon Sep 17 00:00:00 2001 From: Ani Sinha Date: Tue, 4 Apr 2023 19:59:07 +0530 -Subject: [PATCH 07/10] rhel: make sure previous-hostname file ends with a new - line (#2108) +Subject: [PATCH] rhel: make sure previous-hostname file ends with a new line + (#2108) cloud-init strips new line from "/etc/hostname" on rhel distro when processing "/var/lib/cloud/data/previous-hostname". Although this does not pose a serious @@ -52,6 +52,3 @@ index d8cca015..457dacf4 100644 antonyc aswinrajamannar beantaxi --- -2.40.0 - diff --git a/0008-Don-t-change-permissions-of-netrules-target-2076.patch b/0008-Don-t-change-permissions-of-netrules-target-2076.patch index 9e8bd2d..d9bdf4f 100644 --- a/0008-Don-t-change-permissions-of-netrules-target-2076.patch +++ b/0008-Don-t-change-permissions-of-netrules-target-2076.patch @@ -1,7 +1,7 @@ From 34ef256dc614c7dcf5b04a431d410030e333d82b Mon Sep 17 00:00:00 2001 From: Emanuele Giuseppe Esposito Date: Mon, 17 Apr 2023 10:20:16 +0200 -Subject: [PATCH 08/10] Don't change permissions of netrules target (#2076) +Subject: [PATCH] Don't change permissions of netrules target (#2076) Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2182948 @@ -119,6 +119,3 @@ index e9fb0591..b1c89ce3 100644 ) def test_apply_network_config_ipv6_ub(self): --- -2.40.0 - diff --git a/0009-Make-user-vendor-data-sensitive-and-remove-log-permi.patch b/0009-Make-user-vendor-data-sensitive-and-remove-log-permi.patch index f577b9a..570a42b 100644 --- a/0009-Make-user-vendor-data-sensitive-and-remove-log-permi.patch +++ b/0009-Make-user-vendor-data-sensitive-and-remove-log-permi.patch @@ -1,8 +1,8 @@ From d092efe0f437ad149f6d6e3a9f8b816c0f5c1c2a Mon Sep 17 00:00:00 2001 From: James Falcon Date: Wed, 26 Apr 2023 15:11:55 -0500 -Subject: [PATCH 09/10] Make user/vendor data sensitive and remove log - permissions (#2144) +Subject: [PATCH] Make user/vendor data sensitive and remove log permissions + (#2144) Because user data and vendor data may contain sensitive information, this commit ensures that any user data or vendor data written to @@ -293,6 +293,3 @@ index 15a7e973..a61f9df9 100644 - assert mode == stat.S_IMODE(log_file.stat().mode) + assert 0o640 == stat.S_IMODE(log_file.stat().mode) --- -2.40.0 - diff --git a/0010-Do-not-generate-dsa-and-ed25519-key-types-when-crypt.patch b/0010-Do-not-generate-dsa-and-ed25519-key-types-when-crypt.patch index 51f7a09..d51a9dd 100644 --- a/0010-Do-not-generate-dsa-and-ed25519-key-types-when-crypt.patch +++ b/0010-Do-not-generate-dsa-and-ed25519-key-types-when-crypt.patch @@ -1,8 +1,8 @@ From 6bf6ceab79df97eb1c90b4df61f654bc0b2f598c Mon Sep 17 00:00:00 2001 From: Ani Sinha Date: Tue, 2 May 2023 20:35:45 +0530 -Subject: [PATCH 10/10] Do not generate dsa and ed25519 key types when crypto - FIPS mode is enabled (#2142) +Subject: [PATCH] Do not generate dsa and ed25519 key types when crypto FIPS + mode is enabled (#2142) DSA and ED25519 key types are not supported when FIPS is enabled in crypto. Check if FIPS has been enabled on the system and if so, do not generate those @@ -204,6 +204,3 @@ index 07142a86..17182d06 100644 class TestLoadYaml(helpers.CiTestCase): mydefault = "7b03a8ebace993d806255121073fed52" with_logs = True --- -2.40.0 - diff --git a/0011-Revert-Manual-revert-Use-Network-Manager-and-Netplan.patch b/0011-Revert-Manual-revert-Use-Network-Manager-and-Netplan.patch new file mode 100644 index 0000000..bce0c00 --- /dev/null +++ b/0011-Revert-Manual-revert-Use-Network-Manager-and-Netplan.patch @@ -0,0 +1,93 @@ +From 0b0632f6c084a8ce95b53cb5125dc0f4107e6968 Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Thu, 4 May 2023 15:34:43 +0530 +Subject: [PATCH] Revert "Manual revert "Use Network-Manager and Netplan as + default renderers for RHEL and Fedora (#1465)"" + +This reverts commit ecae81f98ce230266eb99671b74534a4ede660f0. + +This is patch 1 of the two patches that re-enables NM renderer. This change +can be ignored while rebasing to latest upstream. + +X-downstream-only: true +Signed-off-by: Ani Sinha +--- + cloudinit/net/renderers.py | 1 + + config/cloud.cfg.tmpl | 3 +++ + doc/rtd/reference/network-config.rst | 16 ++++++++++++++-- + 3 files changed, 18 insertions(+), 2 deletions(-) + +diff --git a/cloudinit/net/renderers.py b/cloudinit/net/renderers.py +index c92b9dcf..022ff938 100644 +--- a/cloudinit/net/renderers.py ++++ b/cloudinit/net/renderers.py +@@ -28,6 +28,7 @@ DEFAULT_PRIORITY = [ + "eni", + "sysconfig", + "netplan", ++ "network-manager", + "freebsd", + "netbsd", + "openbsd", +diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl +index 12f32c51..7238c102 100644 +--- a/config/cloud.cfg.tmpl ++++ b/config/cloud.cfg.tmpl +@@ -381,6 +381,9 @@ system_info: + {% elif variant in ["dragonfly"] %} + network: + renderers: ['freebsd'] ++{% elif variant in ["fedora"] or is_rhel %} ++ network: ++ renderers: ['netplan', 'network-manager', 'networkd', 'sysconfig', 'eni'] + {% elif variant == "openmandriva" %} + network: + renderers: ['network-manager', 'networkd'] +diff --git a/doc/rtd/reference/network-config.rst b/doc/rtd/reference/network-config.rst +index bc52afa5..ea331f1c 100644 +--- a/doc/rtd/reference/network-config.rst ++++ b/doc/rtd/reference/network-config.rst +@@ -176,6 +176,16 @@ this state, ``cloud-init`` delegates rendering of the configuration to + distro-supported formats. The following ``renderers`` are supported in + ``cloud-init``: + ++NetworkManager ++-------------- ++ ++`NetworkManager`_ is the standard Linux network configuration tool suite. It ++supports a wide range of networking setups. Configuration is typically stored ++in :file:`/etc/NetworkManager`. ++ ++It is the default for a number of Linux distributions; notably Fedora, ++CentOS/RHEL, and their derivatives. ++ + ENI + --- + +@@ -213,6 +223,7 @@ preference) is as follows: + - ENI + - Sysconfig + - Netplan ++- NetworkManager + - FreeBSD + - NetBSD + - OpenBSD +@@ -223,6 +234,7 @@ preference) is as follows: + + - **ENI**: using ``ifup``, ``ifdown`` to manage device setup/teardown + - **Netplan**: using ``netplan apply`` to manage device setup/teardown ++- **NetworkManager**: using ``nmcli`` to manage device setup/teardown + - **Networkd**: using ``ip`` to manage device setup/teardown + + When applying the policy, ``cloud-init`` checks if the current instance has the +@@ -232,8 +244,8 @@ supplying an updated configuration in cloud-config. :: + + system_info: + network: +- renderers: ['netplan', 'eni', 'sysconfig', 'freebsd', 'netbsd', 'openbsd'] +- activators: ['eni', 'netplan', 'networkd'] ++ renderers: ['netplan', 'network-manager', 'eni', 'sysconfig', 'freebsd', 'netbsd', 'openbsd'] ++ activators: ['eni', 'netplan', 'network-manager', 'networkd'] + + Network configuration tools + =========================== diff --git a/0012-Revert-Revert-Add-native-NetworkManager-support-1224.patch b/0012-Revert-Revert-Add-native-NetworkManager-support-1224.patch new file mode 100644 index 0000000..2ae66c7 --- /dev/null +++ b/0012-Revert-Revert-Add-native-NetworkManager-support-1224.patch @@ -0,0 +1,1391 @@ +From 5822f72230a58d18dae8c3b76c02d4bf7a149e56 Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Thu, 4 May 2023 15:39:17 +0530 +Subject: [PATCH] Revert "Revert "Add native NetworkManager support (#1224)"" + +This reverts commit b1dd14ffafad2d2ca84326c525962b2ca086b292. + +This is patch 2 of the two patches that re-enables NM renderer. This change can +be ignored while rebasing to latest upstream. + +X-downstream-only: true +Signed-off-by: Ani Sinha +--- + cloudinit/cmd/devel/net_convert.py | 14 +- + cloudinit/net/activators.py | 25 +- + cloudinit/net/network_manager.py | 393 ++++++++++++++++ + cloudinit/net/renderers.py | 2 + + cloudinit/net/sysconfig.py | 42 +- + tests/unittests/test_net.py | 597 ++++++++++++++++++++----- + tests/unittests/test_net_activators.py | 11 +- + 7 files changed, 923 insertions(+), 161 deletions(-) + create mode 100644 cloudinit/net/network_manager.py + +diff --git a/cloudinit/cmd/devel/net_convert.py b/cloudinit/cmd/devel/net_convert.py +index 1a0a31ac..eee49860 100755 +--- a/cloudinit/cmd/devel/net_convert.py ++++ b/cloudinit/cmd/devel/net_convert.py +@@ -10,7 +10,14 @@ import sys + import yaml + + from cloudinit import distros, log, safeyaml +-from cloudinit.net import eni, netplan, network_state, networkd, sysconfig ++from cloudinit.net import ( ++ eni, ++ netplan, ++ network_manager, ++ network_state, ++ networkd, ++ sysconfig, ++) + from cloudinit.sources import DataSourceAzure as azure + from cloudinit.sources.helpers import openstack + from cloudinit.sources.helpers.vmware.imc import guestcust_util +@@ -77,7 +84,7 @@ def get_parser(parser=None): + parser.add_argument( + "-O", + "--output-kind", +- choices=["eni", "netplan", "networkd", "sysconfig"], ++ choices=["eni", "netplan", "networkd", "sysconfig", "network-manager"], + required=True, + help="The network config format to emit", + ) +@@ -150,6 +157,9 @@ def handle_args(name, args): + elif args.output_kind == "sysconfig": + r_cls = sysconfig.Renderer + config = distro.renderer_configs.get("sysconfig") ++ elif args.output_kind == "network-manager": ++ r_cls = network_manager.Renderer ++ config = distro.renderer_configs.get("network-manager") + else: + raise RuntimeError("Invalid output_kind") + +diff --git a/cloudinit/net/activators.py b/cloudinit/net/activators.py +index d9a8c4d7..7d11a02c 100644 +--- a/cloudinit/net/activators.py ++++ b/cloudinit/net/activators.py +@@ -1,15 +1,14 @@ + # This file is part of cloud-init. See LICENSE file for license information. + import logging +-import os + from abc import ABC, abstractmethod + from typing import Dict, Iterable, List, Optional, Type, Union + + from cloudinit import subp, util + from cloudinit.net.eni import available as eni_available + from cloudinit.net.netplan import available as netplan_available ++from cloudinit.net.network_manager import available as nm_available + from cloudinit.net.network_state import NetworkState + from cloudinit.net.networkd import available as networkd_available +-from cloudinit.net.sysconfig import NM_CFG_FILE + + LOG = logging.getLogger(__name__) + +@@ -124,20 +123,24 @@ class IfUpDownActivator(NetworkActivator): + class NetworkManagerActivator(NetworkActivator): + @staticmethod + def available(target=None) -> bool: +- """Return true if network manager can be used on this system.""" +- config_present = os.path.isfile( +- subp.target_path(target, path=NM_CFG_FILE) +- ) +- nmcli_present = subp.which("nmcli", target=target) +- return config_present and bool(nmcli_present) ++ """Return true if NetworkManager can be used on this system.""" ++ return nm_available(target=target) + + @staticmethod + def bring_up_interface(device_name: str) -> bool: +- """Bring up interface using nmcli. ++ """Bring up connection using nmcli. + + Return True is successful, otherwise return False + """ +- cmd = ["nmcli", "connection", "up", "ifname", device_name] ++ from cloudinit.net.network_manager import conn_filename ++ ++ filename = conn_filename(device_name) ++ cmd = ["nmcli", "connection", "load", filename] ++ if _alter_interface(cmd, device_name): ++ cmd = ["nmcli", "connection", "up", "filename", filename] ++ else: ++ _alter_interface(["nmcli", "connection", "reload"], device_name) ++ cmd = ["nmcli", "connection", "up", "ifname", device_name] + return _alter_interface(cmd, device_name) + + @staticmethod +@@ -146,7 +149,7 @@ class NetworkManagerActivator(NetworkActivator): + + Return True is successful, otherwise return False + """ +- cmd = ["nmcli", "connection", "down", device_name] ++ cmd = ["nmcli", "device", "disconnect", device_name] + return _alter_interface(cmd, device_name) + + +diff --git a/cloudinit/net/network_manager.py b/cloudinit/net/network_manager.py +new file mode 100644 +index 00000000..53763d15 +--- /dev/null ++++ b/cloudinit/net/network_manager.py +@@ -0,0 +1,393 @@ ++# Copyright 2022 Red Hat, Inc. ++# ++# Author: Lubomir Rintel ++# Fixes and suggestions contributed by James Falcon, Neal Gompa, ++# Zbigniew Jędrzejewski-Szmek and Emanuele Giuseppe Esposito. ++# ++# This file is part of cloud-init. See LICENSE file for license information. ++ ++import configparser ++import io ++import itertools ++import os ++import uuid ++from typing import Optional ++ ++from cloudinit import log as logging ++from cloudinit import subp, util ++from cloudinit.net import is_ipv6_address, renderer, subnet_is_ipv6 ++from cloudinit.net.network_state import NetworkState ++ ++NM_RUN_DIR = "/etc/NetworkManager" ++NM_LIB_DIR = "/usr/lib/NetworkManager" ++NM_CFG_FILE = "/etc/NetworkManager/NetworkManager.conf" ++LOG = logging.getLogger(__name__) ++ ++ ++class NMConnection: ++ """Represents a NetworkManager connection profile.""" ++ ++ def __init__(self, con_id): ++ """ ++ Initializes the connection with some very basic properties, ++ notably the UUID so that the connection can be referred to. ++ """ ++ ++ # Chosen by fair dice roll ++ CI_NM_UUID = uuid.UUID("a3924cb8-09e0-43e9-890b-77972a800108") ++ ++ self.config = configparser.ConfigParser() ++ # Identity option name mapping, to achieve case sensitivity ++ self.config.optionxform = str ++ ++ self.config["connection"] = { ++ "id": f"cloud-init {con_id}", ++ "uuid": str(uuid.uuid5(CI_NM_UUID, con_id)), ++ } ++ ++ # This is not actually used anywhere, but may be useful in future ++ self.config["user"] = { ++ "org.freedesktop.NetworkManager.origin": "cloud-init" ++ } ++ ++ def _set_default(self, section, option, value): ++ """ ++ Sets a property unless it's already set, ensuring the section ++ exists. ++ """ ++ ++ if not self.config.has_section(section): ++ self.config[section] = {} ++ if not self.config.has_option(section, option): ++ self.config[section][option] = value ++ ++ def _set_ip_method(self, family, subnet_type): ++ """ ++ Ensures there's appropriate [ipv4]/[ipv6] for given family ++ appropriate for given configuration type ++ """ ++ ++ method_map = { ++ "static": "manual", ++ "dhcp6": "auto", ++ "ipv6_slaac": "auto", ++ "ipv6_dhcpv6-stateless": "auto", ++ "ipv6_dhcpv6-stateful": "auto", ++ "dhcp4": "auto", ++ "dhcp": "auto", ++ } ++ ++ # Ensure we got an [ipvX] section ++ self._set_default(family, "method", "disabled") ++ ++ try: ++ method = method_map[subnet_type] ++ except KeyError: ++ # What else can we do ++ method = "auto" ++ self.config[family]["may-fail"] = "true" ++ ++ # Make sure we don't "downgrade" the method in case ++ # we got conflicting subnets (e.g. static along with dhcp) ++ if self.config[family]["method"] == "dhcp": ++ return ++ if self.config[family]["method"] == "auto" and method == "manual": ++ return ++ ++ self.config[family]["method"] = method ++ self._set_default(family, "may-fail", "false") ++ ++ def _add_numbered(self, section, key_prefix, value): ++ """ ++ Adds a numbered property, such as address or route, ensuring ++ the appropriate value gets used for . ++ """ ++ ++ for index in itertools.count(1): ++ key = f"{key_prefix}{index}" ++ if not self.config.has_option(section, key): ++ self.config[section][key] = value ++ break ++ ++ def _add_address(self, family, subnet): ++ """ ++ Adds an ipv[46]address property. ++ """ ++ ++ value = subnet["address"] + "/" + str(subnet["prefix"]) ++ self._add_numbered(family, "address", value) ++ ++ def _add_route(self, family, route): ++ """ ++ Adds a ipv[46].route property. ++ """ ++ ++ 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): ++ """ ++ 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 + ";" ++ ++ def _add_dns_search(self, family, dns_search): ++ """ ++ 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) + ";" ++ ) ++ ++ def con_uuid(self): ++ """ ++ Returns the connection UUID ++ """ ++ return self.config["connection"]["uuid"] ++ ++ def valid(self): ++ """ ++ Can this be serialized into a meaningful connection profile? ++ """ ++ return self.config.has_option("connection", "type") ++ ++ @staticmethod ++ def mac_addr(addr): ++ """ ++ Sanitize a MAC address. ++ """ ++ return addr.replace("-", ":").upper() ++ ++ def render_interface(self, iface, renderer): ++ """ ++ Integrate information from network state interface information ++ into the connection. Most of the work is done here. ++ """ ++ ++ # Initialize type & connectivity ++ _type_map = { ++ "physical": "ethernet", ++ "vlan": "vlan", ++ "bond": "bond", ++ "bridge": "bridge", ++ "infiniband": "infiniband", ++ "loopback": None, ++ } ++ ++ if_type = _type_map[iface["type"]] ++ if if_type is None: ++ return ++ if "bond-master" in iface: ++ slave_type = "bond" ++ else: ++ slave_type = None ++ ++ self.config["connection"]["type"] = if_type ++ if slave_type is not None: ++ self.config["connection"]["slave-type"] = slave_type ++ self.config["connection"]["master"] = renderer.con_ref( ++ iface[slave_type + "-master"] ++ ) ++ ++ # Add type specific-section ++ self.config[if_type] = {} ++ ++ # These are the interface properties that map nicely ++ # to NetworkManager properties ++ _prop_map = { ++ "bond": { ++ "mode": "bond-mode", ++ "miimon": "bond_miimon", ++ "xmit_hash_policy": "bond-xmit-hash-policy", ++ "num_grat_arp": "bond-num-grat-arp", ++ "downdelay": "bond-downdelay", ++ "updelay": "bond-updelay", ++ "fail_over_mac": "bond-fail-over-mac", ++ "primary_reselect": "bond-primary-reselect", ++ "primary": "bond-primary", ++ }, ++ "bridge": { ++ "stp": "bridge_stp", ++ "priority": "bridge_bridgeprio", ++ }, ++ "vlan": { ++ "id": "vlan_id", ++ }, ++ "ethernet": {}, ++ "infiniband": {}, ++ } ++ ++ device_mtu = iface["mtu"] ++ ipv4_mtu = None ++ ++ # Deal with Layer 3 configuration ++ for subnet in iface["subnets"]: ++ family = "ipv6" if subnet_is_ipv6(subnet) else "ipv4" ++ ++ self._set_ip_method(family, subnet["type"]) ++ if "address" in subnet: ++ self._add_address(family, subnet) ++ 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: ++ for nameserver in subnet["dns_nameservers"]: ++ self._add_nameserver(nameserver) ++ if "dns_search" in subnet: ++ self._add_dns_search(family, subnet["dns_search"]) ++ if family == "ipv4" and "mtu" in subnet: ++ ipv4_mtu = subnet["mtu"] ++ ++ if ipv4_mtu is None: ++ ipv4_mtu = device_mtu ++ if not ipv4_mtu == device_mtu: ++ LOG.warning( ++ "Network config: ignoring %s device-level mtu:%s" ++ " because ipv4 subnet-level mtu:%s provided.", ++ iface["name"], ++ device_mtu, ++ ipv4_mtu, ++ ) ++ ++ # Parse type-specific properties ++ for nm_prop, key in _prop_map[if_type].items(): ++ if key not in iface: ++ continue ++ if iface[key] is None: ++ continue ++ if isinstance(iface[key], bool): ++ self.config[if_type][nm_prop] = ( ++ "true" if iface[key] else "false" ++ ) ++ else: ++ self.config[if_type][nm_prop] = str(iface[key]) ++ ++ # These ones need special treatment ++ if if_type == "ethernet": ++ if iface["wakeonlan"] is True: ++ # NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC ++ self.config["ethernet"]["wake-on-lan"] = str(0x40) ++ if ipv4_mtu is not None: ++ self.config["ethernet"]["mtu"] = str(ipv4_mtu) ++ if iface["mac_address"] is not None: ++ self.config["ethernet"]["mac-address"] = self.mac_addr( ++ iface["mac_address"] ++ ) ++ if if_type == "vlan" and "vlan-raw-device" in iface: ++ self.config["vlan"]["parent"] = renderer.con_ref( ++ iface["vlan-raw-device"] ++ ) ++ if if_type == "bridge": ++ # Bridge is ass-backwards compared to bond ++ for port in iface["bridge_ports"]: ++ port = renderer.get_conn(port) ++ port._set_default("connection", "slave-type", "bridge") ++ port._set_default("connection", "master", self.con_uuid()) ++ if iface["mac_address"] is not None: ++ self.config["bridge"]["mac-address"] = self.mac_addr( ++ iface["mac_address"] ++ ) ++ if if_type == "infiniband" and ipv4_mtu is not None: ++ self.config["infiniband"]["transport-mode"] = "datagram" ++ self.config["infiniband"]["mtu"] = str(ipv4_mtu) ++ if iface["mac_address"] is not None: ++ self.config["infiniband"]["mac-address"] = self.mac_addr( ++ iface["mac_address"] ++ ) ++ ++ # Finish up ++ if if_type == "bridge" or not self.config.has_option( ++ if_type, "mac-address" ++ ): ++ self.config["connection"]["interface-name"] = iface["name"] ++ ++ def dump(self): ++ """ ++ Stringify. ++ """ ++ ++ buf = io.StringIO() ++ self.config.write(buf, space_around_delimiters=False) ++ header = "# Generated by cloud-init. Changes will be lost.\n\n" ++ return header + buf.getvalue() ++ ++ ++class Renderer(renderer.Renderer): ++ """Renders network information in a NetworkManager keyfile format.""" ++ ++ def __init__(self, config=None): ++ self.connections = {} ++ ++ def get_conn(self, con_id): ++ return self.connections[con_id] ++ ++ def con_ref(self, con_id): ++ if con_id in self.connections: ++ return self.connections[con_id].con_uuid() ++ else: ++ # Well, what can we do... ++ return con_id ++ ++ def render_network_state( ++ self, ++ network_state: NetworkState, ++ templates: Optional[dict] = None, ++ target=None, ++ ) -> None: ++ # First pass makes sure there's NMConnections for all known ++ # interfaces that have UUIDs that can be linked to from related ++ # interfaces ++ for iface in network_state.iter_interfaces(): ++ self.connections[iface["name"]] = NMConnection(iface["name"]) ++ ++ # Now render the actual interface configuration ++ for iface in network_state.iter_interfaces(): ++ conn = self.connections[iface["name"]] ++ conn.render_interface(iface, self) ++ ++ # And finally write the files ++ for con_id, conn in self.connections.items(): ++ if not conn.valid(): ++ continue ++ name = conn_filename(con_id, target) ++ util.write_file(name, conn.dump(), 0o600) ++ ++ ++def conn_filename(con_id, target=None): ++ target_con_dir = subp.target_path(target, NM_RUN_DIR) ++ con_file = f"cloud-init-{con_id}.nmconnection" ++ return f"{target_con_dir}/system-connections/{con_file}" ++ ++ ++def available(target=None): ++ # TODO: Move `uses_systemd` to a more appropriate location ++ # It is imported here to avoid circular import ++ from cloudinit.distros import uses_systemd ++ ++ config_present = os.path.isfile(subp.target_path(target, path=NM_CFG_FILE)) ++ nmcli_present = subp.which("nmcli", target=target) ++ service_active = True ++ if uses_systemd(): ++ try: ++ subp.subp(["systemctl", "is-enabled", "NetworkManager.service"]) ++ except subp.ProcessExecutionError: ++ service_active = False ++ ++ return config_present and bool(nmcli_present) and service_active ++ ++ ++# vi: ts=4 expandtab +diff --git a/cloudinit/net/renderers.py b/cloudinit/net/renderers.py +index 022ff938..fcf7feba 100644 +--- a/cloudinit/net/renderers.py ++++ b/cloudinit/net/renderers.py +@@ -8,6 +8,7 @@ from cloudinit.net import ( + freebsd, + netbsd, + netplan, ++ network_manager, + networkd, + openbsd, + renderer, +@@ -19,6 +20,7 @@ NAME_TO_RENDERER = { + "freebsd": freebsd, + "netbsd": netbsd, + "netplan": netplan, ++ "network-manager": network_manager, + "networkd": networkd, + "openbsd": openbsd, + "sysconfig": sysconfig, +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index e08c0c69..b8786fb7 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -6,8 +6,6 @@ import os + import re + from typing import Mapping, Optional + +-from configobj import ConfigObj +- + from cloudinit import log as logging + from cloudinit import subp, util + from cloudinit.distros.parsers import networkmanager_conf, resolv_conf +@@ -37,7 +35,7 @@ KNOWN_DISTROS = [ + "TencentOS", + "virtuozzo", + ] +-NM_CFG_FILE = "/etc/NetworkManager/NetworkManager.conf" ++ + + def _make_header(sep="#"): + lines = [ +@@ -68,26 +66,7 @@ def _quote_value(value): + return value + + +- +-def enable_ifcfg_rh(path): +- """Add ifcfg-rh to NetworkManager.cfg plugins if main section is present""" +- config = ConfigObj(path) +- if "main" in config: +- if "plugins" in config["main"]: +- if "ifcfg-rh" in config["main"]["plugins"]: +- return +- else: +- config["main"]["plugins"] = [] +- +- if isinstance(config["main"]["plugins"], list): +- config["main"]["plugins"].append("ifcfg-rh") +- else: +- config["main"]["plugins"] = [config["main"]["plugins"], "ifcfg-rh"] +- config.write() +- LOG.debug("Enabled ifcfg-rh NetworkManager plugins") +- +- +-class ConfigMap(object): ++class ConfigMap: + """Sysconfig like dictionary object.""" + + # Why does redhat prefer yes/no to true/false?? +@@ -1040,8 +1019,6 @@ class Renderer(renderer.Renderer): + mode=file_mode, + preserve_mode=True, + ) +- if available_nm(target=target): +- enable_ifcfg_rh(subp.target_path(target, path=NM_CFG_FILE)) + + sysconfig_path = subp.target_path(target, templates.get("control")) + # Distros configuring /etc/sysconfig/network as a file e.g. Centos +@@ -1080,14 +1057,9 @@ def _supported_vlan_names(rdev, vid): + + + def available(target=None): +- sysconfig = available_sysconfig(target=target) +- nm = available_nm(target=target) +- return util.system_info()["variant"] in KNOWN_DISTROS and any( +- [nm, sysconfig] +- ) +- ++ if not util.system_info()["variant"] in KNOWN_DISTROS: ++ return False + +-def available_sysconfig(target=None): + expected = ["ifup", "ifdown"] + search = ["/sbin", "/usr/sbin"] + for p in expected: +@@ -1104,10 +1076,4 @@ def available_sysconfig(target=None): + return False + + +-def available_nm(target=None): +- if not os.path.isfile(subp.target_path(target, path=NM_CFG_FILE)): +- return False +- return True +- +- + # vi: ts=4 expandtab +diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py +index 4434b350..0f523ff8 100644 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -23,6 +23,7 @@ from cloudinit.net import ( + mask_and_ipv4_to_bcast_addr, + natural_sort_key, + netplan, ++ network_manager, + network_state, + networkd, + renderers, +@@ -616,6 +617,37 @@ dns = none + ), + ), + ], ++ "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 ++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 ++ ++""".lstrip(), ++ ), ++ ], + }, + { + "in_data": { +@@ -1078,6 +1110,50 @@ NETWORK_CONFIGS = { + USERCTL=no""" + ), + }, ++ "expected_network_manager": { ++ "cloud-init-eth1.nmconnection": textwrap.dedent( ++ """\ ++ # Generated by cloud-init. Changes will be lost. ++ ++ [connection] ++ id=cloud-init eth1 ++ uuid=3c50eb47-7260-5a6d-801d-bd4f587d6b58 ++ type=ethernet ++ ++ [user] ++ org.freedesktop.NetworkManager.origin=cloud-init ++ ++ [ethernet] ++ mac-address=CF:D6:AF:48:E8:80 ++ ++ """ ++ ), ++ "cloud-init-eth99.nmconnection": textwrap.dedent( ++ """\ ++ # Generated by cloud-init. Changes will be lost. ++ ++ [connection] ++ id=cloud-init eth99 ++ uuid=b1b88000-1f03-5360-8377-1a2205efffb4 ++ type=ethernet ++ ++ [user] ++ org.freedesktop.NetworkManager.origin=cloud-init ++ ++ [ethernet] ++ mac-address=C0:D6:9F:2C:E8:80 ++ ++ [ipv4] ++ method=auto ++ may-fail=false ++ address1=192.168.21.3/24 ++ route1=0.0.0.0/0,65.61.151.37 ++ dns=8.8.8.8;8.8.4.4; ++ dns-search=barley.maas;sach.maas; ++ ++ """ ++ ), ++ }, + "yaml": textwrap.dedent( + """ + version: 1 +@@ -1883,6 +1959,29 @@ NETWORK_CONFIGS = { + """ + ), + }, ++ "expected_network_manager": { ++ "cloud-init-iface0.nmconnection": textwrap.dedent( ++ """\ ++ # Generated by cloud-init. Changes will be lost. ++ ++ [connection] ++ id=cloud-init iface0 ++ uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70 ++ type=ethernet ++ interface-name=iface0 ++ ++ [user] ++ org.freedesktop.NetworkManager.origin=cloud-init ++ ++ [ethernet] ++ ++ [ipv4] ++ method=auto ++ may-fail=false ++ ++ """ ++ ), ++ }, + "yaml_v2": textwrap.dedent( + """\ + version: 2 +@@ -1936,6 +2035,30 @@ NETWORK_CONFIGS = { + """ + ), + }, ++ "expected_network_manager": { ++ "cloud-init-iface0.nmconnection": textwrap.dedent( ++ """\ ++ # Generated by cloud-init. Changes will be lost. ++ ++ [connection] ++ id=cloud-init iface0 ++ uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70 ++ type=ethernet ++ interface-name=iface0 ++ ++ [user] ++ org.freedesktop.NetworkManager.origin=cloud-init ++ ++ [ethernet] ++ wake-on-lan=64 ++ ++ [ipv4] ++ method=auto ++ may-fail=false ++ ++ """ ++ ), ++ }, + "yaml_v2": textwrap.dedent( + """\ + version: 2 +@@ -2969,8 +3092,8 @@ iface bond0 inet6 static + - to: 2001:67c:1562:8007::1/64 + via: 2001:67c:1562:8007::aac:40b2 + - metric: 10000 +- to: 3001:67c:1562:8007::1/64 +- via: 3001:67c:1562:8007::aac:40b2 ++ to: 3001:67c:15:8007::1/64 ++ via: 3001:67c:15:8007::aac:40b2 + """ + ), + "expected_netplan-v2": textwrap.dedent( +@@ -3002,8 +3125,8 @@ iface bond0 inet6 static + - to: 2001:67c:1562:8007::1/64 + via: 2001:67c:1562:8007::aac:40b2 + - metric: 10000 +- to: 3001:67c:1562:8007::1/64 +- via: 3001:67c:1562:8007::aac:40b2 ++ to: 3001:67c:15:8007::1/64 ++ via: 3001:67c:15:8007::aac:40b2 + ethernets: + eth0: + match: +@@ -3651,6 +3774,73 @@ iface bond0 inet6 static + """ + ), + }, ++ "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 ++ type=ethernet ++ ++ [user] ++ org.freedesktop.NetworkManager.origin=cloud-init ++ ++ [ethernet] ++ mac-address=52:54:00:12:34:00 ++ ++ [ipv4] ++ method=manual ++ may-fail=false ++ address1=192.168.1.2/24 ++ ++ """ ++ ), ++ "cloud-init-eth1.nmconnection": textwrap.dedent( ++ """\ ++ # Generated by cloud-init. Changes will be lost. ++ ++ [connection] ++ id=cloud-init eth1 ++ uuid=3c50eb47-7260-5a6d-801d-bd4f587d6b58 ++ type=ethernet ++ ++ [user] ++ org.freedesktop.NetworkManager.origin=cloud-init ++ ++ [ethernet] ++ mtu=1480 ++ mac-address=52:54:00:12:34:AA ++ ++ [ipv4] ++ method=auto ++ may-fail=true ++ ++ """ ++ ), ++ "cloud-init-eth2.nmconnection": textwrap.dedent( ++ """\ ++ # Generated by cloud-init. Changes will be lost. ++ ++ [connection] ++ id=cloud-init eth2 ++ uuid=5559a242-3421-5fdd-896e-9cb8313d5804 ++ type=ethernet ++ ++ [user] ++ org.freedesktop.NetworkManager.origin=cloud-init ++ ++ [ethernet] ++ mac-address=52:54:00:12:34:FF ++ ++ [ipv4] ++ method=auto ++ may-fail=true ++ ++ """ ++ ), ++ }, + }, + "v2-dev-name-via-mac-lookup": { + "expected_sysconfig_rhel": { +@@ -4149,7 +4339,6 @@ class TestRhelSysConfigRendering(CiTestCase): + + with_logs = True + +- nm_cfg_file = "/etc/NetworkManager/NetworkManager.conf" + scripts_dir = "/etc/sysconfig/network-scripts" + header = ( + "# Created by cloud-init on instance boot automatically, " +@@ -4724,78 +4913,6 @@ USERCTL=no + self._compare_files_to_expected(entry[self.expected_name], found) + self._assert_headers(found) + +- def test_check_ifcfg_rh(self): +- """ifcfg-rh plugin is added NetworkManager.conf if conf present.""" +- render_dir = self.tmp_dir() +- nm_cfg = subp.target_path(render_dir, path=self.nm_cfg_file) +- util.ensure_dir(os.path.dirname(nm_cfg)) +- +- # write a template nm.conf, note plugins is a list here +- with open(nm_cfg, "w") as fh: +- fh.write("# test_check_ifcfg_rh\n[main]\nplugins=foo,bar\n") +- self.assertTrue(os.path.exists(nm_cfg)) +- +- # render and read +- entry = NETWORK_CONFIGS["small"] +- found = self._render_and_read( +- network_config=yaml.load(entry["yaml"]), dir=render_dir +- ) +- self._compare_files_to_expected(entry[self.expected_name], found) +- self._assert_headers(found) +- +- # check ifcfg-rh is in the 'plugins' list +- config = sysconfig.ConfigObj(nm_cfg) +- self.assertIn("ifcfg-rh", config["main"]["plugins"]) +- +- def test_check_ifcfg_rh_plugins_string(self): +- """ifcfg-rh plugin is append when plugins is a string.""" +- render_dir = self.tmp_path("render") +- os.makedirs(render_dir) +- nm_cfg = subp.target_path(render_dir, path=self.nm_cfg_file) +- util.ensure_dir(os.path.dirname(nm_cfg)) +- +- # write a template nm.conf, note plugins is a value here +- util.write_file(nm_cfg, "# test_check_ifcfg_rh\n[main]\nplugins=foo\n") +- +- # render and read +- entry = NETWORK_CONFIGS["small"] +- found = self._render_and_read( +- network_config=yaml.load(entry["yaml"]), dir=render_dir +- ) +- self._compare_files_to_expected(entry[self.expected_name], found) +- self._assert_headers(found) +- +- # check raw content has plugin +- nm_file_content = util.load_file(nm_cfg) +- self.assertIn("ifcfg-rh", nm_file_content) +- +- # check ifcfg-rh is in the 'plugins' list +- config = sysconfig.ConfigObj(nm_cfg) +- self.assertIn("ifcfg-rh", config["main"]["plugins"]) +- +- def test_check_ifcfg_rh_plugins_no_plugins(self): +- """enable_ifcfg_plugin creates plugins value if missing.""" +- render_dir = self.tmp_path("render") +- os.makedirs(render_dir) +- nm_cfg = subp.target_path(render_dir, path=self.nm_cfg_file) +- util.ensure_dir(os.path.dirname(nm_cfg)) +- +- # write a template nm.conf, note plugins is missing +- util.write_file(nm_cfg, "# test_check_ifcfg_rh\n[main]\n") +- self.assertTrue(os.path.exists(nm_cfg)) +- +- # render and read +- entry = NETWORK_CONFIGS["small"] +- found = self._render_and_read( +- network_config=yaml.load(entry["yaml"]), dir=render_dir +- ) +- self._compare_files_to_expected(entry[self.expected_name], found) +- self._assert_headers(found) +- +- # check ifcfg-rh is in the 'plugins' list +- config = sysconfig.ConfigObj(nm_cfg) +- self.assertIn("ifcfg-rh", config["main"]["plugins"]) +- + def test_netplan_dhcp_false_disable_dhcp_in_state(self): + """netplan config with dhcp[46]: False should not add dhcp in state""" + net_config = yaml.load(NETPLAN_DHCP_FALSE) +@@ -5492,6 +5609,281 @@ STARTMODE=auto + self._assert_headers(found) + + ++@mock.patch( ++ "cloudinit.net.is_openvswitch_internal_interface", ++ mock.Mock(return_value=False), ++) ++class TestNetworkManagerRendering(CiTestCase): ++ ++ with_logs = True ++ ++ scripts_dir = "/etc/NetworkManager/system-connections" ++ ++ expected_name = "expected_network_manager" ++ ++ def _get_renderer(self): ++ return network_manager.Renderer() ++ ++ def _render_and_read(self, network_config=None, state=None, dir=None): ++ if dir is None: ++ dir = self.tmp_dir() ++ ++ if network_config: ++ ns = network_state.parse_net_config_data(network_config) ++ elif state: ++ ns = state ++ else: ++ raise ValueError("Expected data or state, got neither") ++ ++ renderer = self._get_renderer() ++ renderer.render_network_state(ns, target=dir) ++ return dir2dict(dir) ++ ++ def _compare_files_to_expected(self, expected, found): ++ orig_maxdiff = self.maxDiff ++ expected_d = dict( ++ (os.path.join(self.scripts_dir, k), v) for k, v in expected.items() ++ ) ++ ++ try: ++ self.maxDiff = None ++ self.assertEqual(expected_d, found) ++ finally: ++ self.maxDiff = orig_maxdiff ++ ++ @mock.patch("cloudinit.net.util.get_cmdline", return_value="root=myroot") ++ @mock.patch("cloudinit.net.sys_dev_path") ++ @mock.patch("cloudinit.net.read_sys_net") ++ @mock.patch("cloudinit.net.get_devicelist") ++ def test_default_generation( ++ self, ++ mock_get_devicelist, ++ mock_read_sys_net, ++ mock_sys_dev_path, ++ m_get_cmdline, ++ ): ++ tmp_dir = self.tmp_dir() ++ _setup_test( ++ tmp_dir, mock_get_devicelist, mock_read_sys_net, mock_sys_dev_path ++ ) ++ ++ network_cfg = net.generate_fallback_config() ++ ns = network_state.parse_net_config_data( ++ network_cfg, skip_broken=False ++ ) ++ ++ render_dir = os.path.join(tmp_dir, "render") ++ os.makedirs(render_dir) ++ ++ renderer = self._get_renderer() ++ renderer.render_network_state(ns, target=render_dir) ++ ++ found = dir2dict(render_dir) ++ self._compare_files_to_expected( ++ { ++ "cloud-init-eth1000.nmconnection": textwrap.dedent( ++ """\ ++ # Generated by cloud-init. Changes will be lost. ++ ++ [connection] ++ id=cloud-init eth1000 ++ uuid=8c517500-0c95-5308-9c8a-3092eebc44eb ++ type=ethernet ++ ++ [user] ++ org.freedesktop.NetworkManager.origin=cloud-init ++ ++ [ethernet] ++ mac-address=07:1C:C6:75:A4:BE ++ ++ [ipv4] ++ method=auto ++ may-fail=false ++ ++ """ ++ ), ++ }, ++ found, ++ ) ++ ++ def test_openstack_rendering_samples(self): ++ for os_sample in OS_SAMPLES: ++ render_dir = self.tmp_dir() ++ ex_input = os_sample["in_data"] ++ ex_mac_addrs = os_sample["in_macs"] ++ network_cfg = openstack.convert_net_json( ++ ex_input, known_macs=ex_mac_addrs ++ ) ++ ns = network_state.parse_net_config_data( ++ network_cfg, skip_broken=False ++ ) ++ renderer = self._get_renderer() ++ # render a multiple times to simulate reboots ++ renderer.render_network_state(ns, target=render_dir) ++ renderer.render_network_state(ns, target=render_dir) ++ renderer.render_network_state(ns, target=render_dir) ++ for fn, expected_content in os_sample.get(self.expected_name, []): ++ with open(os.path.join(render_dir, fn)) as fh: ++ self.assertEqual(expected_content, fh.read()) ++ ++ def test_network_config_v1_samples(self): ++ ns = network_state.parse_net_config_data(CONFIG_V1_SIMPLE_SUBNET) ++ render_dir = self.tmp_path("render") ++ os.makedirs(render_dir) ++ renderer = self._get_renderer() ++ renderer.render_network_state(ns, target=render_dir) ++ found = dir2dict(render_dir) ++ self._compare_files_to_expected( ++ { ++ "cloud-init-interface0.nmconnection": textwrap.dedent( ++ """\ ++ # Generated by cloud-init. Changes will be lost. ++ ++ [connection] ++ id=cloud-init interface0 ++ uuid=8b6862ed-dbd6-5830-93f7-a91451c13828 ++ type=ethernet ++ ++ [user] ++ org.freedesktop.NetworkManager.origin=cloud-init ++ ++ [ethernet] ++ mac-address=52:54:00:12:34:00 ++ ++ [ipv4] ++ method=manual ++ may-fail=false ++ address1=10.0.2.15/24 ++ gateway=10.0.2.2 ++ ++ """ ++ ), ++ }, ++ found, ++ ) ++ ++ def test_config_with_explicit_loopback(self): ++ render_dir = self.tmp_path("render") ++ os.makedirs(render_dir) ++ ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK) ++ renderer = self._get_renderer() ++ renderer.render_network_state(ns, target=render_dir) ++ found = dir2dict(render_dir) ++ self._compare_files_to_expected( ++ { ++ "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 ++ type=ethernet ++ interface-name=eth0 ++ ++ [user] ++ org.freedesktop.NetworkManager.origin=cloud-init ++ ++ [ethernet] ++ ++ [ipv4] ++ method=auto ++ may-fail=false ++ ++ """ ++ ), ++ }, ++ found, ++ ) ++ ++ def test_bond_config(self): ++ entry = NETWORK_CONFIGS["bond"] ++ found = self._render_and_read(network_config=yaml.load(entry["yaml"])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ ++ def test_vlan_config(self): ++ entry = NETWORK_CONFIGS["vlan"] ++ found = self._render_and_read(network_config=yaml.load(entry["yaml"])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ ++ def test_bridge_config(self): ++ entry = NETWORK_CONFIGS["bridge"] ++ found = self._render_and_read(network_config=yaml.load(entry["yaml"])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ ++ def test_manual_config(self): ++ entry = NETWORK_CONFIGS["manual"] ++ found = self._render_and_read(network_config=yaml.load(entry["yaml"])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ ++ def test_all_config(self): ++ entry = NETWORK_CONFIGS["all"] ++ found = self._render_and_read(network_config=yaml.load(entry["yaml"])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ self.assertNotIn( ++ "WARNING: Network config: ignoring eth0.101 device-level mtu", ++ self.logs.getvalue(), ++ ) ++ ++ def test_small_config(self): ++ entry = NETWORK_CONFIGS["small"] ++ found = self._render_and_read(network_config=yaml.load(entry["yaml"])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ ++ def test_v4_and_v6_static_config(self): ++ entry = NETWORK_CONFIGS["v4_and_v6_static"] ++ found = self._render_and_read(network_config=yaml.load(entry["yaml"])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ expected_msg = ( ++ "WARNING: Network config: ignoring iface0 device-level mtu:8999" ++ " because ipv4 subnet-level mtu:9000 provided." ++ ) ++ self.assertIn(expected_msg, self.logs.getvalue()) ++ ++ def test_dhcpv6_only_config(self): ++ entry = NETWORK_CONFIGS["dhcpv6_only"] ++ found = self._render_and_read(network_config=yaml.load(entry["yaml"])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ ++ def test_simple_render_ipv6_slaac(self): ++ entry = NETWORK_CONFIGS["ipv6_slaac"] ++ found = self._render_and_read(network_config=yaml.load(entry["yaml"])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ ++ def test_dhcpv6_stateless_config(self): ++ entry = NETWORK_CONFIGS["dhcpv6_stateless"] ++ found = self._render_and_read(network_config=yaml.load(entry["yaml"])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ ++ def test_wakeonlan_disabled_config_v2(self): ++ entry = NETWORK_CONFIGS["wakeonlan_disabled"] ++ found = self._render_and_read( ++ network_config=yaml.load(entry["yaml_v2"]) ++ ) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ ++ def test_wakeonlan_enabled_config_v2(self): ++ entry = NETWORK_CONFIGS["wakeonlan_enabled"] ++ found = self._render_and_read( ++ network_config=yaml.load(entry["yaml_v2"]) ++ ) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ ++ def test_render_v4_and_v6(self): ++ entry = NETWORK_CONFIGS["v4_and_v6"] ++ found = self._render_and_read(network_config=yaml.load(entry["yaml"])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ ++ def test_render_v6_and_v4(self): ++ entry = NETWORK_CONFIGS["v6_and_v4"] ++ found = self._render_and_read(network_config=yaml.load(entry["yaml"])) ++ self._compare_files_to_expected(entry[self.expected_name], found) ++ ++ ++@mock.patch( ++ "cloudinit.net.is_openvswitch_internal_interface", ++ mock.Mock(return_value=False), ++) + class TestEniNetRendering(CiTestCase): + @mock.patch("cloudinit.net.util.get_cmdline", return_value="root=myroot") + @mock.patch("cloudinit.net.sys_dev_path") +@@ -7259,9 +7651,9 @@ class TestNetworkdRoundTrip(CiTestCase): + + class TestRenderersSelect: + @pytest.mark.parametrize( +- "renderer_selected,netplan,eni,nm,scfg,sys,networkd", ++ "renderer_selected,netplan,eni,sys,network_manager,networkd", + ( +- # -netplan -ifupdown -nm -scfg -sys raises error ++ # -netplan -ifupdown -sys -network-manager -networkd raises error + ( + net.RendererNotFoundError, + False, +@@ -7269,52 +7661,51 @@ class TestRenderersSelect: + False, + False, + False, +- False, + ), +- # -netplan +ifupdown -nm -scfg -sys selects eni +- ("eni", False, True, False, False, False, False), +- # +netplan +ifupdown -nm -scfg -sys selects eni +- ("eni", True, True, False, False, False, False), +- # +netplan -ifupdown -nm -scfg -sys selects netplan +- ("netplan", True, False, False, False, False, False), +- # Ubuntu with Network-Manager installed +- # +netplan -ifupdown +nm -scfg -sys selects netplan +- ("netplan", True, False, True, False, False, False), +- # Centos/OpenSuse with Network-Manager installed selects sysconfig +- # -netplan -ifupdown +nm -scfg +sys selects netplan +- ("sysconfig", False, False, True, False, True, False), +- # -netplan -ifupdown -nm -scfg -sys +networkd selects networkd +- ("networkd", False, False, False, False, False, True), ++ # -netplan +ifupdown -sys -nm -networkd selects eni ++ ("eni", False, True, False, False, False), ++ # +netplan +ifupdown -sys -nm -networkd selects eni ++ ("eni", True, True, False, False, False), ++ # +netplan -ifupdown -sys -nm -networkd selects netplan ++ ("netplan", True, False, False, False, False), ++ # +netplan -ifupdown -sys -nm -networkd selects netplan ++ ("netplan", True, False, False, False, False), ++ # -netplan -ifupdown +sys -nm -networkd selects sysconfig ++ ("sysconfig", False, False, True, False, False), ++ # -netplan -ifupdown +sys +nm -networkd selects sysconfig ++ ("sysconfig", False, False, True, True, False), ++ # -netplan -ifupdown -sys +nm -networkd selects nm ++ ("network-manager", False, False, False, True, False), ++ # -netplan -ifupdown -sys +nm +networkd selects nm ++ ("network-manager", False, False, False, True, True), ++ # -netplan -ifupdown -sys -nm +networkd selects networkd ++ ("networkd", False, False, False, False, True), + ), + ) + @mock.patch("cloudinit.net.renderers.networkd.available") ++ @mock.patch("cloudinit.net.renderers.network_manager.available") + @mock.patch("cloudinit.net.renderers.netplan.available") + @mock.patch("cloudinit.net.renderers.sysconfig.available") +- @mock.patch("cloudinit.net.renderers.sysconfig.available_sysconfig") +- @mock.patch("cloudinit.net.renderers.sysconfig.available_nm") + @mock.patch("cloudinit.net.renderers.eni.available") + def test_valid_renderer_from_defaults_depending_on_availability( + self, + m_eni_avail, +- m_nm_avail, +- m_scfg_avail, + m_sys_avail, + m_netplan_avail, ++ m_network_manager_avail, + m_networkd_avail, + renderer_selected, + netplan, + eni, +- nm, +- scfg, + sys, ++ network_manager, + networkd, + ): + """Assert proper renderer per DEFAULT_PRIORITY given availability.""" + m_eni_avail.return_value = eni # ifupdown pkg presence +- m_nm_avail.return_value = nm # network-manager presence +- m_scfg_avail.return_value = scfg # sysconfig presence + m_sys_avail.return_value = sys # sysconfig/ifup/down presence + m_netplan_avail.return_value = netplan # netplan presence ++ m_network_manager_avail.return_value = network_manager # NM presence + m_networkd_avail.return_value = networkd # networkd presence + if isinstance(renderer_selected, str): + (renderer_name, _rnd_class) = renderers.select( +@@ -7372,7 +7763,7 @@ class TestNetRenderers(CiTestCase): + priority=["sysconfig", "eni"], + ) + +- @mock.patch("cloudinit.net.sysconfig.available_sysconfig") ++ @mock.patch("cloudinit.net.sysconfig.available") + @mock.patch("cloudinit.util.system_info") + def test_sysconfig_available_uses_variant_mapping(self, m_info, m_avail): + m_avail.return_value = True +diff --git a/tests/unittests/test_net_activators.py b/tests/unittests/test_net_activators.py +index b735ea9e..afd9056a 100644 +--- a/tests/unittests/test_net_activators.py ++++ b/tests/unittests/test_net_activators.py +@@ -139,10 +139,6 @@ NETPLAN_AVAILABLE_CALLS = [ + (("netplan",), {"search": ["/usr/sbin", "/sbin"], "target": None}), + ] + +-NETWORK_MANAGER_AVAILABLE_CALLS = [ +- (("nmcli",), {"target": None}), +-] +- + NETWORKD_AVAILABLE_CALLS = [ + (("ip",), {"search": ["/usr/sbin", "/bin"], "target": None}), + (("systemctl",), {"search": ["/usr/sbin", "/bin"], "target": None}), +@@ -154,7 +150,6 @@ NETWORKD_AVAILABLE_CALLS = [ + [ + (IfUpDownActivator, IF_UP_DOWN_AVAILABLE_CALLS), + (NetplanActivator, NETPLAN_AVAILABLE_CALLS), +- (NetworkManagerActivator, NETWORK_MANAGER_AVAILABLE_CALLS), + (NetworkdActivator, NETWORKD_AVAILABLE_CALLS), + ], + ) +@@ -259,9 +254,11 @@ class TestActivatorsBringUp: + def test_bring_up_interface( + self, m_subp, activator, expected_call_list, available_mocks + ): ++ index = 0 + activator.bring_up_interface("eth0") +- assert len(m_subp.call_args_list) == 1 +- assert m_subp.call_args_list[0] == expected_call_list[0] ++ for call in m_subp.call_args_list: ++ assert call == expected_call_list[index] ++ index += 1 + + @patch("cloudinit.subp.subp", return_value=("", "")) + def test_bring_up_interfaces( diff --git a/0013-net-sysconfig-do-not-use-the-highest-autoconnect-pri.patch b/0013-net-sysconfig-do-not-use-the-highest-autoconnect-pri.patch new file mode 100644 index 0000000..cb9b594 --- /dev/null +++ b/0013-net-sysconfig-do-not-use-the-highest-autoconnect-pri.patch @@ -0,0 +1,33 @@ +From 0a2c6b6118ffaf29694b3a51aff3a33298419c50 Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Mon, 15 May 2023 19:15:12 +0530 +Subject: [PATCH] net/sysconfig: do not use the highest autoconnect priority + +Using the highest priority is a very big hammer that we may not want to use. We +may want users to override the cloud init generated ifcfg files for custom +configuration of interfaces. If cloud init uses the highest priority, nothing +can beat it. Hence lower the priority to 120 allowing values from 121 to 999 +to be used by users if they want to use a custom interface nm keyfile. + +X-downstream-only: true + +Suggested-by: thaller@redhat.com +fixes: c589da20eb92231 ("Setting highest autoconnect priority for network-scripts") +Signed-off-by: Ani Sinha +--- + cloudinit/net/sysconfig.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index b8786fb7..1fe82412 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -317,7 +317,7 @@ class Renderer(renderer.Renderer): + "ONBOOT": True, + "USERCTL": False, + "BOOTPROTO": "none", +- "AUTOCONNECT_PRIORITY": 999 ++ "AUTOCONNECT_PRIORITY": 120, + }, + "suse": {"BOOTPROTO": "static", "STARTMODE": "auto"}, + } diff --git a/0014-net-sysconfig-cosmetic-fix-tox-formatting.patch b/0014-net-sysconfig-cosmetic-fix-tox-formatting.patch new file mode 100644 index 0000000..4af002f --- /dev/null +++ b/0014-net-sysconfig-cosmetic-fix-tox-formatting.patch @@ -0,0 +1,43 @@ +From 603ad38bca7735eeb72217b4f169a4b4c42ac092 Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Tue, 16 May 2023 16:08:21 +0530 +Subject: [PATCH] net/sysconfig: cosmetic - fix tox formatting + +recommended cloud-init code formatting was not enforced with an older downstream +only change. This change fixes the formatting issue so that tox -e do_format +does not complain. Changes are cosmetic. + +X-downstream-only: true + +fixes: b3b96bff187e9d ("Do not write NM_CONTROLLED=no in generated interface config files") +Signed-off-by: Ani Sinha +--- + cloudinit/net/sysconfig.py | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index 1fe82412..fcce3e99 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -1025,15 +1025,15 @@ class Renderer(renderer.Renderer): + if sysconfig_path.endswith("network"): + util.ensure_dir(os.path.dirname(sysconfig_path)) + netcfg = [] +- for line in util.load_file(sysconfig_path, quiet=True).split('\n'): +- if 'cloud-init' in line: ++ for line in util.load_file(sysconfig_path, quiet=True).split("\n"): ++ if "cloud-init" in line: + break +- if not line.startswith(('NETWORKING=', +- 'IPV6_AUTOCONF=', +- 'NETWORKING_IPV6=')): ++ if not line.startswith( ++ ("NETWORKING=", "IPV6_AUTOCONF=", "NETWORKING_IPV6=") ++ ): + netcfg.append(line) + # Now generate the cloud-init portion of sysconfig/network +- netcfg.extend([_make_header(), 'NETWORKING=yes']) ++ netcfg.extend([_make_header(), "NETWORKING=yes"]) + if network_state.use_ipv6: + netcfg.append("NETWORKING_IPV6=yes") + netcfg.append("IPV6_AUTOCONF=no") diff --git a/0015-nm-generate-ipv6-stateful-dhcp-config-at-par-with-sy.patch b/0015-nm-generate-ipv6-stateful-dhcp-config-at-par-with-sy.patch new file mode 100644 index 0000000..31e95a8 --- /dev/null +++ b/0015-nm-generate-ipv6-stateful-dhcp-config-at-par-with-sy.patch @@ -0,0 +1,49 @@ +From 58d7574bca2b00d05d090c180f1345a2408cc700 Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Mon, 22 May 2023 21:30:01 +0530 +Subject: [PATCH] nm: generate ipv6 stateful dhcp config at par with sysconfig + (#4115) + +The sysconfig renderer sets the following in the ifcfg file for IPV6 stateful +DHCP configuration: + + BOOTPROTO = "dhcp" + DHCPV6C = True + IPV6INIT = True + IPV6_AUTOCONF = False + +This should result in + [ipv6] + method=dhcp + +in the network manager generated keyfile as DHCPV6C is set and +IPV6_AUTOCONF is not set. Unfortunately the network manager renderer +deviates from this and generates: + [ipv6] + method=auto + +in it's rendered keyfile. This change fixes this deviation and sets the +IPV6 dhcp stateful configuration in alignment with what is generated by the +sysconfig renderer. + +RHBZ: 2207716 + +Signed-off-by: Ani Sinha +(cherry picked from commit ea573ba6fc25fe49a6a1a322eeb5259b6238d78b) +--- + cloudinit/net/network_manager.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/cloudinit/net/network_manager.py b/cloudinit/net/network_manager.py +index 53763d15..744c0cbb 100644 +--- a/cloudinit/net/network_manager.py ++++ b/cloudinit/net/network_manager.py +@@ -72,7 +72,7 @@ class NMConnection: + "dhcp6": "auto", + "ipv6_slaac": "auto", + "ipv6_dhcpv6-stateless": "auto", +- "ipv6_dhcpv6-stateful": "auto", ++ "ipv6_dhcpv6-stateful": "dhcp", + "dhcp4": "auto", + "dhcp": "auto", + } diff --git a/0016-network_manager-add-a-method-for-ipv6-static-IP-conf.patch b/0016-network_manager-add-a-method-for-ipv6-static-IP-conf.patch new file mode 100644 index 0000000..be26e61 --- /dev/null +++ b/0016-network_manager-add-a-method-for-ipv6-static-IP-conf.patch @@ -0,0 +1,31 @@ +From 018aa09f049791755dd746b533abb2464b08a92d Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Mon, 22 May 2023 21:33:53 +0530 +Subject: [PATCH] network_manager: add a method for ipv6 static IP + configuration (#4127) + +The static IP configuration for IPv6 in the method_map is missing for +network manager renderer. This is causing cloud-init to generate a keyfile with +IPv6 method as "auto" instead of "manual". This fixes this issue. + +fixes: #4126 +RHBZ: 2196284 + +Signed-off-by: Ani Sinha +(cherry picked from commit 5d440856cb6d2b4c908015fe4eb7227615c17c8b) +--- + cloudinit/net/network_manager.py | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/cloudinit/net/network_manager.py b/cloudinit/net/network_manager.py +index 744c0cbb..2752f52f 100644 +--- a/cloudinit/net/network_manager.py ++++ b/cloudinit/net/network_manager.py +@@ -69,6 +69,7 @@ class NMConnection: + + method_map = { + "static": "manual", ++ "static6": "manual", + "dhcp6": "auto", + "ipv6_slaac": "auto", + "ipv6_dhcpv6-stateless": "auto", diff --git a/0017-net-sysconfig-enable-sysconfig-renderer-if-network-m.patch b/0017-net-sysconfig-enable-sysconfig-renderer-if-network-m.patch new file mode 100644 index 0000000..ba373ba --- /dev/null +++ b/0017-net-sysconfig-enable-sysconfig-renderer-if-network-m.patch @@ -0,0 +1,62 @@ +From 19adc5a0939fc1804b180333af5486e69d6af0ac Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Mon, 22 May 2023 22:06:28 +0530 +Subject: [PATCH] net/sysconfig: enable sysconfig renderer if network manager + has ifcfg-rh plugin (#4132) + +Some distributions like RHEL does not have ifup and ifdown +scripts that traditionally handled ifcfg-eth* files. Instead RHEL +uses network manager with ifcfg-rh plugin to handle ifcfg +scripts. Therefore, the sysconfig should check for the +existence of ifcfg-rh plugin in addition to checking for the +existence of ifup and ifdown scripts in order to determine if it +can handle ifcfg files. If either the plugin or ifup/ifdown scripts +are present, sysconfig renderer can be enabled. + +fixes: #4131 +RHBZ: 2194050 + +Signed-off-by: Ani Sinha +(cherry picked from commit 009dbf85a72a9077b2267d377b2ff46639fb3def) +--- + cloudinit/net/sysconfig.py | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index fcce3e99..f2c7c92c 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -1,6 +1,7 @@ + # This file is part of cloud-init. See LICENSE file for license information. + + import copy ++import glob + import io + import os + import re +@@ -1059,7 +1060,25 @@ def _supported_vlan_names(rdev, vid): + def available(target=None): + if not util.system_info()["variant"] in KNOWN_DISTROS: + return False ++ if available_sysconfig(target): ++ return True ++ if available_nm_ifcfg_rh(target): ++ return True ++ return False ++ ++ ++def available_nm_ifcfg_rh(target=None): ++ # The ifcfg-rh plugin of NetworkManager is installed. ++ # NetworkManager can handle the ifcfg files. ++ return glob.glob( ++ subp.target_path( ++ target, ++ "usr/lib*/NetworkManager/*/libnm-settings-plugin-ifcfg-rh.so", ++ ) ++ ) ++ + ++def available_sysconfig(target=None): + expected = ["ifup", "ifdown"] + search = ["/sbin", "/usr/sbin"] + for p in expected: diff --git a/0018-network-manager-Set-higher-autoconnect-priority-for-.patch b/0018-network-manager-Set-higher-autoconnect-priority-for-.patch new file mode 100644 index 0000000..84deac5 --- /dev/null +++ b/0018-network-manager-Set-higher-autoconnect-priority-for-.patch @@ -0,0 +1,401 @@ +From f0cf9e52fd084c23f0552456e3b780b5c9c3313a Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Tue, 23 May 2023 20:38:31 +0530 +Subject: [PATCH] network-manager: Set higher autoconnect priority for nm + keyfiles (#3671) + +cloud init generated keyfiles by network manager renderer for network +interfaces can sometimes conflict with existing keyfiles that are left as an +artifact of an upgrade process or are old user generated keyfiles. When two +such keyfiles are present, the existing keyfile can take precedence over the +cloud init generated keyfile making the later ineffective. Removing the old +keyfile blindly by cloud init would also not be correct since there would be +no way to enforce a different interface configuration if one needs it. + +This change adds an autoconnect-priority value for cloud init generated keyfile +so that the cloud init configuration takes precedence over the existing old +keyfile configuration in the default case. The priority values range from 0 +to 999. We set a value of 120 so that it would be high enough in the default +case and result in cloud init keyfile to take precedence but not too high so +that if the user generated keyfile needs to take precedence, the user can do +so by using a higher value than the one used by cloud init key file, between +the values 121 and 999. + +RHBZ: 2196231 + +Signed-off-by: Ani Sinha +(cherry picked from commit f663e94ac50bc518e694cbd167fdab216fcff029) +--- + cloudinit/net/network_manager.py | 1 + + tests/unittests/cmd/devel/test_net_convert.py | 1 + + .../cloud-init-encc000.2653.nmconnection | 1 + + .../cloud-init-encc000.nmconnection | 1 + + .../cloud-init-zz-all-en.nmconnection | 1 + + .../cloud-init-zz-all-eth.nmconnection | 1 + + tests/unittests/test_net.py | 36 +++++++++++++++++++ + 7 files changed, 42 insertions(+) + +diff --git a/cloudinit/net/network_manager.py b/cloudinit/net/network_manager.py +index 2752f52f..ca216928 100644 +--- a/cloudinit/net/network_manager.py ++++ b/cloudinit/net/network_manager.py +@@ -43,6 +43,7 @@ class NMConnection: + self.config["connection"] = { + "id": f"cloud-init {con_id}", + "uuid": str(uuid.uuid5(CI_NM_UUID, con_id)), ++ "autoconnect-priority": "120", + } + + # This is not actually used anywhere, but may be useful in future +diff --git a/tests/unittests/cmd/devel/test_net_convert.py b/tests/unittests/cmd/devel/test_net_convert.py +index 100aa8de..71654750 100644 +--- a/tests/unittests/cmd/devel/test_net_convert.py ++++ b/tests/unittests/cmd/devel/test_net_convert.py +@@ -74,6 +74,7 @@ SAMPLE_NETWORK_MANAGER_CONTENT = """\ + [connection] + id=cloud-init eth0 + uuid=1dd9a779-d327-56e1-8454-c65e2556c12c ++autoconnect-priority=120 + type=ethernet + interface-name=eth0 + +diff --git a/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-encc000.2653.nmconnection b/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-encc000.2653.nmconnection +index 80483d4f..f44485d2 100644 +--- a/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-encc000.2653.nmconnection ++++ b/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-encc000.2653.nmconnection +@@ -3,6 +3,7 @@ + [connection] + id=cloud-init encc000.2653 + uuid=116aaf19-aabc-50ea-b480-e9aee18bda59 ++autoconnect-priority=120 + type=vlan + interface-name=encc000.2653 + +diff --git a/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-encc000.nmconnection b/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-encc000.nmconnection +index 3368388d..fbdfbc65 100644 +--- a/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-encc000.nmconnection ++++ b/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-encc000.nmconnection +@@ -3,6 +3,7 @@ + [connection] + id=cloud-init encc000 + uuid=f869ebd3-f175-5747-bf02-d0d44d687248 ++autoconnect-priority=120 + type=ethernet + interface-name=encc000 + +diff --git a/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-zz-all-en.nmconnection b/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-zz-all-en.nmconnection +index 16120bc1..dce56c7d 100644 +--- a/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-zz-all-en.nmconnection ++++ b/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-zz-all-en.nmconnection +@@ -3,6 +3,7 @@ + [connection] + id=cloud-init zz-all-en + uuid=159daec9-cba3-5101-85e7-46d831857f43 ++autoconnect-priority=120 + type=ethernet + interface-name=zz-all-en + +diff --git a/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-zz-all-eth.nmconnection b/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-zz-all-eth.nmconnection +index df44d546..ee436bf2 100644 +--- a/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-zz-all-eth.nmconnection ++++ b/tests/unittests/net/artifacts/no_matching_mac/etc/NetworkManager/system-connections/cloud-init-zz-all-eth.nmconnection +@@ -3,6 +3,7 @@ + [connection] + id=cloud-init zz-all-eth + uuid=23a83d8a-d7db-5133-a77b-e68a6ac61ec9 ++autoconnect-priority=120 + type=ethernet + interface-name=zz-all-eth + +diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py +index 0f523ff8..7abe61b9 100644 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -631,6 +631,7 @@ dns = none + [connection] + id=cloud-init eth0 + uuid=1dd9a779-d327-56e1-8454-c65e2556c12c ++autoconnect-priority=120 + type=ethernet + + [user] +@@ -1118,6 +1119,7 @@ NETWORK_CONFIGS = { + [connection] + id=cloud-init eth1 + uuid=3c50eb47-7260-5a6d-801d-bd4f587d6b58 ++ autoconnect-priority=120 + type=ethernet + + [user] +@@ -1135,6 +1137,7 @@ NETWORK_CONFIGS = { + [connection] + id=cloud-init eth99 + uuid=b1b88000-1f03-5360-8377-1a2205efffb4 ++ autoconnect-priority=120 + type=ethernet + + [user] +@@ -1234,6 +1237,7 @@ NETWORK_CONFIGS = { + [connection] + id=cloud-init iface0 + uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70 ++ autoconnect-priority=120 + type=ethernet + interface-name=iface0 + +@@ -1364,6 +1368,7 @@ NETWORK_CONFIGS = { + [connection] + id=cloud-init iface0 + uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70 ++ autoconnect-priority=120 + type=ethernet + interface-name=iface0 + +@@ -1404,6 +1409,7 @@ NETWORK_CONFIGS = { + [connection] + id=cloud-init iface0 + uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70 ++ autoconnect-priority=120 + type=ethernet + interface-name=iface0 + +@@ -1504,6 +1510,7 @@ NETWORK_CONFIGS = { + [connection] + id=cloud-init iface0 + uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70 ++ autoconnect-priority=120 + type=ethernet + interface-name=iface0 + +@@ -1734,6 +1741,7 @@ NETWORK_CONFIGS = { + [connection] + id=cloud-init iface0 + uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70 ++ autoconnect-priority=120 + type=ethernet + interface-name=iface0 + +@@ -1845,6 +1853,7 @@ NETWORK_CONFIGS = { + [connection] + id=cloud-init iface0 + uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70 ++ autoconnect-priority=120 + type=ethernet + interface-name=iface0 + +@@ -1967,6 +1976,7 @@ NETWORK_CONFIGS = { + [connection] + id=cloud-init iface0 + uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70 ++ autoconnect-priority=120 + type=ethernet + interface-name=iface0 + +@@ -2043,6 +2053,7 @@ NETWORK_CONFIGS = { + [connection] + id=cloud-init iface0 + uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70 ++ autoconnect-priority=120 + type=ethernet + interface-name=iface0 + +@@ -2507,6 +2518,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + [connection] + id=cloud-init eth3 + uuid=b7e95dda-7746-5bf8-bf33-6e5f3c926790 ++ autoconnect-priority=120 + type=ethernet + slave-type=bridge + master=dee46ce4-af7a-5e7c-aa08-b25533ae9213 +@@ -2526,6 +2538,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + [connection] + id=cloud-init eth5 + uuid=5fda13c7-9942-5e90-a41b-1d043bd725dc ++ autoconnect-priority=120 + type=ethernet + + [user] +@@ -2547,6 +2560,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + [connection] + id=cloud-init ib0 + uuid=11a1dda7-78b4-5529-beba-d9b5f549ad7b ++ autoconnect-priority=120 + type=infiniband + + [user] +@@ -2571,6 +2585,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + [connection] + id=cloud-init bond0.200 + uuid=88984a9c-ff22-5233-9267-86315e0acaa7 ++ autoconnect-priority=120 + type=vlan + interface-name=bond0.200 + +@@ -2594,6 +2609,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + [connection] + id=cloud-init eth0 + uuid=1dd9a779-d327-56e1-8454-c65e2556c12c ++ autoconnect-priority=120 + type=ethernet + + [user] +@@ -2611,6 +2627,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + [connection] + id=cloud-init eth4 + uuid=e27e4959-fb50-5580-b9a4-2073554627b9 ++ autoconnect-priority=120 + type=ethernet + slave-type=bridge + master=dee46ce4-af7a-5e7c-aa08-b25533ae9213 +@@ -2630,6 +2647,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + [connection] + id=cloud-init eth1 + uuid=3c50eb47-7260-5a6d-801d-bd4f587d6b58 ++ autoconnect-priority=120 + type=ethernet + slave-type=bond + master=54317911-f840-516b-a10d-82cb4c1f075c +@@ -2649,6 +2667,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + [connection] + id=cloud-init br0 + uuid=dee46ce4-af7a-5e7c-aa08-b25533ae9213 ++ autoconnect-priority=120 + type=bridge + interface-name=br0 + +@@ -2680,6 +2699,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + [connection] + id=cloud-init eth0.101 + uuid=b5acec5e-db80-5935-8b02-0d5619fc42bf ++ autoconnect-priority=120 + type=vlan + interface-name=eth0.101 + +@@ -2708,6 +2728,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + [connection] + id=cloud-init bond0 + uuid=54317911-f840-516b-a10d-82cb4c1f075c ++ autoconnect-priority=120 + type=bond + interface-name=bond0 + +@@ -2732,6 +2753,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + [connection] + id=cloud-init eth2 + uuid=5559a242-3421-5fdd-896e-9cb8313d5804 ++ autoconnect-priority=120 + type=ethernet + slave-type=bond + master=54317911-f840-516b-a10d-82cb4c1f075c +@@ -3257,6 +3279,7 @@ iface bond0 inet6 static + [connection] + id=cloud-init bond0s0 + uuid=09d0b5b9-67e7-5577-a1af-74d1cf17a71e ++ autoconnect-priority=120 + type=ethernet + slave-type=bond + master=54317911-f840-516b-a10d-82cb4c1f075c +@@ -3276,6 +3299,7 @@ iface bond0 inet6 static + [connection] + id=cloud-init bond0s1 + uuid=4d9aca96-b515-5630-ad83-d13daac7f9d0 ++ autoconnect-priority=120 + type=ethernet + slave-type=bond + master=54317911-f840-516b-a10d-82cb4c1f075c +@@ -3295,6 +3319,7 @@ iface bond0 inet6 static + [connection] + id=cloud-init bond0 + uuid=54317911-f840-516b-a10d-82cb4c1f075c ++ autoconnect-priority=120 + type=bond + interface-name=bond0 + +@@ -3421,6 +3446,7 @@ iface bond0 inet6 static + [connection] + id=cloud-init en0.99 + uuid=f594e2ed-f107-51df-b225-1dc530a5356b ++ autoconnect-priority=120 + type=vlan + interface-name=en0.99 + +@@ -3453,6 +3479,7 @@ iface bond0 inet6 static + [connection] + id=cloud-init en0 + uuid=e0ca478b-8d84-52ab-8fae-628482c629b5 ++ autoconnect-priority=120 + type=ethernet + + [user] +@@ -3580,6 +3607,7 @@ iface bond0 inet6 static + [connection] + id=cloud-init br0 + uuid=dee46ce4-af7a-5e7c-aa08-b25533ae9213 ++ autoconnect-priority=120 + type=bridge + interface-name=br0 + +@@ -3604,6 +3632,7 @@ iface bond0 inet6 static + [connection] + id=cloud-init eth0 + uuid=1dd9a779-d327-56e1-8454-c65e2556c12c ++ autoconnect-priority=120 + type=ethernet + slave-type=bridge + master=dee46ce4-af7a-5e7c-aa08-b25533ae9213 +@@ -3628,6 +3657,7 @@ iface bond0 inet6 static + [connection] + id=cloud-init eth1 + uuid=3c50eb47-7260-5a6d-801d-bd4f587d6b58 ++ autoconnect-priority=120 + type=ethernet + slave-type=bridge + master=dee46ce4-af7a-5e7c-aa08-b25533ae9213 +@@ -3782,6 +3812,7 @@ iface bond0 inet6 static + [connection] + id=cloud-init eth0 + uuid=1dd9a779-d327-56e1-8454-c65e2556c12c ++ autoconnect-priority=120 + type=ethernet + + [user] +@@ -3804,6 +3835,7 @@ iface bond0 inet6 static + [connection] + id=cloud-init eth1 + uuid=3c50eb47-7260-5a6d-801d-bd4f587d6b58 ++ autoconnect-priority=120 + type=ethernet + + [user] +@@ -3826,6 +3858,7 @@ iface bond0 inet6 static + [connection] + id=cloud-init eth2 + uuid=5559a242-3421-5fdd-896e-9cb8313d5804 ++ autoconnect-priority=120 + type=ethernet + + [user] +@@ -5688,6 +5721,7 @@ class TestNetworkManagerRendering(CiTestCase): + [connection] + id=cloud-init eth1000 + uuid=8c517500-0c95-5308-9c8a-3092eebc44eb ++ autoconnect-priority=120 + type=ethernet + + [user] +@@ -5742,6 +5776,7 @@ class TestNetworkManagerRendering(CiTestCase): + [connection] + id=cloud-init interface0 + uuid=8b6862ed-dbd6-5830-93f7-a91451c13828 ++ autoconnect-priority=120 + type=ethernet + + [user] +@@ -5778,6 +5813,7 @@ class TestNetworkManagerRendering(CiTestCase): + [connection] + id=cloud-init eth0 + uuid=1dd9a779-d327-56e1-8454-c65e2556c12c ++ autoconnect-priority=120 + type=ethernet + interface-name=eth0 + diff --git a/0019-Set-default-renderer-as-sysconfig-for-c9s-RHEL-9.patch b/0019-Set-default-renderer-as-sysconfig-for-c9s-RHEL-9.patch new file mode 100644 index 0000000..82c2353 --- /dev/null +++ b/0019-Set-default-renderer-as-sysconfig-for-c9s-RHEL-9.patch @@ -0,0 +1,42 @@ +From 5394e28e896e9d18db6d359190bfb58dcbdb2646 Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Tue, 23 May 2023 21:15:30 +0530 +Subject: [PATCH] Set default renderer as sysconfig for c9s/RHEL 9 + +Currently, network manager is disabled on c9s and RHEL 9 and therefore +sysconfig is used as the primary renderer for network configuration on those +distribution flavors. We do not want to change this for c9s or RHEL 9 even when +network-manager renderer is re-enabled. NM was re-enabled with the following +commit: +5822f72230a58d ("Revert "Revert "Add native NetworkManager support (#1224)""") + +This change bumps up the priority for sysconfig renderer so that it is used as +the primary renderer on c9s/RHEL 9 and other downstream distributions derived +from them. For c10s or RHEL 10, we might revert this change so that +network-manager again becomes the primary renderer for those distributions. + +X-downstream-only: true + +fixes: 0b0632f6c0 ("Revert "Manual revert "Use Network-Manager and Netplan as default renderers for RHEL and Fedora (#1465)") +fixes: 7703aa98b89 ("Use Network-Manager and Netplan as default renderers for RHEL and Fedora (#1465)") + +RHBZ:2209349 + +Signed-off-by: Ani Sinha +--- + config/cloud.cfg.tmpl | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl +index 7238c102..22c6654c 100644 +--- a/config/cloud.cfg.tmpl ++++ b/config/cloud.cfg.tmpl +@@ -383,7 +383,7 @@ system_info: + renderers: ['freebsd'] + {% elif variant in ["fedora"] or is_rhel %} + network: +- renderers: ['netplan', 'network-manager', 'networkd', 'sysconfig', 'eni'] ++ renderers: ['sysconfig', 'eni', 'netplan', 'network-manager', 'networkd'] + {% elif variant == "openmandriva" %} + network: + renderers: ['network-manager', 'networkd'] diff --git a/cloud-init.spec b/cloud-init.spec index 7fc676d..25b5f86 100644 --- a/cloud-init.spec +++ b/cloud-init.spec @@ -1,6 +1,6 @@ Name: cloud-init Version: 23.1.1 -Release: 5%{?dist} +Release: 6%{?dist} Summary: Cloud instance init scripts License: ASL 2.0 or GPLv3 URL: http://launchpad.net/cloud-init @@ -18,6 +18,15 @@ Patch7: 0007-rhel-make-sure-previous-hostname-file-ends-with-a-ne.patch Patch8: 0008-Don-t-change-permissions-of-netrules-target-2076.patch Patch9: 0009-Make-user-vendor-data-sensitive-and-remove-log-permi.patch Patch10: 0010-Do-not-generate-dsa-and-ed25519-key-types-when-crypt.patch +Patch11: 0011-Revert-Manual-revert-Use-Network-Manager-and-Netplan.patch +Patch12: 0012-Revert-Revert-Add-native-NetworkManager-support-1224.patch +Patch13: 0013-net-sysconfig-do-not-use-the-highest-autoconnect-pri.patch +Patch14: 0014-net-sysconfig-cosmetic-fix-tox-formatting.patch +Patch15: 0015-nm-generate-ipv6-stateful-dhcp-config-at-par-with-sy.patch +Patch16: 0016-network_manager-add-a-method-for-ipv6-static-IP-conf.patch +Patch17: 0017-net-sysconfig-enable-sysconfig-renderer-if-network-m.patch +Patch18: 0018-network-manager-Set-higher-autoconnect-priority-for-.patch +Patch19: 0019-Set-default-renderer-as-sysconfig-for-c9s-RHEL-9.patch BuildArch: noarch @@ -207,6 +216,18 @@ fi %config(noreplace) %{_sysconfdir}/rsyslog.d/21-cloudinit.conf %changelog +* Tue Jun 06 2023 Camilla Conte - 23.1.1-6 +- 0011-Revert-Manual-revert-Use-Network-Manager-and-Netplan.patch +- 0012-Revert-Revert-Add-native-NetworkManager-support-1224.patch +- 0013-net-sysconfig-do-not-use-the-highest-autoconnect-pri.patch +- 0014-net-sysconfig-cosmetic-fix-tox-formatting.patch +- 0015-nm-generate-ipv6-stateful-dhcp-config-at-par-with-sy.patch [bz#2207716] +- 0016-network_manager-add-a-method-for-ipv6-static-IP-conf.patch [bz#2196284] +- 0017-net-sysconfig-enable-sysconfig-renderer-if-network-m.patch [bz#2194050] +- 0018-network-manager-Set-higher-autoconnect-priority-for-.patch [bz#2196231] +- 0019-Set-default-renderer-as-sysconfig-for-c9s-RHEL-9.patch [bz#2209349] +- Resolves: bz#2118235 bz#2194050 bz#2196231 bz#2196284 bz#2207716 bz#2209349 + * Tue May 16 2023 Camilla Conte - 23.1.1-5 - 0010-Do-not-generate-dsa-and-ed25519-key-types-when-crypt.patch [bz#2187164] - Resolves: bz#2187164