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