ad9e5f
From f1836e78d20ef34b05b6aba002fc10a97eceb454 Mon Sep 17 00:00:00 2001
ad9e5f
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
ad9e5f
Date: Mon, 8 Aug 2022 10:08:50 +0200
ad9e5f
Subject: [PATCH 1/2] Revert "Add native NetworkManager support (#1224)"
ad9e5f
ad9e5f
RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
ad9e5f
RH-MergeRequest: 81: Revert "Use Network-Manager and Netplan as default renderers for RHEL and Fedora (#1465)"
ad9e5f
RH-Commit: [1/2] 5b3e51502a89c2dcfbc97dc08a86b792454fedd3
ad9e5f
RH-Bugzilla: 2107464 2110066 2117526 2104393 2098624
ad9e5f
RH-Acked-by: Eduardo Otubo <otubo@redhat.com>
ad9e5f
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
ad9e5f
RH-Acked-by: Mohamed Gamal Morsy <mmorsy@redhat.com>
ad9e5f
ad9e5f
NM is not yet stable, so we don't want to support it for now.
ad9e5f
This reverts commit 0d93e53fd05c44b62e3456b7580c9de8135e6b5a.
ad9e5f
ad9e5f
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
ad9e5f
---
ad9e5f
 cloudinit/cmd/devel/net_convert.py     |   14 +-
ad9e5f
 cloudinit/net/activators.py            |   25 +-
ad9e5f
 cloudinit/net/network_manager.py       |  377 -------
ad9e5f
 cloudinit/net/renderers.py             |    3 -
ad9e5f
 cloudinit/net/sysconfig.py             |   37 +-
ad9e5f
 tests/unittests/test_net.py            | 1268 +++---------------------
ad9e5f
 tests/unittests/test_net_activators.py |   93 +-
ad9e5f
 7 files changed, 193 insertions(+), 1624 deletions(-)
ad9e5f
 delete mode 100644 cloudinit/net/network_manager.py
ad9e5f
ad9e5f
diff --git a/cloudinit/cmd/devel/net_convert.py b/cloudinit/cmd/devel/net_convert.py
ad9e5f
index 647fe07b..18b1e7ff 100755
ad9e5f
--- a/cloudinit/cmd/devel/net_convert.py
ad9e5f
+++ b/cloudinit/cmd/devel/net_convert.py
ad9e5f
@@ -7,14 +7,7 @@ import os
ad9e5f
 import sys
ad9e5f
 
ad9e5f
 from cloudinit import distros, log, safeyaml
ad9e5f
-from cloudinit.net import (
ad9e5f
-    eni,
ad9e5f
-    netplan,
ad9e5f
-    network_manager,
ad9e5f
-    network_state,
ad9e5f
-    networkd,
ad9e5f
-    sysconfig,
ad9e5f
-)
ad9e5f
+from cloudinit.net import eni, netplan, network_state, networkd, sysconfig
ad9e5f
 from cloudinit.sources import DataSourceAzure as azure
ad9e5f
 from cloudinit.sources import DataSourceOVF as ovf
ad9e5f
 from cloudinit.sources.helpers import openstack
ad9e5f
@@ -81,7 +74,7 @@ def get_parser(parser=None):
ad9e5f
     parser.add_argument(
ad9e5f
         "-O",
ad9e5f
         "--output-kind",
ad9e5f
-        choices=["eni", "netplan", "networkd", "sysconfig", "network-manager"],
ad9e5f
+        choices=["eni", "netplan", "networkd", "sysconfig"],
ad9e5f
         required=True,
ad9e5f
         help="The network config format to emit",
ad9e5f
     )
ad9e5f
@@ -155,9 +148,6 @@ def handle_args(name, args):
ad9e5f
     elif args.output_kind == "sysconfig":
ad9e5f
         r_cls = sysconfig.Renderer
ad9e5f
         config = distro.renderer_configs.get("sysconfig")
ad9e5f
-    elif args.output_kind == "network-manager":
ad9e5f
-        r_cls = network_manager.Renderer
ad9e5f
-        config = distro.renderer_configs.get("network-manager")
ad9e5f
     else:
ad9e5f
         raise RuntimeError("Invalid output_kind")
ad9e5f
 
ad9e5f
diff --git a/cloudinit/net/activators.py b/cloudinit/net/activators.py
ad9e5f
index edbc0c06..e80c26df 100644
ad9e5f
--- a/cloudinit/net/activators.py
ad9e5f
+++ b/cloudinit/net/activators.py
ad9e5f
@@ -1,14 +1,15 @@
ad9e5f
 # This file is part of cloud-init. See LICENSE file for license information.
ad9e5f
 import logging
ad9e5f
+import os
ad9e5f
 from abc import ABC, abstractmethod
ad9e5f
 from typing import Iterable, List, Type
ad9e5f
 
ad9e5f
 from cloudinit import subp, util
ad9e5f
 from cloudinit.net.eni import available as eni_available
ad9e5f
 from cloudinit.net.netplan import available as netplan_available
ad9e5f
-from cloudinit.net.network_manager import available as nm_available
ad9e5f
 from cloudinit.net.network_state import NetworkState
ad9e5f
 from cloudinit.net.networkd import available as networkd_available
ad9e5f
+from cloudinit.net.sysconfig import NM_CFG_FILE
ad9e5f
 
ad9e5f
 LOG = logging.getLogger(__name__)
ad9e5f
 
ad9e5f
@@ -123,24 +124,20 @@ class IfUpDownActivator(NetworkActivator):
ad9e5f
 class NetworkManagerActivator(NetworkActivator):
ad9e5f
     @staticmethod
ad9e5f
     def available(target=None) -> bool:
ad9e5f
-        """Return true if NetworkManager can be used on this system."""
ad9e5f
-        return nm_available(target=target)
ad9e5f
+        """Return true if network manager can be used on this system."""
ad9e5f
+        config_present = os.path.isfile(
ad9e5f
+            subp.target_path(target, path=NM_CFG_FILE)
ad9e5f
+        )
ad9e5f
+        nmcli_present = subp.which("nmcli", target=target)
ad9e5f
+        return config_present and bool(nmcli_present)
ad9e5f
 
ad9e5f
     @staticmethod
ad9e5f
     def bring_up_interface(device_name: str) -> bool:
ad9e5f
-        """Bring up connection using nmcli.
ad9e5f
+        """Bring up interface using nmcli.
ad9e5f
 
ad9e5f
         Return True is successful, otherwise return False
ad9e5f
         """
ad9e5f
-        from cloudinit.net.network_manager import conn_filename
ad9e5f
-
ad9e5f
-        filename = conn_filename(device_name)
ad9e5f
-        cmd = ["nmcli", "connection", "load", filename]
ad9e5f
-        if _alter_interface(cmd, device_name):
ad9e5f
-            cmd = ["nmcli", "connection", "up", "filename", filename]
ad9e5f
-        else:
ad9e5f
-            _alter_interface(["nmcli", "connection", "reload"], device_name)
ad9e5f
-            cmd = ["nmcli", "connection", "up", "ifname", device_name]
ad9e5f
+        cmd = ["nmcli", "connection", "up", "ifname", device_name]
ad9e5f
         return _alter_interface(cmd, device_name)
ad9e5f
 
ad9e5f
     @staticmethod
ad9e5f
@@ -149,7 +146,7 @@ class NetworkManagerActivator(NetworkActivator):
ad9e5f
 
ad9e5f
         Return True is successful, otherwise return False
ad9e5f
         """
ad9e5f
-        cmd = ["nmcli", "device", "disconnect", device_name]
ad9e5f
+        cmd = ["nmcli", "connection", "down", device_name]
ad9e5f
         return _alter_interface(cmd, device_name)
ad9e5f
 
ad9e5f
 
ad9e5f
diff --git a/cloudinit/net/network_manager.py b/cloudinit/net/network_manager.py
ad9e5f
deleted file mode 100644
ad9e5f
index 79b0fe0b..00000000
ad9e5f
--- a/cloudinit/net/network_manager.py
ad9e5f
+++ /dev/null
ad9e5f
@@ -1,377 +0,0 @@
ad9e5f
-# Copyright 2022 Red Hat, Inc.
ad9e5f
-#
ad9e5f
-# Author: Lubomir Rintel <lkundrak@v3.sk>
ad9e5f
-# Fixes and suggestions contributed by James Falcon, Neal Gompa,
ad9e5f
-# Zbigniew Jędrzejewski-Szmek and Emanuele Giuseppe Esposito.
ad9e5f
-#
ad9e5f
-# This file is part of cloud-init. See LICENSE file for license information.
ad9e5f
-
ad9e5f
-import configparser
ad9e5f
-import io
ad9e5f
-import itertools
ad9e5f
-import os
ad9e5f
-import uuid
ad9e5f
-
ad9e5f
-from cloudinit import log as logging
ad9e5f
-from cloudinit import subp, util
ad9e5f
-
ad9e5f
-from . import renderer
ad9e5f
-from .network_state import is_ipv6_addr, subnet_is_ipv6
ad9e5f
-
ad9e5f
-NM_RUN_DIR = "/etc/NetworkManager"
ad9e5f
-NM_LIB_DIR = "/usr/lib/NetworkManager"
ad9e5f
-LOG = logging.getLogger(__name__)
ad9e5f
-
ad9e5f
-
ad9e5f
-class NMConnection:
ad9e5f
-    """Represents a NetworkManager connection profile."""
ad9e5f
-
ad9e5f
-    def __init__(self, con_id):
ad9e5f
-        """
ad9e5f
-        Initializes the connection with some very basic properties,
ad9e5f
-        notably the UUID so that the connection can be referred to.
ad9e5f
-        """
ad9e5f
-
ad9e5f
-        # Chosen by fair dice roll
ad9e5f
-        CI_NM_UUID = uuid.UUID("a3924cb8-09e0-43e9-890b-77972a800108")
ad9e5f
-
ad9e5f
-        self.config = configparser.ConfigParser()
ad9e5f
-        # Identity option name mapping, to achieve case sensitivity
ad9e5f
-        self.config.optionxform = str
ad9e5f
-
ad9e5f
-        self.config["connection"] = {
ad9e5f
-            "id": f"cloud-init {con_id}",
ad9e5f
-            "uuid": str(uuid.uuid5(CI_NM_UUID, con_id)),
ad9e5f
-        }
ad9e5f
-
ad9e5f
-        # This is not actually used anywhere, but may be useful in future
ad9e5f
-        self.config["user"] = {
ad9e5f
-            "org.freedesktop.NetworkManager.origin": "cloud-init"
ad9e5f
-        }
ad9e5f
-
ad9e5f
-    def _set_default(self, section, option, value):
ad9e5f
-        """
ad9e5f
-        Sets a property unless it's already set, ensuring the section
ad9e5f
-        exists.
ad9e5f
-        """
ad9e5f
-
ad9e5f
-        if not self.config.has_section(section):
ad9e5f
-            self.config[section] = {}
ad9e5f
-        if not self.config.has_option(section, option):
ad9e5f
-            self.config[section][option] = value
ad9e5f
-
ad9e5f
-    def _set_ip_method(self, family, subnet_type):
ad9e5f
-        """
ad9e5f
-        Ensures there's appropriate [ipv4]/[ipv6] for given family
ad9e5f
-        appropriate for given configuration type
ad9e5f
-        """
ad9e5f
-
ad9e5f
-        method_map = {
ad9e5f
-            "static": "manual",
ad9e5f
-            "dhcp6": "dhcp",
ad9e5f
-            "ipv6_slaac": "auto",
ad9e5f
-            "ipv6_dhcpv6-stateless": "auto",
ad9e5f
-            "ipv6_dhcpv6-stateful": "auto",
ad9e5f
-            "dhcp4": "auto",
ad9e5f
-            "dhcp": "auto",
ad9e5f
-        }
ad9e5f
-
ad9e5f
-        # Ensure we got an [ipvX] section
ad9e5f
-        self._set_default(family, "method", "disabled")
ad9e5f
-
ad9e5f
-        try:
ad9e5f
-            method = method_map[subnet_type]
ad9e5f
-        except KeyError:
ad9e5f
-            # What else can we do
ad9e5f
-            method = "auto"
ad9e5f
-            self.config[family]["may-fail"] = "true"
ad9e5f
-
ad9e5f
-        # Make sure we don't "downgrade" the method in case
ad9e5f
-        # we got conflicting subnets (e.g. static along with dhcp)
ad9e5f
-        if self.config[family]["method"] == "dhcp":
ad9e5f
-            return
ad9e5f
-        if self.config[family]["method"] == "auto" and method == "manual":
ad9e5f
-            return
ad9e5f
-
ad9e5f
-        self.config[family]["method"] = method
ad9e5f
-        self._set_default(family, "may-fail", "false")
ad9e5f
-        if family == "ipv6":
ad9e5f
-            self._set_default(family, "addr-gen-mode", "stable-privacy")
ad9e5f
-
ad9e5f
-    def _add_numbered(self, section, key_prefix, value):
ad9e5f
-        """
ad9e5f
-        Adds a numbered property, such as address<n> or route<n>, ensuring
ad9e5f
-        the appropriate value gets used for <n>.
ad9e5f
-        """
ad9e5f
-
ad9e5f
-        for index in itertools.count(1):
ad9e5f
-            key = f"{key_prefix}{index}"
ad9e5f
-            if not self.config.has_option(section, key):
ad9e5f
-                self.config[section][key] = value
ad9e5f
-                break
ad9e5f
-
ad9e5f
-    def _add_address(self, family, subnet):
ad9e5f
-        """
ad9e5f
-        Adds an ipv[46]address<n> property.
ad9e5f
-        """
ad9e5f
-
ad9e5f
-        value = subnet["address"] + "/" + str(subnet["prefix"])
ad9e5f
-        self._add_numbered(family, "address", value)
ad9e5f
-
ad9e5f
-    def _add_route(self, family, route):
ad9e5f
-        """
ad9e5f
-        Adds a ipv[46].route<n> property.
ad9e5f
-        """
ad9e5f
-
ad9e5f
-        value = route["network"] + "/" + str(route["prefix"])
ad9e5f
-        if "gateway" in route:
ad9e5f
-            value = value + "," + route["gateway"]
ad9e5f
-        self._add_numbered(family, "route", value)
ad9e5f
-
ad9e5f
-    def _add_nameserver(self, dns):
ad9e5f
-        """
ad9e5f
-        Extends the ipv[46].dns property with a name server.
ad9e5f
-        """
ad9e5f
-
ad9e5f
-        # FIXME: the subnet contains IPv4 and IPv6 name server mixed
ad9e5f
-        # together. We might be getting an IPv6 name server while
ad9e5f
-        # we're dealing with an IPv4 subnet. Sort this out by figuring
ad9e5f
-        # out the correct family and making sure a valid section exist.
ad9e5f
-        family = "ipv6" if is_ipv6_addr(dns) else "ipv4"
ad9e5f
-        self._set_default(family, "method", "disabled")
ad9e5f
-
ad9e5f
-        self._set_default(family, "dns", "")
ad9e5f
-        self.config[family]["dns"] = self.config[family]["dns"] + dns + ";"
ad9e5f
-
ad9e5f
-    def _add_dns_search(self, family, dns_search):
ad9e5f
-        """
ad9e5f
-        Extends the ipv[46].dns-search property with a name server.
ad9e5f
-        """
ad9e5f
-
ad9e5f
-        self._set_default(family, "dns-search", "")
ad9e5f
-        self.config[family]["dns-search"] = (
ad9e5f
-            self.config[family]["dns-search"] + ";".join(dns_search) + ";"
ad9e5f
-        )
ad9e5f
-
ad9e5f
-    def con_uuid(self):
ad9e5f
-        """
ad9e5f
-        Returns the connection UUID
ad9e5f
-        """
ad9e5f
-        return self.config["connection"]["uuid"]
ad9e5f
-
ad9e5f
-    def valid(self):
ad9e5f
-        """
ad9e5f
-        Can this be serialized into a meaningful connection profile?
ad9e5f
-        """
ad9e5f
-        return self.config.has_option("connection", "type")
ad9e5f
-
ad9e5f
-    @staticmethod
ad9e5f
-    def mac_addr(addr):
ad9e5f
-        """
ad9e5f
-        Sanitize a MAC address.
ad9e5f
-        """
ad9e5f
-        return addr.replace("-", ":").upper()
ad9e5f
-
ad9e5f
-    def render_interface(self, iface, renderer):
ad9e5f
-        """
ad9e5f
-        Integrate information from network state interface information
ad9e5f
-        into the connection. Most of the work is done here.
ad9e5f
-        """
ad9e5f
-
ad9e5f
-        # Initialize type & connectivity
ad9e5f
-        _type_map = {
ad9e5f
-            "physical": "ethernet",
ad9e5f
-            "vlan": "vlan",
ad9e5f
-            "bond": "bond",
ad9e5f
-            "bridge": "bridge",
ad9e5f
-            "infiniband": "infiniband",
ad9e5f
-            "loopback": None,
ad9e5f
-        }
ad9e5f
-
ad9e5f
-        if_type = _type_map[iface["type"]]
ad9e5f
-        if if_type is None:
ad9e5f
-            return
ad9e5f
-        if "bond-master" in iface:
ad9e5f
-            slave_type = "bond"
ad9e5f
-        else:
ad9e5f
-            slave_type = None
ad9e5f
-
ad9e5f
-        self.config["connection"]["type"] = if_type
ad9e5f
-        if slave_type is not None:
ad9e5f
-            self.config["connection"]["slave-type"] = slave_type
ad9e5f
-            self.config["connection"]["master"] = renderer.con_ref(
ad9e5f
-                iface[slave_type + "-master"]
ad9e5f
-            )
ad9e5f
-
ad9e5f
-        # Add type specific-section
ad9e5f
-        self.config[if_type] = {}
ad9e5f
-
ad9e5f
-        # These are the interface properties that map nicely
ad9e5f
-        # to NetworkManager properties
ad9e5f
-        _prop_map = {
ad9e5f
-            "bond": {
ad9e5f
-                "mode": "bond-mode",
ad9e5f
-                "miimon": "bond_miimon",
ad9e5f
-                "xmit_hash_policy": "bond-xmit-hash-policy",
ad9e5f
-                "num_grat_arp": "bond-num-grat-arp",
ad9e5f
-                "downdelay": "bond-downdelay",
ad9e5f
-                "updelay": "bond-updelay",
ad9e5f
-                "fail_over_mac": "bond-fail-over-mac",
ad9e5f
-                "primary_reselect": "bond-primary-reselect",
ad9e5f
-                "primary": "bond-primary",
ad9e5f
-            },
ad9e5f
-            "bridge": {
ad9e5f
-                "stp": "bridge_stp",
ad9e5f
-                "priority": "bridge_bridgeprio",
ad9e5f
-            },
ad9e5f
-            "vlan": {
ad9e5f
-                "id": "vlan_id",
ad9e5f
-            },
ad9e5f
-            "ethernet": {},
ad9e5f
-            "infiniband": {},
ad9e5f
-        }
ad9e5f
-
ad9e5f
-        device_mtu = iface["mtu"]
ad9e5f
-        ipv4_mtu = None
ad9e5f
-
ad9e5f
-        # Deal with Layer 3 configuration
ad9e5f
-        for subnet in iface["subnets"]:
ad9e5f
-            family = "ipv6" if subnet_is_ipv6(subnet) else "ipv4"
ad9e5f
-
ad9e5f
-            self._set_ip_method(family, subnet["type"])
ad9e5f
-            if "address" in subnet:
ad9e5f
-                self._add_address(family, subnet)
ad9e5f
-            if "gateway" in subnet:
ad9e5f
-                self.config[family]["gateway"] = subnet["gateway"]
ad9e5f
-            for route in subnet["routes"]:
ad9e5f
-                self._add_route(family, route)
ad9e5f
-            if "dns_nameservers" in subnet:
ad9e5f
-                for nameserver in subnet["dns_nameservers"]:
ad9e5f
-                    self._add_nameserver(nameserver)
ad9e5f
-            if "dns_search" in subnet:
ad9e5f
-                self._add_dns_search(family, subnet["dns_search"])
ad9e5f
-            if family == "ipv4" and "mtu" in subnet:
ad9e5f
-                ipv4_mtu = subnet["mtu"]
ad9e5f
-
ad9e5f
-        if ipv4_mtu is None:
ad9e5f
-            ipv4_mtu = device_mtu
ad9e5f
-        if not ipv4_mtu == device_mtu:
ad9e5f
-            LOG.warning(
ad9e5f
-                "Network config: ignoring %s device-level mtu:%s"
ad9e5f
-                " because ipv4 subnet-level mtu:%s provided.",
ad9e5f
-                iface["name"],
ad9e5f
-                device_mtu,
ad9e5f
-                ipv4_mtu,
ad9e5f
-            )
ad9e5f
-
ad9e5f
-        # Parse type-specific properties
ad9e5f
-        for nm_prop, key in _prop_map[if_type].items():
ad9e5f
-            if key not in iface:
ad9e5f
-                continue
ad9e5f
-            if iface[key] is None:
ad9e5f
-                continue
ad9e5f
-            if isinstance(iface[key], bool):
ad9e5f
-                self.config[if_type][nm_prop] = (
ad9e5f
-                    "true" if iface[key] else "false"
ad9e5f
-                )
ad9e5f
-            else:
ad9e5f
-                self.config[if_type][nm_prop] = str(iface[key])
ad9e5f
-
ad9e5f
-        # These ones need special treatment
ad9e5f
-        if if_type == "ethernet":
ad9e5f
-            if iface["wakeonlan"] is True:
ad9e5f
-                # NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC
ad9e5f
-                self.config["ethernet"]["wake-on-lan"] = str(0x40)
ad9e5f
-            if ipv4_mtu is not None:
ad9e5f
-                self.config["ethernet"]["mtu"] = str(ipv4_mtu)
ad9e5f
-            if iface["mac_address"] is not None:
ad9e5f
-                self.config["ethernet"]["mac-address"] = self.mac_addr(
ad9e5f
-                    iface["mac_address"]
ad9e5f
-                )
ad9e5f
-        if if_type == "vlan" and "vlan-raw-device" in iface:
ad9e5f
-            self.config["vlan"]["parent"] = renderer.con_ref(
ad9e5f
-                iface["vlan-raw-device"]
ad9e5f
-            )
ad9e5f
-        if if_type == "bridge":
ad9e5f
-            # Bridge is ass-backwards compared to bond
ad9e5f
-            for port in iface["bridge_ports"]:
ad9e5f
-                port = renderer.get_conn(port)
ad9e5f
-                port._set_default("connection", "slave-type", "bridge")
ad9e5f
-                port._set_default("connection", "master", self.con_uuid())
ad9e5f
-            if iface["mac_address"] is not None:
ad9e5f
-                self.config["bridge"]["mac-address"] = self.mac_addr(
ad9e5f
-                    iface["mac_address"]
ad9e5f
-                )
ad9e5f
-        if if_type == "infiniband" and ipv4_mtu is not None:
ad9e5f
-            self.config["infiniband"]["transport-mode"] = "datagram"
ad9e5f
-            self.config["infiniband"]["mtu"] = str(ipv4_mtu)
ad9e5f
-            if iface["mac_address"] is not None:
ad9e5f
-                self.config["infiniband"]["mac-address"] = self.mac_addr(
ad9e5f
-                    iface["mac_address"]
ad9e5f
-                )
ad9e5f
-
ad9e5f
-        # Finish up
ad9e5f
-        if if_type == "bridge" or not self.config.has_option(
ad9e5f
-            if_type, "mac-address"
ad9e5f
-        ):
ad9e5f
-            self.config["connection"]["interface-name"] = iface["name"]
ad9e5f
-
ad9e5f
-    def dump(self):
ad9e5f
-        """
ad9e5f
-        Stringify.
ad9e5f
-        """
ad9e5f
-
ad9e5f
-        buf = io.StringIO()
ad9e5f
-        self.config.write(buf, space_around_delimiters=False)
ad9e5f
-        header = "# Generated by cloud-init. Changes will be lost.\n\n"
ad9e5f
-        return header + buf.getvalue()
ad9e5f
-
ad9e5f
-
ad9e5f
-class Renderer(renderer.Renderer):
ad9e5f
-    """Renders network information in a NetworkManager keyfile format."""
ad9e5f
-
ad9e5f
-    def __init__(self, config=None):
ad9e5f
-        self.connections = {}
ad9e5f
-
ad9e5f
-    def get_conn(self, con_id):
ad9e5f
-        return self.connections[con_id]
ad9e5f
-
ad9e5f
-    def con_ref(self, con_id):
ad9e5f
-        if con_id in self.connections:
ad9e5f
-            return self.connections[con_id].con_uuid()
ad9e5f
-        else:
ad9e5f
-            # Well, what can we do...
ad9e5f
-            return con_id
ad9e5f
-
ad9e5f
-    def render_network_state(self, network_state, templates=None, target=None):
ad9e5f
-        # First pass makes sure there's NMConnections for all known
ad9e5f
-        # interfaces that have UUIDs that can be linked to from related
ad9e5f
-        # interfaces
ad9e5f
-        for iface in network_state.iter_interfaces():
ad9e5f
-            self.connections[iface["name"]] = NMConnection(iface["name"])
ad9e5f
-
ad9e5f
-        # Now render the actual interface configuration
ad9e5f
-        for iface in network_state.iter_interfaces():
ad9e5f
-            conn = self.connections[iface["name"]]
ad9e5f
-            conn.render_interface(iface, self)
ad9e5f
-
ad9e5f
-        # And finally write the files
ad9e5f
-        for con_id, conn in self.connections.items():
ad9e5f
-            if not conn.valid():
ad9e5f
-                continue
ad9e5f
-            name = conn_filename(con_id, target)
ad9e5f
-            util.write_file(name, conn.dump(), 0o600)
ad9e5f
-
ad9e5f
-
ad9e5f
-def conn_filename(con_id, target=None):
ad9e5f
-    target_con_dir = subp.target_path(target, NM_RUN_DIR)
ad9e5f
-    con_file = f"cloud-init-{con_id}.nmconnection"
ad9e5f
-    return f"{target_con_dir}/system-connections/{con_file}"
ad9e5f
-
ad9e5f
-
ad9e5f
-def available(target=None):
ad9e5f
-    target_nm_dir = subp.target_path(target, NM_LIB_DIR)
ad9e5f
-    return os.path.exists(target_nm_dir)
ad9e5f
-
ad9e5f
-
ad9e5f
-# vi: ts=4 expandtab
ad9e5f
diff --git a/cloudinit/net/renderers.py b/cloudinit/net/renderers.py
ad9e5f
index 7edc34b5..c755f04c 100644
ad9e5f
--- a/cloudinit/net/renderers.py
ad9e5f
+++ b/cloudinit/net/renderers.py
ad9e5f
@@ -8,7 +8,6 @@ from . import (
ad9e5f
     freebsd,
ad9e5f
     netbsd,
ad9e5f
     netplan,
ad9e5f
-    network_manager,
ad9e5f
     networkd,
ad9e5f
     openbsd,
ad9e5f
     renderer,
ad9e5f
@@ -20,7 +19,6 @@ NAME_TO_RENDERER = {
ad9e5f
     "freebsd": freebsd,
ad9e5f
     "netbsd": netbsd,
ad9e5f
     "netplan": netplan,
ad9e5f
-    "network-manager": network_manager,
ad9e5f
     "networkd": networkd,
ad9e5f
     "openbsd": openbsd,
ad9e5f
     "sysconfig": sysconfig,
ad9e5f
@@ -30,7 +28,6 @@ DEFAULT_PRIORITY = [
ad9e5f
     "eni",
ad9e5f
     "sysconfig",
ad9e5f
     "netplan",
ad9e5f
-    "network-manager",
ad9e5f
     "freebsd",
ad9e5f
     "netbsd",
ad9e5f
     "openbsd",
ad9e5f
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
ad9e5f
index c3b0c795..362e8d19 100644
ad9e5f
--- a/cloudinit/net/sysconfig.py
ad9e5f
+++ b/cloudinit/net/sysconfig.py
ad9e5f
@@ -5,6 +5,8 @@ import io
ad9e5f
 import os
ad9e5f
 import re
ad9e5f
 
ad9e5f
+from configobj import ConfigObj
ad9e5f
+
ad9e5f
 from cloudinit import log as logging
ad9e5f
 from cloudinit import subp, util
ad9e5f
 from cloudinit.distros.parsers import networkmanager_conf, resolv_conf
ad9e5f
@@ -64,6 +66,24 @@ def _quote_value(value):
ad9e5f
         return value
ad9e5f
 
ad9e5f
 
ad9e5f
+def enable_ifcfg_rh(path):
ad9e5f
+    """Add ifcfg-rh to NetworkManager.cfg plugins if main section is present"""
ad9e5f
+    config = ConfigObj(path)
ad9e5f
+    if "main" in config:
ad9e5f
+        if "plugins" in config["main"]:
ad9e5f
+            if "ifcfg-rh" in config["main"]["plugins"]:
ad9e5f
+                return
ad9e5f
+        else:
ad9e5f
+            config["main"]["plugins"] = []
ad9e5f
+
ad9e5f
+        if isinstance(config["main"]["plugins"], list):
ad9e5f
+            config["main"]["plugins"].append("ifcfg-rh")
ad9e5f
+        else:
ad9e5f
+            config["main"]["plugins"] = [config["main"]["plugins"], "ifcfg-rh"]
ad9e5f
+        config.write()
ad9e5f
+        LOG.debug("Enabled ifcfg-rh NetworkManager plugins")
ad9e5f
+
ad9e5f
+
ad9e5f
 class ConfigMap(object):
ad9e5f
     """Sysconfig like dictionary object."""
ad9e5f
 
ad9e5f
@@ -1011,6 +1031,8 @@ class Renderer(renderer.Renderer):
ad9e5f
             netrules_content = self._render_persistent_net(network_state)
ad9e5f
             netrules_path = subp.target_path(target, self.netrules_path)
ad9e5f
             util.write_file(netrules_path, netrules_content, file_mode)
ad9e5f
+        if available_nm(target=target):
ad9e5f
+            enable_ifcfg_rh(subp.target_path(target, path=NM_CFG_FILE))
ad9e5f
 
ad9e5f
         sysconfig_path = subp.target_path(target, templates.get("control"))
ad9e5f
         # Distros configuring /etc/sysconfig/network as a file e.g. Centos
ad9e5f
@@ -1049,9 +1071,14 @@ def _supported_vlan_names(rdev, vid):
ad9e5f
 
ad9e5f
 
ad9e5f
 def available(target=None):
ad9e5f
-    if not util.system_info()["variant"] in KNOWN_DISTROS:
ad9e5f
-        return False
ad9e5f
+    sysconfig = available_sysconfig(target=target)
ad9e5f
+    nm = available_nm(target=target)
ad9e5f
+    return util.system_info()["variant"] in KNOWN_DISTROS and any(
ad9e5f
+        [nm, sysconfig]
ad9e5f
+    )
ad9e5f
+
ad9e5f
 
ad9e5f
+def available_sysconfig(target=None):
ad9e5f
     expected = ["ifup", "ifdown"]
ad9e5f
     search = ["/sbin", "/usr/sbin"]
ad9e5f
     for p in expected:
ad9e5f
@@ -1068,4 +1095,10 @@ def available(target=None):
ad9e5f
     return False
ad9e5f
 
ad9e5f
 
ad9e5f
+def available_nm(target=None):
ad9e5f
+    if not os.path.isfile(subp.target_path(target, path=NM_CFG_FILE)):
ad9e5f
+        return False
ad9e5f
+    return True
ad9e5f
+
ad9e5f
+
ad9e5f
 # vi: ts=4 expandtab
ad9e5f
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
ad9e5f
index ef21ad76..591241b3 100644
ad9e5f
--- a/tests/unittests/test_net.py
ad9e5f
+++ b/tests/unittests/test_net.py
ad9e5f
@@ -21,7 +21,6 @@ from cloudinit.net import (
ad9e5f
     interface_has_own_mac,
ad9e5f
     natural_sort_key,
ad9e5f
     netplan,
ad9e5f
-    network_manager,
ad9e5f
     network_state,
ad9e5f
     networkd,
ad9e5f
     renderers,
ad9e5f
@@ -612,37 +611,6 @@ dns = none
ad9e5f
                 ),
ad9e5f
             ),
ad9e5f
         ],
ad9e5f
-        "expected_network_manager": [
ad9e5f
-            (
ad9e5f
-                "".join(
ad9e5f
-                    [
ad9e5f
-                        "etc/NetworkManager/system-connections",
ad9e5f
-                        "/cloud-init-eth0.nmconnection",
ad9e5f
-                    ]
ad9e5f
-                ),
ad9e5f
-                """
ad9e5f
-# Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-[connection]
ad9e5f
-id=cloud-init eth0
ad9e5f
-uuid=1dd9a779-d327-56e1-8454-c65e2556c12c
ad9e5f
-type=ethernet
ad9e5f
-
ad9e5f
-[user]
ad9e5f
-org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-[ethernet]
ad9e5f
-mac-address=FA:16:3E:ED:9A:59
ad9e5f
-
ad9e5f
-[ipv4]
ad9e5f
-method=manual
ad9e5f
-may-fail=false
ad9e5f
-address1=172.19.1.34/22
ad9e5f
-route1=0.0.0.0/0,172.19.3.254
ad9e5f
-
ad9e5f
-""".lstrip(),
ad9e5f
-            ),
ad9e5f
-        ],
ad9e5f
     },
ad9e5f
     {
ad9e5f
         "in_data": {
ad9e5f
@@ -1105,50 +1073,6 @@ NETWORK_CONFIGS = {
ad9e5f
                 USERCTL=no"""
ad9e5f
             ),
ad9e5f
         },
ad9e5f
-        "expected_network_manager": {
ad9e5f
-            "cloud-init-eth1.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth1
ad9e5f
-                uuid=3c50eb47-7260-5a6d-801d-bd4f587d6b58
ad9e5f
-                type=ethernet
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=CF:D6:AF:48:E8:80
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-eth99.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth99
ad9e5f
-                uuid=b1b88000-1f03-5360-8377-1a2205efffb4
ad9e5f
-                type=ethernet
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=C0:D6:9F:2C:E8:80
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=auto
ad9e5f
-                may-fail=false
ad9e5f
-                address1=192.168.21.3/24
ad9e5f
-                route1=0.0.0.0/0,65.61.151.37
ad9e5f
-                dns=8.8.8.8;8.8.4.4;
ad9e5f
-                dns-search=barley.maas;sach.maas;
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-        },
ad9e5f
         "yaml": textwrap.dedent(
ad9e5f
             """
ad9e5f
             version: 1
ad9e5f
@@ -1221,34 +1145,6 @@ NETWORK_CONFIGS = {
ad9e5f
                 STARTMODE=auto"""
ad9e5f
             )
ad9e5f
         },
ad9e5f
-        "expected_network_manager": {
ad9e5f
-            "cloud-init-iface0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init iface0
ad9e5f
-                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
ad9e5f
-                type=ethernet
ad9e5f
-                interface-name=iface0
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=auto
ad9e5f
-                may-fail=false
ad9e5f
-
ad9e5f
-                [ipv6]
ad9e5f
-                method=dhcp
ad9e5f
-                may-fail=false
ad9e5f
-                addr-gen-mode=stable-privacy
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-        },
ad9e5f
         "yaml": textwrap.dedent(
ad9e5f
             """\
ad9e5f
             version: 1
ad9e5f
@@ -1351,37 +1247,6 @@ NETWORK_CONFIGS = {
ad9e5f
                 """
ad9e5f
             ),
ad9e5f
         },
ad9e5f
-        "expected_network_manager": {
ad9e5f
-            "cloud-init-iface0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init iface0
ad9e5f
-                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
ad9e5f
-                type=ethernet
ad9e5f
-                interface-name=iface0
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mtu=9000
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=manual
ad9e5f
-                may-fail=false
ad9e5f
-                address1=192.168.14.2/24
ad9e5f
-
ad9e5f
-                [ipv6]
ad9e5f
-                method=manual
ad9e5f
-                may-fail=false
ad9e5f
-                addr-gen-mode=stable-privacy
ad9e5f
-                address1=2001:1::1/64
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-        },
ad9e5f
     },
ad9e5f
     "v6_and_v4": {
ad9e5f
         "expected_sysconfig_opensuse": {
ad9e5f
@@ -1392,34 +1257,6 @@ NETWORK_CONFIGS = {
ad9e5f
                 STARTMODE=auto"""
ad9e5f
             )
ad9e5f
         },
ad9e5f
-        "expected_network_manager": {
ad9e5f
-            "cloud-init-iface0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init iface0
ad9e5f
-                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
ad9e5f
-                type=ethernet
ad9e5f
-                interface-name=iface0
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-
ad9e5f
-                [ipv6]
ad9e5f
-                method=dhcp
ad9e5f
-                may-fail=false
ad9e5f
-                addr-gen-mode=stable-privacy
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=auto
ad9e5f
-                may-fail=false
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-        },
ad9e5f
         "yaml": textwrap.dedent(
ad9e5f
             """\
ad9e5f
             version: 1
ad9e5f
@@ -1493,30 +1330,6 @@ NETWORK_CONFIGS = {
ad9e5f
                 """
ad9e5f
             ),
ad9e5f
         },
ad9e5f
-        "expected_network_manager": {
ad9e5f
-            "cloud-init-iface0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init iface0
ad9e5f
-                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
ad9e5f
-                type=ethernet
ad9e5f
-                interface-name=iface0
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-
ad9e5f
-                [ipv6]
ad9e5f
-                method=dhcp
ad9e5f
-                may-fail=false
ad9e5f
-                addr-gen-mode=stable-privacy
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-        },
ad9e5f
     },
ad9e5f
     "dhcpv6_accept_ra": {
ad9e5f
         "expected_eni": textwrap.dedent(
ad9e5f
@@ -1724,30 +1537,6 @@ NETWORK_CONFIGS = {
ad9e5f
             """
ad9e5f
             ),
ad9e5f
         },
ad9e5f
-        "expected_network_manager": {
ad9e5f
-            "cloud-init-iface0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init iface0
ad9e5f
-                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
ad9e5f
-                type=ethernet
ad9e5f
-                interface-name=iface0
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-
ad9e5f
-                [ipv6]
ad9e5f
-                method=auto
ad9e5f
-                may-fail=false
ad9e5f
-                addr-gen-mode=stable-privacy
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-        },
ad9e5f
     },
ad9e5f
     "static6": {
ad9e5f
         "yaml": textwrap.dedent(
ad9e5f
@@ -1836,30 +1625,6 @@ NETWORK_CONFIGS = {
ad9e5f
             """
ad9e5f
             ),
ad9e5f
         },
ad9e5f
-        "expected_network_manager": {
ad9e5f
-            "cloud-init-iface0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init iface0
ad9e5f
-                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
ad9e5f
-                type=ethernet
ad9e5f
-                interface-name=iface0
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-
ad9e5f
-                [ipv6]
ad9e5f
-                method=auto
ad9e5f
-                may-fail=false
ad9e5f
-                addr-gen-mode=stable-privacy
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-        },
ad9e5f
     },
ad9e5f
     "dhcpv6_stateful": {
ad9e5f
         "expected_eni": textwrap.dedent(
ad9e5f
@@ -1959,29 +1724,6 @@ NETWORK_CONFIGS = {
ad9e5f
             """
ad9e5f
             ),
ad9e5f
         },
ad9e5f
-        "expected_network_manager": {
ad9e5f
-            "cloud-init-iface0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init iface0
ad9e5f
-                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
ad9e5f
-                type=ethernet
ad9e5f
-                interface-name=iface0
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=auto
ad9e5f
-                may-fail=false
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-        },
ad9e5f
         "yaml_v2": textwrap.dedent(
ad9e5f
             """\
ad9e5f
             version: 2
ad9e5f
@@ -2035,30 +1777,6 @@ NETWORK_CONFIGS = {
ad9e5f
             """
ad9e5f
             ),
ad9e5f
         },
ad9e5f
-        "expected_network_manager": {
ad9e5f
-            "cloud-init-iface0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init iface0
ad9e5f
-                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
ad9e5f
-                type=ethernet
ad9e5f
-                interface-name=iface0
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                wake-on-lan=64
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=auto
ad9e5f
-                may-fail=false
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-        },
ad9e5f
         "yaml_v2": textwrap.dedent(
ad9e5f
             """\
ad9e5f
             version: 2
ad9e5f
@@ -2497,254 +2215,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
ad9e5f
                 USERCTL=no"""
ad9e5f
             ),
ad9e5f
         },
ad9e5f
-        "expected_network_manager": {
ad9e5f
-            "cloud-init-eth3.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth3
ad9e5f
-                uuid=b7e95dda-7746-5bf8-bf33-6e5f3c926790
ad9e5f
-                type=ethernet
ad9e5f
-                slave-type=bridge
ad9e5f
-                master=dee46ce4-af7a-5e7c-aa08-b25533ae9213
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=66:BB:9F:2C:E8:80
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-eth5.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth5
ad9e5f
-                uuid=5fda13c7-9942-5e90-a41b-1d043bd725dc
ad9e5f
-                type=ethernet
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=98:BB:9F:2C:E8:8A
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=auto
ad9e5f
-                may-fail=false
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-ib0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init ib0
ad9e5f
-                uuid=11a1dda7-78b4-5529-beba-d9b5f549ad7b
ad9e5f
-                type=infiniband
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [infiniband]
ad9e5f
-                transport-mode=datagram
ad9e5f
-                mtu=9000
ad9e5f
-                mac-address=A0:00:02:20:FE:80:00:00:00:00:00:00:EC:0D:9A:03:00:15:E2:C1
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=manual
ad9e5f
-                may-fail=false
ad9e5f
-                address1=192.168.200.7/24
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-bond0.200.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init bond0.200
ad9e5f
-                uuid=88984a9c-ff22-5233-9267-86315e0acaa7
ad9e5f
-                type=vlan
ad9e5f
-                interface-name=bond0.200
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [vlan]
ad9e5f
-                id=200
ad9e5f
-                parent=54317911-f840-516b-a10d-82cb4c1f075c
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=auto
ad9e5f
-                may-fail=false
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-eth0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth0
ad9e5f
-                uuid=1dd9a779-d327-56e1-8454-c65e2556c12c
ad9e5f
-                type=ethernet
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=C0:D6:9F:2C:E8:80
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-eth4.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth4
ad9e5f
-                uuid=e27e4959-fb50-5580-b9a4-2073554627b9
ad9e5f
-                type=ethernet
ad9e5f
-                slave-type=bridge
ad9e5f
-                master=dee46ce4-af7a-5e7c-aa08-b25533ae9213
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=98:BB:9F:2C:E8:80
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-eth1.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth1
ad9e5f
-                uuid=3c50eb47-7260-5a6d-801d-bd4f587d6b58
ad9e5f
-                type=ethernet
ad9e5f
-                slave-type=bond
ad9e5f
-                master=54317911-f840-516b-a10d-82cb4c1f075c
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=AA:D6:9F:2C:E8:80
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-br0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init br0
ad9e5f
-                uuid=dee46ce4-af7a-5e7c-aa08-b25533ae9213
ad9e5f
-                type=bridge
ad9e5f
-                interface-name=br0
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [bridge]
ad9e5f
-                stp=false
ad9e5f
-                priority=22
ad9e5f
-                mac-address=BB:BB:BB:BB:BB:AA
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=manual
ad9e5f
-                may-fail=false
ad9e5f
-                address1=192.168.14.2/24
ad9e5f
-
ad9e5f
-                [ipv6]
ad9e5f
-                method=manual
ad9e5f
-                may-fail=false
ad9e5f
-                addr-gen-mode=stable-privacy
ad9e5f
-                address1=2001:1::1/64
ad9e5f
-                route1=::/0,2001:4800:78ff:1b::1
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-eth0.101.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth0.101
ad9e5f
-                uuid=b5acec5e-db80-5935-8b02-0d5619fc42bf
ad9e5f
-                type=vlan
ad9e5f
-                interface-name=eth0.101
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [vlan]
ad9e5f
-                id=101
ad9e5f
-                parent=1dd9a779-d327-56e1-8454-c65e2556c12c
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=manual
ad9e5f
-                may-fail=false
ad9e5f
-                address1=192.168.0.2/24
ad9e5f
-                gateway=192.168.0.1
ad9e5f
-                dns=192.168.0.10;10.23.23.134;
ad9e5f
-                dns-search=barley.maas;sacchromyces.maas;brettanomyces.maas;
ad9e5f
-                address2=192.168.2.10/24
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-bond0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init bond0
ad9e5f
-                uuid=54317911-f840-516b-a10d-82cb4c1f075c
ad9e5f
-                type=bond
ad9e5f
-                interface-name=bond0
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [bond]
ad9e5f
-                mode=active-backup
ad9e5f
-                miimon=100
ad9e5f
-                xmit_hash_policy=layer3+4
ad9e5f
-
ad9e5f
-                [ipv6]
ad9e5f
-                method=dhcp
ad9e5f
-                may-fail=false
ad9e5f
-                addr-gen-mode=stable-privacy
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-eth2.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth2
ad9e5f
-                uuid=5559a242-3421-5fdd-896e-9cb8313d5804
ad9e5f
-                type=ethernet
ad9e5f
-                slave-type=bond
ad9e5f
-                master=54317911-f840-516b-a10d-82cb4c1f075c
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=C0:BB:9F:2C:E8:80
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-        },
ad9e5f
         "yaml": textwrap.dedent(
ad9e5f
             """
ad9e5f
             version: 1
ad9e5f
@@ -2933,10 +2403,10 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
ad9e5f
                   - type: static
ad9e5f
                     address: 2001:1::1/92
ad9e5f
                     routes:
ad9e5f
-                        - gateway: 2001:67c:1562::1
ad9e5f
+                        - gateway: 2001:67c:1562:1
ad9e5f
                           network: 2001:67c:1
ad9e5f
                           netmask: "ffff:ffff::"
ad9e5f
-                        - gateway: 3001:67c:15::1
ad9e5f
+                        - gateway: 3001:67c:1562:1
ad9e5f
                           network: 3001:67c:1
ad9e5f
                           netmask: "ffff:ffff::"
ad9e5f
                           metric: 10000
ad9e5f
@@ -2981,10 +2451,10 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
ad9e5f
                      -   to: 10.1.3.0/24
ad9e5f
                          via: 192.168.0.3
ad9e5f
                      -   to: 2001:67c:1/32
ad9e5f
-                         via: 2001:67c:1562::1
ad9e5f
+                         via: 2001:67c:1562:1
ad9e5f
                      -   metric: 10000
ad9e5f
                          to: 3001:67c:1/32
ad9e5f
-                         via: 3001:67c:15::1
ad9e5f
+                         via: 3001:67c:1562:1
ad9e5f
         """
ad9e5f
         ),
ad9e5f
         "expected_eni": textwrap.dedent(
ad9e5f
@@ -3044,11 +2514,11 @@ iface bond0 inet static
ad9e5f
 # control-alias bond0
ad9e5f
 iface bond0 inet6 static
ad9e5f
     address 2001:1::1/92
ad9e5f
-    post-up route add -A inet6 2001:67c:1/32 gw 2001:67c:1562::1 || true
ad9e5f
-    pre-down route del -A inet6 2001:67c:1/32 gw 2001:67c:1562::1 || true
ad9e5f
-    post-up route add -A inet6 3001:67c:1/32 gw 3001:67c:15::1 metric 10000 \
ad9e5f
+    post-up route add -A inet6 2001:67c:1/32 gw 2001:67c:1562:1 || true
ad9e5f
+    pre-down route del -A inet6 2001:67c:1/32 gw 2001:67c:1562:1 || true
ad9e5f
+    post-up route add -A inet6 3001:67c:1/32 gw 3001:67c:1562:1 metric 10000 \
ad9e5f
 || true
ad9e5f
-    pre-down route del -A inet6 3001:67c:1/32 gw 3001:67c:15::1 metric 10000 \
ad9e5f
+    pre-down route del -A inet6 3001:67c:1/32 gw 3001:67c:1562:1 metric 10000 \
ad9e5f
 || true
ad9e5f
         """
ad9e5f
         ),
ad9e5f
@@ -3091,8 +2561,8 @@ iface bond0 inet6 static
ad9e5f
                 -   to: 2001:67c:1562:8007::1/64
ad9e5f
                     via: 2001:67c:1562:8007::aac:40b2
ad9e5f
                 -   metric: 10000
ad9e5f
-                    to: 3001:67c:15:8007::1/64
ad9e5f
-                    via: 3001:67c:15:8007::aac:40b2
ad9e5f
+                    to: 3001:67c:1562:8007::1/64
ad9e5f
+                    via: 3001:67c:1562:8007::aac:40b2
ad9e5f
             """
ad9e5f
         ),
ad9e5f
         "expected_netplan-v2": textwrap.dedent(
ad9e5f
@@ -3124,8 +2594,8 @@ iface bond0 inet6 static
ad9e5f
                      -   to: 2001:67c:1562:8007::1/64
ad9e5f
                          via: 2001:67c:1562:8007::aac:40b2
ad9e5f
                      -   metric: 10000
ad9e5f
-                         to: 3001:67c:15:8007::1/64
ad9e5f
-                         via: 3001:67c:15:8007::aac:40b2
ad9e5f
+                         to: 3001:67c:1562:8007::1/64
ad9e5f
+                         via: 3001:67c:1562:8007::aac:40b2
ad9e5f
              ethernets:
ad9e5f
                  eth0:
ad9e5f
                      match:
ad9e5f
@@ -3224,8 +2694,8 @@ iface bond0 inet6 static
ad9e5f
                 """\
ad9e5f
         # Created by cloud-init on instance boot automatically, do not edit.
ad9e5f
         #
ad9e5f
-        2001:67c:1/32 via 2001:67c:1562::1  dev bond0
ad9e5f
-        3001:67c:1/32 via 3001:67c:15::1 metric 10000 dev bond0
ad9e5f
+        2001:67c:1/32 via 2001:67c:1562:1  dev bond0
ad9e5f
+        3001:67c:1/32 via 3001:67c:1562:1 metric 10000 dev bond0
ad9e5f
             """
ad9e5f
             ),
ad9e5f
             "route-bond0": textwrap.dedent(
ad9e5f
@@ -3248,88 +2718,6 @@ iface bond0 inet6 static
ad9e5f
         """
ad9e5f
             ),
ad9e5f
         },
ad9e5f
-        "expected_network_manager": {
ad9e5f
-            "cloud-init-bond0s0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init bond0s0
ad9e5f
-                uuid=09d0b5b9-67e7-5577-a1af-74d1cf17a71e
ad9e5f
-                type=ethernet
ad9e5f
-                slave-type=bond
ad9e5f
-                master=54317911-f840-516b-a10d-82cb4c1f075c
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=AA:BB:CC:DD:E8:00
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-bond0s1.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init bond0s1
ad9e5f
-                uuid=4d9aca96-b515-5630-ad83-d13daac7f9d0
ad9e5f
-                type=ethernet
ad9e5f
-                slave-type=bond
ad9e5f
-                master=54317911-f840-516b-a10d-82cb4c1f075c
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=AA:BB:CC:DD:E8:01
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-bond0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init bond0
ad9e5f
-                uuid=54317911-f840-516b-a10d-82cb4c1f075c
ad9e5f
-                type=bond
ad9e5f
-                interface-name=bond0
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [bond]
ad9e5f
-                mode=active-backup
ad9e5f
-                miimon=100
ad9e5f
-                xmit_hash_policy=layer3+4
ad9e5f
-                num_grat_arp=5
ad9e5f
-                downdelay=10
ad9e5f
-                updelay=20
ad9e5f
-                fail_over_mac=active
ad9e5f
-                primary_reselect=always
ad9e5f
-                primary=bond0s0
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=manual
ad9e5f
-                may-fail=false
ad9e5f
-                address1=192.168.0.2/24
ad9e5f
-                gateway=192.168.0.1
ad9e5f
-                route1=10.1.3.0/24,192.168.0.3
ad9e5f
-                address2=192.168.1.2/24
ad9e5f
-
ad9e5f
-                [ipv6]
ad9e5f
-                method=manual
ad9e5f
-                may-fail=false
ad9e5f
-                addr-gen-mode=stable-privacy
ad9e5f
-                address1=2001:1::1/92
ad9e5f
-                route1=2001:67c:1/32,2001:67c:1562::1
ad9e5f
-                route2=3001:67c:1/32,3001:67c:15::1
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-        },
ad9e5f
     },
ad9e5f
     "vlan": {
ad9e5f
         "yaml": textwrap.dedent(
ad9e5f
@@ -3413,58 +2801,6 @@ iface bond0 inet6 static
ad9e5f
                 VLAN=yes"""
ad9e5f
             ),
ad9e5f
         },
ad9e5f
-        "expected_network_manager": {
ad9e5f
-            "cloud-init-en0.99.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init en0.99
ad9e5f
-                uuid=f594e2ed-f107-51df-b225-1dc530a5356b
ad9e5f
-                type=vlan
ad9e5f
-                interface-name=en0.99
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [vlan]
ad9e5f
-                id=99
ad9e5f
-                parent=e0ca478b-8d84-52ab-8fae-628482c629b5
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=manual
ad9e5f
-                may-fail=false
ad9e5f
-                address1=192.168.2.2/24
ad9e5f
-                address2=192.168.1.2/24
ad9e5f
-                gateway=192.168.1.1
ad9e5f
-
ad9e5f
-                [ipv6]
ad9e5f
-                method=manual
ad9e5f
-                may-fail=false
ad9e5f
-                addr-gen-mode=stable-privacy
ad9e5f
-                address1=2001:1::bbbb/96
ad9e5f
-                route1=::/0,2001:1::1
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-en0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init en0
ad9e5f
-                uuid=e0ca478b-8d84-52ab-8fae-628482c629b5
ad9e5f
-                type=ethernet
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=AA:BB:CC:DD:E8:00
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-        },
ad9e5f
     },
ad9e5f
     "bridge": {
ad9e5f
         "yaml": textwrap.dedent(
ad9e5f
@@ -3573,82 +2909,6 @@ iface bond0 inet6 static
ad9e5f
                 """
ad9e5f
             ),
ad9e5f
         },
ad9e5f
-        "expected_network_manager": {
ad9e5f
-            "cloud-init-br0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init br0
ad9e5f
-                uuid=dee46ce4-af7a-5e7c-aa08-b25533ae9213
ad9e5f
-                type=bridge
ad9e5f
-                interface-name=br0
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [bridge]
ad9e5f
-                stp=false
ad9e5f
-                priority=22
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=manual
ad9e5f
-                may-fail=false
ad9e5f
-                address1=192.168.2.2/24
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-eth0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth0
ad9e5f
-                uuid=1dd9a779-d327-56e1-8454-c65e2556c12c
ad9e5f
-                type=ethernet
ad9e5f
-                slave-type=bridge
ad9e5f
-                master=dee46ce4-af7a-5e7c-aa08-b25533ae9213
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=52:54:00:12:34:00
ad9e5f
-
ad9e5f
-                [ipv6]
ad9e5f
-                method=manual
ad9e5f
-                may-fail=false
ad9e5f
-                addr-gen-mode=stable-privacy
ad9e5f
-                address1=2001:1::100/96
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-eth1.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth1
ad9e5f
-                uuid=3c50eb47-7260-5a6d-801d-bd4f587d6b58
ad9e5f
-                type=ethernet
ad9e5f
-                slave-type=bridge
ad9e5f
-                master=dee46ce4-af7a-5e7c-aa08-b25533ae9213
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=52:54:00:12:34:01
ad9e5f
-
ad9e5f
-                [ipv6]
ad9e5f
-                method=manual
ad9e5f
-                may-fail=false
ad9e5f
-                addr-gen-mode=stable-privacy
ad9e5f
-                address1=2001:1::101/96
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-        },
ad9e5f
     },
ad9e5f
     "manual": {
ad9e5f
         "yaml": textwrap.dedent(
ad9e5f
@@ -3777,92 +3037,25 @@ iface bond0 inet6 static
ad9e5f
                 """
ad9e5f
             ),
ad9e5f
         },
ad9e5f
-        "expected_network_manager": {
ad9e5f
-            "cloud-init-eth0.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth0
ad9e5f
-                uuid=1dd9a779-d327-56e1-8454-c65e2556c12c
ad9e5f
-                type=ethernet
ad9e5f
+    },
ad9e5f
+}
ad9e5f
 
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
 
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=52:54:00:12:34:00
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=manual
ad9e5f
-                may-fail=false
ad9e5f
-                address1=192.168.1.2/24
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-eth1.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth1
ad9e5f
-                uuid=3c50eb47-7260-5a6d-801d-bd4f587d6b58
ad9e5f
-                type=ethernet
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mtu=1480
ad9e5f
-                mac-address=52:54:00:12:34:AA
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=auto
ad9e5f
-                may-fail=true
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-            "cloud-init-eth2.nmconnection": textwrap.dedent(
ad9e5f
-                """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth2
ad9e5f
-                uuid=5559a242-3421-5fdd-896e-9cb8313d5804
ad9e5f
-                type=ethernet
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=52:54:00:12:34:FF
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=auto
ad9e5f
-                may-fail=true
ad9e5f
-
ad9e5f
-                """
ad9e5f
-            ),
ad9e5f
-        },
ad9e5f
-    },
ad9e5f
-}
ad9e5f
-
ad9e5f
-
ad9e5f
-CONFIG_V1_EXPLICIT_LOOPBACK = {
ad9e5f
-    "version": 1,
ad9e5f
-    "config": [
ad9e5f
-        {
ad9e5f
-            "name": "eth0",
ad9e5f
-            "type": "physical",
ad9e5f
-            "subnets": [{"control": "auto", "type": "dhcp"}],
ad9e5f
-        },
ad9e5f
-        {
ad9e5f
-            "name": "lo",
ad9e5f
-            "type": "loopback",
ad9e5f
-            "subnets": [{"control": "auto", "type": "loopback"}],
ad9e5f
-        },
ad9e5f
-    ],
ad9e5f
-}
ad9e5f
+CONFIG_V1_EXPLICIT_LOOPBACK = {
ad9e5f
+    "version": 1,
ad9e5f
+    "config": [
ad9e5f
+        {
ad9e5f
+            "name": "eth0",
ad9e5f
+            "type": "physical",
ad9e5f
+            "subnets": [{"control": "auto", "type": "dhcp"}],
ad9e5f
+        },
ad9e5f
+        {
ad9e5f
+            "name": "lo",
ad9e5f
+            "type": "loopback",
ad9e5f
+            "subnets": [{"control": "auto", "type": "loopback"}],
ad9e5f
+        },
ad9e5f
+    ],
ad9e5f
+}
ad9e5f
 
ad9e5f
 
ad9e5f
 CONFIG_V1_SIMPLE_SUBNET = {
ad9e5f
@@ -4304,6 +3497,7 @@ class TestRhelSysConfigRendering(CiTestCase):
ad9e5f
 
ad9e5f
     with_logs = True
ad9e5f
 
ad9e5f
+    nm_cfg_file = "/etc/NetworkManager/NetworkManager.conf"
ad9e5f
     scripts_dir = "/etc/sysconfig/network-scripts"
ad9e5f
     header = (
ad9e5f
         "# Created by cloud-init on instance boot automatically, "
ad9e5f
@@ -4878,6 +4072,78 @@ USERCTL=no
ad9e5f
         self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
         self._assert_headers(found)
ad9e5f
 
ad9e5f
+    def test_check_ifcfg_rh(self):
ad9e5f
+        """ifcfg-rh plugin is added NetworkManager.conf if conf present."""
ad9e5f
+        render_dir = self.tmp_dir()
ad9e5f
+        nm_cfg = subp.target_path(render_dir, path=self.nm_cfg_file)
ad9e5f
+        util.ensure_dir(os.path.dirname(nm_cfg))
ad9e5f
+
ad9e5f
+        # write a template nm.conf, note plugins is a list here
ad9e5f
+        with open(nm_cfg, "w") as fh:
ad9e5f
+            fh.write("# test_check_ifcfg_rh\n[main]\nplugins=foo,bar\n")
ad9e5f
+        self.assertTrue(os.path.exists(nm_cfg))
ad9e5f
+
ad9e5f
+        # render and read
ad9e5f
+        entry = NETWORK_CONFIGS["small"]
ad9e5f
+        found = self._render_and_read(
ad9e5f
+            network_config=yaml.load(entry["yaml"]), dir=render_dir
ad9e5f
+        )
ad9e5f
+        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
+        self._assert_headers(found)
ad9e5f
+
ad9e5f
+        # check ifcfg-rh is in the 'plugins' list
ad9e5f
+        config = sysconfig.ConfigObj(nm_cfg)
ad9e5f
+        self.assertIn("ifcfg-rh", config["main"]["plugins"])
ad9e5f
+
ad9e5f
+    def test_check_ifcfg_rh_plugins_string(self):
ad9e5f
+        """ifcfg-rh plugin is append when plugins is a string."""
ad9e5f
+        render_dir = self.tmp_path("render")
ad9e5f
+        os.makedirs(render_dir)
ad9e5f
+        nm_cfg = subp.target_path(render_dir, path=self.nm_cfg_file)
ad9e5f
+        util.ensure_dir(os.path.dirname(nm_cfg))
ad9e5f
+
ad9e5f
+        # write a template nm.conf, note plugins is a value here
ad9e5f
+        util.write_file(nm_cfg, "# test_check_ifcfg_rh\n[main]\nplugins=foo\n")
ad9e5f
+
ad9e5f
+        # render and read
ad9e5f
+        entry = NETWORK_CONFIGS["small"]
ad9e5f
+        found = self._render_and_read(
ad9e5f
+            network_config=yaml.load(entry["yaml"]), dir=render_dir
ad9e5f
+        )
ad9e5f
+        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
+        self._assert_headers(found)
ad9e5f
+
ad9e5f
+        # check raw content has plugin
ad9e5f
+        nm_file_content = util.load_file(nm_cfg)
ad9e5f
+        self.assertIn("ifcfg-rh", nm_file_content)
ad9e5f
+
ad9e5f
+        # check ifcfg-rh is in the 'plugins' list
ad9e5f
+        config = sysconfig.ConfigObj(nm_cfg)
ad9e5f
+        self.assertIn("ifcfg-rh", config["main"]["plugins"])
ad9e5f
+
ad9e5f
+    def test_check_ifcfg_rh_plugins_no_plugins(self):
ad9e5f
+        """enable_ifcfg_plugin creates plugins value if missing."""
ad9e5f
+        render_dir = self.tmp_path("render")
ad9e5f
+        os.makedirs(render_dir)
ad9e5f
+        nm_cfg = subp.target_path(render_dir, path=self.nm_cfg_file)
ad9e5f
+        util.ensure_dir(os.path.dirname(nm_cfg))
ad9e5f
+
ad9e5f
+        # write a template nm.conf, note plugins is missing
ad9e5f
+        util.write_file(nm_cfg, "# test_check_ifcfg_rh\n[main]\n")
ad9e5f
+        self.assertTrue(os.path.exists(nm_cfg))
ad9e5f
+
ad9e5f
+        # render and read
ad9e5f
+        entry = NETWORK_CONFIGS["small"]
ad9e5f
+        found = self._render_and_read(
ad9e5f
+            network_config=yaml.load(entry["yaml"]), dir=render_dir
ad9e5f
+        )
ad9e5f
+        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
+        self._assert_headers(found)
ad9e5f
+
ad9e5f
+        # check ifcfg-rh is in the 'plugins' list
ad9e5f
+        config = sysconfig.ConfigObj(nm_cfg)
ad9e5f
+        self.assertIn("ifcfg-rh", config["main"]["plugins"])
ad9e5f
+
ad9e5f
     def test_netplan_dhcp_false_disable_dhcp_in_state(self):
ad9e5f
         """netplan config with dhcp[46]: False should not add dhcp in state"""
ad9e5f
         net_config = yaml.load(NETPLAN_DHCP_FALSE)
ad9e5f
@@ -5433,281 +4699,6 @@ STARTMODE=auto
ad9e5f
         self._assert_headers(found)
ad9e5f
 
ad9e5f
 
ad9e5f
-@mock.patch(
ad9e5f
-    "cloudinit.net.is_openvswitch_internal_interface",
ad9e5f
-    mock.Mock(return_value=False),
ad9e5f
-)
ad9e5f
-class TestNetworkManagerRendering(CiTestCase):
ad9e5f
-
ad9e5f
-    with_logs = True
ad9e5f
-
ad9e5f
-    scripts_dir = "/etc/NetworkManager/system-connections"
ad9e5f
-
ad9e5f
-    expected_name = "expected_network_manager"
ad9e5f
-
ad9e5f
-    def _get_renderer(self):
ad9e5f
-        return network_manager.Renderer()
ad9e5f
-
ad9e5f
-    def _render_and_read(self, network_config=None, state=None, dir=None):
ad9e5f
-        if dir is None:
ad9e5f
-            dir = self.tmp_dir()
ad9e5f
-
ad9e5f
-        if network_config:
ad9e5f
-            ns = network_state.parse_net_config_data(network_config)
ad9e5f
-        elif state:
ad9e5f
-            ns = state
ad9e5f
-        else:
ad9e5f
-            raise ValueError("Expected data or state, got neither")
ad9e5f
-
ad9e5f
-        renderer = self._get_renderer()
ad9e5f
-        renderer.render_network_state(ns, target=dir)
ad9e5f
-        return dir2dict(dir)
ad9e5f
-
ad9e5f
-    def _compare_files_to_expected(self, expected, found):
ad9e5f
-        orig_maxdiff = self.maxDiff
ad9e5f
-        expected_d = dict(
ad9e5f
-            (os.path.join(self.scripts_dir, k), v) for k, v in expected.items()
ad9e5f
-        )
ad9e5f
-
ad9e5f
-        try:
ad9e5f
-            self.maxDiff = None
ad9e5f
-            self.assertEqual(expected_d, found)
ad9e5f
-        finally:
ad9e5f
-            self.maxDiff = orig_maxdiff
ad9e5f
-
ad9e5f
-    @mock.patch("cloudinit.net.util.get_cmdline", return_value="root=myroot")
ad9e5f
-    @mock.patch("cloudinit.net.sys_dev_path")
ad9e5f
-    @mock.patch("cloudinit.net.read_sys_net")
ad9e5f
-    @mock.patch("cloudinit.net.get_devicelist")
ad9e5f
-    def test_default_generation(
ad9e5f
-        self,
ad9e5f
-        mock_get_devicelist,
ad9e5f
-        mock_read_sys_net,
ad9e5f
-        mock_sys_dev_path,
ad9e5f
-        m_get_cmdline,
ad9e5f
-    ):
ad9e5f
-        tmp_dir = self.tmp_dir()
ad9e5f
-        _setup_test(
ad9e5f
-            tmp_dir, mock_get_devicelist, mock_read_sys_net, mock_sys_dev_path
ad9e5f
-        )
ad9e5f
-
ad9e5f
-        network_cfg = net.generate_fallback_config()
ad9e5f
-        ns = network_state.parse_net_config_data(
ad9e5f
-            network_cfg, skip_broken=False
ad9e5f
-        )
ad9e5f
-
ad9e5f
-        render_dir = os.path.join(tmp_dir, "render")
ad9e5f
-        os.makedirs(render_dir)
ad9e5f
-
ad9e5f
-        renderer = self._get_renderer()
ad9e5f
-        renderer.render_network_state(ns, target=render_dir)
ad9e5f
-
ad9e5f
-        found = dir2dict(render_dir)
ad9e5f
-        self._compare_files_to_expected(
ad9e5f
-            {
ad9e5f
-                "cloud-init-eth1000.nmconnection": textwrap.dedent(
ad9e5f
-                    """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth1000
ad9e5f
-                uuid=8c517500-0c95-5308-9c8a-3092eebc44eb
ad9e5f
-                type=ethernet
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=07:1C:C6:75:A4:BE
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=auto
ad9e5f
-                may-fail=false
ad9e5f
-
ad9e5f
-                """
ad9e5f
-                ),
ad9e5f
-            },
ad9e5f
-            found,
ad9e5f
-        )
ad9e5f
-
ad9e5f
-    def test_openstack_rendering_samples(self):
ad9e5f
-        for os_sample in OS_SAMPLES:
ad9e5f
-            render_dir = self.tmp_dir()
ad9e5f
-            ex_input = os_sample["in_data"]
ad9e5f
-            ex_mac_addrs = os_sample["in_macs"]
ad9e5f
-            network_cfg = openstack.convert_net_json(
ad9e5f
-                ex_input, known_macs=ex_mac_addrs
ad9e5f
-            )
ad9e5f
-            ns = network_state.parse_net_config_data(
ad9e5f
-                network_cfg, skip_broken=False
ad9e5f
-            )
ad9e5f
-            renderer = self._get_renderer()
ad9e5f
-            # render a multiple times to simulate reboots
ad9e5f
-            renderer.render_network_state(ns, target=render_dir)
ad9e5f
-            renderer.render_network_state(ns, target=render_dir)
ad9e5f
-            renderer.render_network_state(ns, target=render_dir)
ad9e5f
-            for fn, expected_content in os_sample.get(self.expected_name, []):
ad9e5f
-                with open(os.path.join(render_dir, fn)) as fh:
ad9e5f
-                    self.assertEqual(expected_content, fh.read())
ad9e5f
-
ad9e5f
-    def test_network_config_v1_samples(self):
ad9e5f
-        ns = network_state.parse_net_config_data(CONFIG_V1_SIMPLE_SUBNET)
ad9e5f
-        render_dir = self.tmp_path("render")
ad9e5f
-        os.makedirs(render_dir)
ad9e5f
-        renderer = self._get_renderer()
ad9e5f
-        renderer.render_network_state(ns, target=render_dir)
ad9e5f
-        found = dir2dict(render_dir)
ad9e5f
-        self._compare_files_to_expected(
ad9e5f
-            {
ad9e5f
-                "cloud-init-interface0.nmconnection": textwrap.dedent(
ad9e5f
-                    """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init interface0
ad9e5f
-                uuid=8b6862ed-dbd6-5830-93f7-a91451c13828
ad9e5f
-                type=ethernet
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-                mac-address=52:54:00:12:34:00
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=manual
ad9e5f
-                may-fail=false
ad9e5f
-                address1=10.0.2.15/24
ad9e5f
-                gateway=10.0.2.2
ad9e5f
-
ad9e5f
-                """
ad9e5f
-                ),
ad9e5f
-            },
ad9e5f
-            found,
ad9e5f
-        )
ad9e5f
-
ad9e5f
-    def test_config_with_explicit_loopback(self):
ad9e5f
-        render_dir = self.tmp_path("render")
ad9e5f
-        os.makedirs(render_dir)
ad9e5f
-        ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK)
ad9e5f
-        renderer = self._get_renderer()
ad9e5f
-        renderer.render_network_state(ns, target=render_dir)
ad9e5f
-        found = dir2dict(render_dir)
ad9e5f
-        self._compare_files_to_expected(
ad9e5f
-            {
ad9e5f
-                "cloud-init-eth0.nmconnection": textwrap.dedent(
ad9e5f
-                    """\
ad9e5f
-                # Generated by cloud-init. Changes will be lost.
ad9e5f
-
ad9e5f
-                [connection]
ad9e5f
-                id=cloud-init eth0
ad9e5f
-                uuid=1dd9a779-d327-56e1-8454-c65e2556c12c
ad9e5f
-                type=ethernet
ad9e5f
-                interface-name=eth0
ad9e5f
-
ad9e5f
-                [user]
ad9e5f
-                org.freedesktop.NetworkManager.origin=cloud-init
ad9e5f
-
ad9e5f
-                [ethernet]
ad9e5f
-
ad9e5f
-                [ipv4]
ad9e5f
-                method=auto
ad9e5f
-                may-fail=false
ad9e5f
-
ad9e5f
-                """
ad9e5f
-                ),
ad9e5f
-            },
ad9e5f
-            found,
ad9e5f
-        )
ad9e5f
-
ad9e5f
-    def test_bond_config(self):
ad9e5f
-        entry = NETWORK_CONFIGS["bond"]
ad9e5f
-        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
ad9e5f
-        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
-
ad9e5f
-    def test_vlan_config(self):
ad9e5f
-        entry = NETWORK_CONFIGS["vlan"]
ad9e5f
-        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
ad9e5f
-        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
-
ad9e5f
-    def test_bridge_config(self):
ad9e5f
-        entry = NETWORK_CONFIGS["bridge"]
ad9e5f
-        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
ad9e5f
-        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
-
ad9e5f
-    def test_manual_config(self):
ad9e5f
-        entry = NETWORK_CONFIGS["manual"]
ad9e5f
-        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
ad9e5f
-        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
-
ad9e5f
-    def test_all_config(self):
ad9e5f
-        entry = NETWORK_CONFIGS["all"]
ad9e5f
-        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
ad9e5f
-        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
-        self.assertNotIn(
ad9e5f
-            "WARNING: Network config: ignoring eth0.101 device-level mtu",
ad9e5f
-            self.logs.getvalue(),
ad9e5f
-        )
ad9e5f
-
ad9e5f
-    def test_small_config(self):
ad9e5f
-        entry = NETWORK_CONFIGS["small"]
ad9e5f
-        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
ad9e5f
-        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
-
ad9e5f
-    def test_v4_and_v6_static_config(self):
ad9e5f
-        entry = NETWORK_CONFIGS["v4_and_v6_static"]
ad9e5f
-        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
ad9e5f
-        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
-        expected_msg = (
ad9e5f
-            "WARNING: Network config: ignoring iface0 device-level mtu:8999"
ad9e5f
-            " because ipv4 subnet-level mtu:9000 provided."
ad9e5f
-        )
ad9e5f
-        self.assertIn(expected_msg, self.logs.getvalue())
ad9e5f
-
ad9e5f
-    def test_dhcpv6_only_config(self):
ad9e5f
-        entry = NETWORK_CONFIGS["dhcpv6_only"]
ad9e5f
-        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
ad9e5f
-        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
-
ad9e5f
-    def test_simple_render_ipv6_slaac(self):
ad9e5f
-        entry = NETWORK_CONFIGS["ipv6_slaac"]
ad9e5f
-        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
ad9e5f
-        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
-
ad9e5f
-    def test_dhcpv6_stateless_config(self):
ad9e5f
-        entry = NETWORK_CONFIGS["dhcpv6_stateless"]
ad9e5f
-        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
ad9e5f
-        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
-
ad9e5f
-    def test_wakeonlan_disabled_config_v2(self):
ad9e5f
-        entry = NETWORK_CONFIGS["wakeonlan_disabled"]
ad9e5f
-        found = self._render_and_read(
ad9e5f
-            network_config=yaml.load(entry["yaml_v2"])
ad9e5f
-        )
ad9e5f
-        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
-
ad9e5f
-    def test_wakeonlan_enabled_config_v2(self):
ad9e5f
-        entry = NETWORK_CONFIGS["wakeonlan_enabled"]
ad9e5f
-        found = self._render_and_read(
ad9e5f
-            network_config=yaml.load(entry["yaml_v2"])
ad9e5f
-        )
ad9e5f
-        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
-
ad9e5f
-    def test_render_v4_and_v6(self):
ad9e5f
-        entry = NETWORK_CONFIGS["v4_and_v6"]
ad9e5f
-        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
ad9e5f
-        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
-
ad9e5f
-    def test_render_v6_and_v4(self):
ad9e5f
-        entry = NETWORK_CONFIGS["v6_and_v4"]
ad9e5f
-        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
ad9e5f
-        self._compare_files_to_expected(entry[self.expected_name], found)
ad9e5f
-
ad9e5f
-
ad9e5f
-@mock.patch(
ad9e5f
-    "cloudinit.net.is_openvswitch_internal_interface",
ad9e5f
-    mock.Mock(return_value=False),
ad9e5f
-)
ad9e5f
 class TestEniNetRendering(CiTestCase):
ad9e5f
     @mock.patch("cloudinit.net.util.get_cmdline", return_value="root=myroot")
ad9e5f
     @mock.patch("cloudinit.net.sys_dev_path")
ad9e5f
@@ -7145,9 +6136,9 @@ class TestNetworkdRoundTrip(CiTestCase):
ad9e5f
 
ad9e5f
 class TestRenderersSelect:
ad9e5f
     @pytest.mark.parametrize(
ad9e5f
-        "renderer_selected,netplan,eni,sys,network_manager,networkd",
ad9e5f
+        "renderer_selected,netplan,eni,nm,scfg,sys,networkd",
ad9e5f
         (
ad9e5f
-            # -netplan -ifupdown -sys -network-manager -networkd raises error
ad9e5f
+            # -netplan -ifupdown -nm -scfg -sys raises error
ad9e5f
             (
ad9e5f
                 net.RendererNotFoundError,
ad9e5f
                 False,
ad9e5f
@@ -7155,51 +6146,52 @@ class TestRenderersSelect:
ad9e5f
                 False,
ad9e5f
                 False,
ad9e5f
                 False,
ad9e5f
+                False,
ad9e5f
             ),
ad9e5f
-            # -netplan +ifupdown -sys -nm -networkd selects eni
ad9e5f
-            ("eni", False, True, False, False, False),
ad9e5f
-            # +netplan +ifupdown -sys -nm -networkd selects eni
ad9e5f
-            ("eni", True, True, False, False, False),
ad9e5f
-            # +netplan -ifupdown -sys -nm -networkd selects netplan
ad9e5f
-            ("netplan", True, False, False, False, False),
ad9e5f
-            # +netplan -ifupdown -sys -nm -networkd selects netplan
ad9e5f
-            ("netplan", True, False, False, False, False),
ad9e5f
-            # -netplan -ifupdown +sys -nm -networkd selects sysconfig
ad9e5f
-            ("sysconfig", False, False, True, False, False),
ad9e5f
-            # -netplan -ifupdown +sys +nm -networkd selects sysconfig
ad9e5f
-            ("sysconfig", False, False, True, True, False),
ad9e5f
-            # -netplan -ifupdown -sys +nm -networkd selects nm
ad9e5f
-            ("network-manager", False, False, False, True, False),
ad9e5f
-            # -netplan -ifupdown -sys +nm +networkd selects nm
ad9e5f
-            ("network-manager", False, False, False, True, True),
ad9e5f
-            # -netplan -ifupdown -sys -nm +networkd selects networkd
ad9e5f
-            ("networkd", False, False, False, False, True),
ad9e5f
+            # -netplan +ifupdown -nm -scfg -sys selects eni
ad9e5f
+            ("eni", False, True, False, False, False, False),
ad9e5f
+            # +netplan +ifupdown -nm -scfg -sys selects eni
ad9e5f
+            ("eni", True, True, False, False, False, False),
ad9e5f
+            # +netplan -ifupdown -nm -scfg -sys selects netplan
ad9e5f
+            ("netplan", True, False, False, False, False, False),
ad9e5f
+            # Ubuntu with Network-Manager installed
ad9e5f
+            # +netplan -ifupdown +nm -scfg -sys selects netplan
ad9e5f
+            ("netplan", True, False, True, False, False, False),
ad9e5f
+            # Centos/OpenSuse with Network-Manager installed selects sysconfig
ad9e5f
+            # -netplan -ifupdown +nm -scfg +sys selects netplan
ad9e5f
+            ("sysconfig", False, False, True, False, True, False),
ad9e5f
+            # -netplan -ifupdown -nm -scfg -sys +networkd selects networkd
ad9e5f
+            ("networkd", False, False, False, False, False, True),
ad9e5f
         ),
ad9e5f
     )
ad9e5f
     @mock.patch("cloudinit.net.renderers.networkd.available")
ad9e5f
-    @mock.patch("cloudinit.net.renderers.network_manager.available")
ad9e5f
     @mock.patch("cloudinit.net.renderers.netplan.available")
ad9e5f
     @mock.patch("cloudinit.net.renderers.sysconfig.available")
ad9e5f
+    @mock.patch("cloudinit.net.renderers.sysconfig.available_sysconfig")
ad9e5f
+    @mock.patch("cloudinit.net.renderers.sysconfig.available_nm")
ad9e5f
     @mock.patch("cloudinit.net.renderers.eni.available")
ad9e5f
     def test_valid_renderer_from_defaults_depending_on_availability(
ad9e5f
         self,
ad9e5f
         m_eni_avail,
ad9e5f
+        m_nm_avail,
ad9e5f
+        m_scfg_avail,
ad9e5f
         m_sys_avail,
ad9e5f
         m_netplan_avail,
ad9e5f
-        m_network_manager_avail,
ad9e5f
         m_networkd_avail,
ad9e5f
         renderer_selected,
ad9e5f
         netplan,
ad9e5f
         eni,
ad9e5f
+        nm,
ad9e5f
+        scfg,
ad9e5f
         sys,
ad9e5f
-        network_manager,
ad9e5f
         networkd,
ad9e5f
     ):
ad9e5f
         """Assert proper renderer per DEFAULT_PRIORITY given availability."""
ad9e5f
         m_eni_avail.return_value = eni  # ifupdown pkg presence
ad9e5f
+        m_nm_avail.return_value = nm  # network-manager presence
ad9e5f
+        m_scfg_avail.return_value = scfg  # sysconfig presence
ad9e5f
         m_sys_avail.return_value = sys  # sysconfig/ifup/down presence
ad9e5f
         m_netplan_avail.return_value = netplan  # netplan presence
ad9e5f
-        m_network_manager_avail.return_value = network_manager  # NM presence
ad9e5f
         m_networkd_avail.return_value = networkd  # networkd presence
ad9e5f
         if isinstance(renderer_selected, str):
ad9e5f
             (renderer_name, _rnd_class) = renderers.select(
ad9e5f
@@ -7257,7 +6249,7 @@ class TestNetRenderers(CiTestCase):
ad9e5f
             priority=["sysconfig", "eni"],
ad9e5f
         )
ad9e5f
 
ad9e5f
-    @mock.patch("cloudinit.net.sysconfig.available")
ad9e5f
+    @mock.patch("cloudinit.net.sysconfig.available_sysconfig")
ad9e5f
     @mock.patch("cloudinit.util.system_info")
ad9e5f
     def test_sysconfig_available_uses_variant_mapping(self, m_info, m_avail):
ad9e5f
         m_avail.return_value = True
ad9e5f
diff --git a/tests/unittests/test_net_activators.py b/tests/unittests/test_net_activators.py
ad9e5f
index 4525c49c..3c29e2f7 100644
ad9e5f
--- a/tests/unittests/test_net_activators.py
ad9e5f
+++ b/tests/unittests/test_net_activators.py
ad9e5f
@@ -41,20 +41,18 @@ NETPLAN_CALL_LIST = [
ad9e5f
 
ad9e5f
 @pytest.fixture
ad9e5f
 def available_mocks():
ad9e5f
-    mocks = namedtuple("Mocks", "m_which, m_file, m_exists")
ad9e5f
+    mocks = namedtuple("Mocks", "m_which, m_file")
ad9e5f
     with patch("cloudinit.subp.which", return_value=True) as m_which:
ad9e5f
         with patch("os.path.isfile", return_value=True) as m_file:
ad9e5f
-            with patch("os.path.exists", return_value=True) as m_exists:
ad9e5f
-                yield mocks(m_which, m_file, m_exists)
ad9e5f
+            yield mocks(m_which, m_file)
ad9e5f
 
ad9e5f
 
ad9e5f
 @pytest.fixture
ad9e5f
 def unavailable_mocks():
ad9e5f
-    mocks = namedtuple("Mocks", "m_which, m_file, m_exists")
ad9e5f
+    mocks = namedtuple("Mocks", "m_which, m_file")
ad9e5f
     with patch("cloudinit.subp.which", return_value=False) as m_which:
ad9e5f
         with patch("os.path.isfile", return_value=False) as m_file:
ad9e5f
-            with patch("os.path.exists", return_value=False) as m_exists:
ad9e5f
-                yield mocks(m_which, m_file, m_exists)
ad9e5f
+            yield mocks(m_which, m_file)
ad9e5f
 
ad9e5f
 
ad9e5f
 class TestSearchAndSelect:
ad9e5f
@@ -115,6 +113,10 @@ NETPLAN_AVAILABLE_CALLS = [
ad9e5f
     (("netplan",), {"search": ["/usr/sbin", "/sbin"], "target": None}),
ad9e5f
 ]
ad9e5f
 
ad9e5f
+NETWORK_MANAGER_AVAILABLE_CALLS = [
ad9e5f
+    (("nmcli",), {"target": None}),
ad9e5f
+]
ad9e5f
+
ad9e5f
 NETWORKD_AVAILABLE_CALLS = [
ad9e5f
     (("ip",), {"search": ["/usr/sbin", "/bin"], "target": None}),
ad9e5f
     (("systemctl",), {"search": ["/usr/sbin", "/bin"], "target": None}),
ad9e5f
@@ -126,6 +128,7 @@ NETWORKD_AVAILABLE_CALLS = [
ad9e5f
     [
ad9e5f
         (IfUpDownActivator, IF_UP_DOWN_AVAILABLE_CALLS),
ad9e5f
         (NetplanActivator, NETPLAN_AVAILABLE_CALLS),
ad9e5f
+        (NetworkManagerActivator, NETWORK_MANAGER_AVAILABLE_CALLS),
ad9e5f
         (NetworkdActivator, NETWORKD_AVAILABLE_CALLS),
ad9e5f
     ],
ad9e5f
 )
ad9e5f
@@ -141,72 +144,8 @@ IF_UP_DOWN_BRING_UP_CALL_LIST = [
ad9e5f
 ]
ad9e5f
 
ad9e5f
 NETWORK_MANAGER_BRING_UP_CALL_LIST = [
ad9e5f
-    (
ad9e5f
-        (
ad9e5f
-            [
ad9e5f
-                "nmcli",
ad9e5f
-                "connection",
ad9e5f
-                "load",
ad9e5f
-                "".join(
ad9e5f
-                    [
ad9e5f
-                        "/etc/NetworkManager/system-connections",
ad9e5f
-                        "/cloud-init-eth0.nmconnection",
ad9e5f
-                    ]
ad9e5f
-                ),
ad9e5f
-            ],
ad9e5f
-        ),
ad9e5f
-        {},
ad9e5f
-    ),
ad9e5f
-    (
ad9e5f
-        (
ad9e5f
-            [
ad9e5f
-                "nmcli",
ad9e5f
-                "connection",
ad9e5f
-                "up",
ad9e5f
-                "filename",
ad9e5f
-                "".join(
ad9e5f
-                    [
ad9e5f
-                        "/etc/NetworkManager/system-connections",
ad9e5f
-                        "/cloud-init-eth0.nmconnection",
ad9e5f
-                    ]
ad9e5f
-                ),
ad9e5f
-            ],
ad9e5f
-        ),
ad9e5f
-        {},
ad9e5f
-    ),
ad9e5f
-    (
ad9e5f
-        (
ad9e5f
-            [
ad9e5f
-                "nmcli",
ad9e5f
-                "connection",
ad9e5f
-                "load",
ad9e5f
-                "".join(
ad9e5f
-                    [
ad9e5f
-                        "/etc/NetworkManager/system-connections",
ad9e5f
-                        "/cloud-init-eth1.nmconnection",
ad9e5f
-                    ]
ad9e5f
-                ),
ad9e5f
-            ],
ad9e5f
-        ),
ad9e5f
-        {},
ad9e5f
-    ),
ad9e5f
-    (
ad9e5f
-        (
ad9e5f
-            [
ad9e5f
-                "nmcli",
ad9e5f
-                "connection",
ad9e5f
-                "up",
ad9e5f
-                "filename",
ad9e5f
-                "".join(
ad9e5f
-                    [
ad9e5f
-                        "/etc/NetworkManager/system-connections",
ad9e5f
-                        "/cloud-init-eth1.nmconnection",
ad9e5f
-                    ]
ad9e5f
-                ),
ad9e5f
-            ],
ad9e5f
-        ),
ad9e5f
-        {},
ad9e5f
-    ),
ad9e5f
+    ((["nmcli", "connection", "up", "ifname", "eth0"],), {}),
ad9e5f
+    ((["nmcli", "connection", "up", "ifname", "eth1"],), {}),
ad9e5f
 ]
ad9e5f
 
ad9e5f
 NETWORKD_BRING_UP_CALL_LIST = [
ad9e5f
@@ -230,11 +169,9 @@ class TestActivatorsBringUp:
ad9e5f
     def test_bring_up_interface(
ad9e5f
         self, m_subp, activator, expected_call_list, available_mocks
ad9e5f
     ):
ad9e5f
-        index = 0
ad9e5f
         activator.bring_up_interface("eth0")
ad9e5f
-        for call in m_subp.call_args_list:
ad9e5f
-            assert call == expected_call_list[index]
ad9e5f
-            index += 1
ad9e5f
+        assert len(m_subp.call_args_list) == 1
ad9e5f
+        assert m_subp.call_args_list[0] == expected_call_list[0]
ad9e5f
 
ad9e5f
     @patch("cloudinit.subp.subp", return_value=("", ""))
ad9e5f
     def test_bring_up_interfaces(
ad9e5f
@@ -271,8 +208,8 @@ IF_UP_DOWN_BRING_DOWN_CALL_LIST = [
ad9e5f
 ]
ad9e5f
 
ad9e5f
 NETWORK_MANAGER_BRING_DOWN_CALL_LIST = [
ad9e5f
-    ((["nmcli", "device", "disconnect", "eth0"],), {}),
ad9e5f
-    ((["nmcli", "device", "disconnect", "eth1"],), {}),
ad9e5f
+    ((["nmcli", "connection", "down", "eth0"],), {}),
ad9e5f
+    ((["nmcli", "connection", "down", "eth1"],), {}),
ad9e5f
 ]
ad9e5f
 
ad9e5f
 NETWORKD_BRING_DOWN_CALL_LIST = [
ad9e5f
-- 
ad9e5f
2.27.0
ad9e5f