From f403bd6b3bb0d616b6962cb8c221555f6fa3c571 Mon Sep 17 00:00:00 2001 From: Miroslav Rezanina Date: Mon, 10 Jul 2023 08:41:14 -0400 Subject: [PATCH] * Mon Jul 10 2023 Miroslav Rezanina - 23.1.1-6 - ci-Revert-Manual-revert-Use-Network-Manager-and-Netplan.patch [bz#2219528] - ci-Revert-Revert-Add-native-NetworkManager-support-1224.patch [bz#2219528] - ci-nm-generate-ipv6-stateful-dhcp-config-at-par-with-sy.patch [bz#2219528] - ci-network_manager-add-a-method-for-ipv6-static-IP-conf.patch [bz#2219528] - ci-net-sysconfig-enable-sysconfig-renderer-if-network-m.patch [bz#2219528] - ci-network-manager-Set-higher-autoconnect-priority-for-.patch [bz#2219528] - ci-Set-default-renderer-as-sysconfig-for-centos-rhel-41.patch [bz#2219528] - Resolves: bz#2219528 ([RHEL8] Support configuring network by NM keyfiles) --- ...vert-Use-Network-Manager-and-Netplan.patch | 102 ++ ...d-native-NetworkManager-support-1224.patch | 1401 +++++++++++++++++ ...erer-as-sysconfig-for-centos-rhel-41.patch | 44 + ...able-sysconfig-renderer-if-network-m.patch | 71 + ...Set-higher-autoconnect-priority-for-.patch | 410 +++++ ...add-a-method-for-ipv6-static-IP-conf.patch | 40 + ...-stateful-dhcp-config-at-par-with-sy.patch | 58 + cloud-init.spec | 27 +- 8 files changed, 2152 insertions(+), 1 deletion(-) create mode 100644 ci-Revert-Manual-revert-Use-Network-Manager-and-Netplan.patch create mode 100644 ci-Revert-Revert-Add-native-NetworkManager-support-1224.patch create mode 100644 ci-Set-default-renderer-as-sysconfig-for-centos-rhel-41.patch create mode 100644 ci-net-sysconfig-enable-sysconfig-renderer-if-network-m.patch create mode 100644 ci-network-manager-Set-higher-autoconnect-priority-for-.patch create mode 100644 ci-network_manager-add-a-method-for-ipv6-static-IP-conf.patch create mode 100644 ci-nm-generate-ipv6-stateful-dhcp-config-at-par-with-sy.patch diff --git a/ci-Revert-Manual-revert-Use-Network-Manager-and-Netplan.patch b/ci-Revert-Manual-revert-Use-Network-Manager-and-Netplan.patch new file mode 100644 index 0000000..f8efe4a --- /dev/null +++ b/ci-Revert-Manual-revert-Use-Network-Manager-and-Netplan.patch @@ -0,0 +1,102 @@ +From f7aaef405cd87d7d969f28401f3a4a7538d57c76 Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Thu, 4 May 2023 15:34:43 +0530 +Subject: [PATCH 1/7] Revert "Manual revert "Use Network-Manager and Netplan as + default renderers for RHEL and Fedora (#1465)"" + +RH-Author: Ani Sinha +RH-MergeRequest: 103: [RHEL8] Support configuring network by NM keyfiles +RH-Bugzilla: 2219528 +RH-Acked-by: Miroslav Rezanina +RH-Commit: [1/7] 65838b451e21f92cf92d2d4967015c48816f82f9 + +This reverts commit 0616dbd3f523395b619960b67b3b65c2f0ea15f4. + +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 + =========================== +-- +2.39.3 + diff --git a/ci-Revert-Revert-Add-native-NetworkManager-support-1224.patch b/ci-Revert-Revert-Add-native-NetworkManager-support-1224.patch new file mode 100644 index 0000000..595b8ef --- /dev/null +++ b/ci-Revert-Revert-Add-native-NetworkManager-support-1224.patch @@ -0,0 +1,1401 @@ +From 7ac066b494e07c14087298ed2ffde347172f1683 Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Thu, 4 May 2023 15:39:17 +0530 +Subject: [PATCH 2/7] Revert "Revert "Add native NetworkManager support + (#1224)"" + +RH-Author: Ani Sinha +RH-MergeRequest: 103: [RHEL8] Support configuring network by NM keyfiles +RH-Bugzilla: 2219528 +RH-Acked-by: Miroslav Rezanina +RH-Commit: [2/7] d4edad28b5e2a4eb99cf846bedc139a86ea63227 + +This reverts commit df17359efbf873396cd49bbd87b1680700cdda41 . + +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 da6d11b3..f7ac5898 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?? +@@ -1039,8 +1018,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 +@@ -1079,14 +1056,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: +@@ -1103,10 +1075,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( +-- +2.39.3 + diff --git a/ci-Set-default-renderer-as-sysconfig-for-centos-rhel-41.patch b/ci-Set-default-renderer-as-sysconfig-for-centos-rhel-41.patch new file mode 100644 index 0000000..b2e00e1 --- /dev/null +++ b/ci-Set-default-renderer-as-sysconfig-for-centos-rhel-41.patch @@ -0,0 +1,44 @@ +From c33a3f27e449371e36f19269f81883c5a50131bb Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Thu, 8 Jun 2023 03:29:13 +0530 +Subject: [PATCH 7/7] Set default renderer as sysconfig for centos/rhel (#4165) + +RH-Author: Ani Sinha +RH-MergeRequest: 103: [RHEL8] Support configuring network by NM keyfiles +RH-Bugzilla: 2219528 +RH-Acked-by: Miroslav Rezanina +RH-Commit: [7/7] aec68bb518c82bfd6b67fbe89b72bbda81c01cf9 + +Currently, network manager is disabled on c9s and therefore sysconfig is used as the primary renderer for network configuration. We do not want to change this for c9s even when network-manager renderer is re-enabled as it would mean a big behaviour change for cloud-init in the centos 9 stream. + +This change bumps up the priority for sysconfig renderer so that it is used as the primary renderer on c9s and other downstream distributions derived from it. In the next major centos stream release, we may use network manager as the default renderer and make changes accordingly. + +RHBZ: 2209349 + +Signed-off-by: Ani Sinha +(cherry picked from commit a1f375095bd0ac8628c4fdc79538dc177bb9ff99) +--- + config/cloud.cfg.tmpl | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl +index 7238c102..020340f9 100644 +--- a/config/cloud.cfg.tmpl ++++ b/config/cloud.cfg.tmpl +@@ -381,9 +381,12 @@ system_info: + {% elif variant in ["dragonfly"] %} + network: + renderers: ['freebsd'] +-{% elif variant in ["fedora"] or is_rhel %} ++{% elif variant in ["fedora"] %} + network: + renderers: ['netplan', 'network-manager', 'networkd', 'sysconfig', 'eni'] ++{% elif is_rhel %} ++ network: ++ renderers: ['sysconfig', 'eni', 'netplan', 'network-manager', 'networkd' ] + {% elif variant == "openmandriva" %} + network: + renderers: ['network-manager', 'networkd'] +-- +2.39.3 + diff --git a/ci-net-sysconfig-enable-sysconfig-renderer-if-network-m.patch b/ci-net-sysconfig-enable-sysconfig-renderer-if-network-m.patch new file mode 100644 index 0000000..b37d469 --- /dev/null +++ b/ci-net-sysconfig-enable-sysconfig-renderer-if-network-m.patch @@ -0,0 +1,71 @@ +From 3b68f70013c84ae9efbc31aa35641b61041fd62a Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Mon, 22 May 2023 22:06:28 +0530 +Subject: [PATCH 5/7] net/sysconfig: enable sysconfig renderer if network + manager has ifcfg-rh plugin (#4132) + +RH-Author: Ani Sinha +RH-MergeRequest: 103: [RHEL8] Support configuring network by NM keyfiles +RH-Bugzilla: 2219528 +RH-Acked-by: Miroslav Rezanina +RH-Commit: [5/7] 4d1602e39fbf85277e50a1fde046a0b528a18364 + +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 f7ac5898..5bf3e7ca 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 +@@ -1058,7 +1059,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: +-- +2.39.3 + diff --git a/ci-network-manager-Set-higher-autoconnect-priority-for-.patch b/ci-network-manager-Set-higher-autoconnect-priority-for-.patch new file mode 100644 index 0000000..e6dc651 --- /dev/null +++ b/ci-network-manager-Set-higher-autoconnect-priority-for-.patch @@ -0,0 +1,410 @@ +From f3f9a6933ba2c348d0ccd92706b1c17655f91625 Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Tue, 23 May 2023 20:38:31 +0530 +Subject: [PATCH 6/7] network-manager: Set higher autoconnect priority for nm + keyfiles (#3671) + +RH-Author: Ani Sinha +RH-MergeRequest: 103: [RHEL8] Support configuring network by NM keyfiles +RH-Bugzilla: 2219528 +RH-Acked-by: Miroslav Rezanina +RH-Commit: [6/7] f263baba1870ed035bd1662ddeb0ab5bcb6a8cd1 + +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 + +-- +2.39.3 + diff --git a/ci-network_manager-add-a-method-for-ipv6-static-IP-conf.patch b/ci-network_manager-add-a-method-for-ipv6-static-IP-conf.patch new file mode 100644 index 0000000..91230cc --- /dev/null +++ b/ci-network_manager-add-a-method-for-ipv6-static-IP-conf.patch @@ -0,0 +1,40 @@ +From 2db9b803e64171d2c8d8a3ad465b0fb979abf146 Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Mon, 22 May 2023 21:33:53 +0530 +Subject: [PATCH 4/7] network_manager: add a method for ipv6 static IP + configuration (#4127) + +RH-Author: Ani Sinha +RH-MergeRequest: 103: [RHEL8] Support configuring network by NM keyfiles +RH-Bugzilla: 2219528 +RH-Acked-by: Miroslav Rezanina +RH-Commit: [4/7] dfc67da03ac11c18439c3500b8cfba6a66a7428e + +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", +-- +2.39.3 + diff --git a/ci-nm-generate-ipv6-stateful-dhcp-config-at-par-with-sy.patch b/ci-nm-generate-ipv6-stateful-dhcp-config-at-par-with-sy.patch new file mode 100644 index 0000000..7851853 --- /dev/null +++ b/ci-nm-generate-ipv6-stateful-dhcp-config-at-par-with-sy.patch @@ -0,0 +1,58 @@ +From 2e5e0383567191808e2054cb236bdbd785540b26 Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Mon, 22 May 2023 21:30:01 +0530 +Subject: [PATCH 3/7] nm: generate ipv6 stateful dhcp config at par with + sysconfig (#4115) + +RH-Author: Ani Sinha +RH-MergeRequest: 103: [RHEL8] Support configuring network by NM keyfiles +RH-Bugzilla: 2219528 +RH-Acked-by: Miroslav Rezanina +RH-Commit: [3/7] cf60e9477ac047f9e7e58c2fc528745fc2ae4248 + +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", + } +-- +2.39.3 + diff --git a/cloud-init.spec b/cloud-init.spec index a4809bb..364be94 100644 --- a/cloud-init.spec +++ b/cloud-init.spec @@ -6,7 +6,7 @@ Name: cloud-init Version: 23.1.1 -Release: 5%{?dist} +Release: 6%{?dist} Summary: Cloud instance init scripts Group: System Environment/Base @@ -27,6 +27,20 @@ Patch8: ci-rhel-make-sure-previous-hostname-file-ends-with-a-ne.patch Patch9: ci-Don-t-change-permissions-of-netrules-target-2076.patch # For bz#2190081 - CVE-2023-1786 cloud-init: sensitive data could be exposed in logs [rhel-8] Patch10: ci-Make-user-vendor-data-sensitive-and-remove-log-permi.patch +# For bz#2219528 - [RHEL8] Support configuring network by NM keyfiles +Patch11: ci-Revert-Manual-revert-Use-Network-Manager-and-Netplan.patch +# For bz#2219528 - [RHEL8] Support configuring network by NM keyfiles +Patch12: ci-Revert-Revert-Add-native-NetworkManager-support-1224.patch +# For bz#2219528 - [RHEL8] Support configuring network by NM keyfiles +Patch13: ci-nm-generate-ipv6-stateful-dhcp-config-at-par-with-sy.patch +# For bz#2219528 - [RHEL8] Support configuring network by NM keyfiles +Patch14: ci-network_manager-add-a-method-for-ipv6-static-IP-conf.patch +# For bz#2219528 - [RHEL8] Support configuring network by NM keyfiles +Patch15: ci-net-sysconfig-enable-sysconfig-renderer-if-network-m.patch +# For bz#2219528 - [RHEL8] Support configuring network by NM keyfiles +Patch16: ci-network-manager-Set-higher-autoconnect-priority-for-.patch +# For bz#2219528 - [RHEL8] Support configuring network by NM keyfiles +Patch17: ci-Set-default-renderer-as-sysconfig-for-centos-rhel-41.patch BuildArch: noarch @@ -233,6 +247,17 @@ fi %config(noreplace) %{_sysconfdir}/rsyslog.d/21-cloudinit.conf %changelog +* Mon Jul 10 2023 Miroslav Rezanina - 23.1.1-6 +- ci-Revert-Manual-revert-Use-Network-Manager-and-Netplan.patch [bz#2219528] +- ci-Revert-Revert-Add-native-NetworkManager-support-1224.patch [bz#2219528] +- ci-nm-generate-ipv6-stateful-dhcp-config-at-par-with-sy.patch [bz#2219528] +- ci-network_manager-add-a-method-for-ipv6-static-IP-conf.patch [bz#2219528] +- ci-net-sysconfig-enable-sysconfig-renderer-if-network-m.patch [bz#2219528] +- ci-network-manager-Set-higher-autoconnect-priority-for-.patch [bz#2219528] +- ci-Set-default-renderer-as-sysconfig-for-centos-rhel-41.patch [bz#2219528] +- Resolves: bz#2219528 + ([RHEL8] Support configuring network by NM keyfiles) + * Tue Jul 4 2023 Camilla Conte - 23.1.1-5 - ci-Add-warning-during-upgrade-from-an-old-version-with-.patch [bz#2210012] - Resolves: bz#2210012