7fc49f
From 5fc5da29e5187ff6f56c968e7c06fabd1fce62ad Mon Sep 17 00:00:00 2001
7fc49f
From: Ryan McCabe <rmccabe@redhat.com>
7fc49f
Date: Thu, 8 Jun 2017 13:24:23 -0400
7fc49f
Subject: [PATCH] net: Allow for NetworkManager configuration
7fc49f
7fc49f
In cases where the config json specifies nameserver entries,
7fc49f
if there are interfaces configured to use dhcp, NetworkManager,
7fc49f
if enabled, will clobber the /etc/resolv.conf that cloud-init
7fc49f
has produced, which can break dns. If there are no interfaces
7fc49f
configured to use dhcp, NetworkManager could clobber
7fc49f
/etc/resolv.conf with an empty file.
7fc49f
7fc49f
This patch adds a mechanism for dropping additional configuration
7fc49f
into /etc/NetworkManager/conf.d/ and disables management of
7fc49f
/etc/resolv.conf by NetworkManager when nameserver information is
7fc49f
provided in the config.
7fc49f
7fc49f
LP: #1693251
7fc49f
7fc49f
Resolves: rhbz#1454491
7fc49f
7fc49f
Signed-off-by: Ryan McCabe <rmccabe@redhat.com>
7fc49f
(cherry picked from commit 67bab5bb804e2346673430868935f6bbcdb88f13)
7fc49f
---
7fc49f
 cloudinit/distros/parsers/networkmanager_conf.py | 23 +++++++++++++++++++++++
7fc49f
 cloudinit/net/sysconfig.py                       | 24 ++++++++++++++++++++++++
7fc49f
 tests/unittests/test_net.py                      | 21 +++++++++++++++++++++
7fc49f
 3 files changed, 68 insertions(+)
7fc49f
 create mode 100644 cloudinit/distros/parsers/networkmanager_conf.py
7fc49f
7fc49f
diff --git a/cloudinit/distros/parsers/networkmanager_conf.py b/cloudinit/distros/parsers/networkmanager_conf.py
7fc49f
new file mode 100644
7fc49f
index 00000000..ac51f122
7fc49f
--- /dev/null
7fc49f
+++ b/cloudinit/distros/parsers/networkmanager_conf.py
7fc49f
@@ -0,0 +1,23 @@
7fc49f
+# Copyright (C) 2017 Red Hat, Inc.
7fc49f
+#
7fc49f
+# Author: Ryan McCabe <rmccabe@redhat.com>
7fc49f
+#
7fc49f
+# This file is part of cloud-init. See LICENSE file for license information.
7fc49f
+
7fc49f
+import configobj
7fc49f
+
7fc49f
+# This module is used to set additional NetworkManager configuration
7fc49f
+# in /etc/NetworkManager/conf.d
7fc49f
+#
7fc49f
+
7fc49f
+
7fc49f
+class NetworkManagerConf(configobj.ConfigObj):
7fc49f
+    def __init__(self, contents):
7fc49f
+        configobj.ConfigObj.__init__(self, contents,
7fc49f
+                                     interpolation=False,
7fc49f
+                                     write_empty_values=False)
7fc49f
+
7fc49f
+    def set_section_keypair(self, section_name, key, value):
7fc49f
+        if section_name not in self.sections:
7fc49f
+            self.main[section_name] = {}
7fc49f
+        self.main[section_name] = {key: value}
7fc49f
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
7fc49f
index ef80d99b..d496d916 100644
7fc49f
--- a/cloudinit/net/sysconfig.py
7fc49f
+++ b/cloudinit/net/sysconfig.py
7fc49f
@@ -5,6 +5,7 @@ import re
7fc49f
 
7fc49f
 import six
7fc49f
 
7fc49f
+from cloudinit.distros.parsers import networkmanager_conf
7fc49f
 from cloudinit.distros.parsers import resolv_conf
7fc49f
 from cloudinit import util
7fc49f
 
7fc49f
@@ -250,6 +251,9 @@ class Renderer(renderer.Renderer):
7fc49f
         self.netrules_path = config.get(
7fc49f
             'netrules_path', 'etc/udev/rules.d/70-persistent-net.rules')
7fc49f
         self.dns_path = config.get('dns_path', 'etc/resolv.conf')
7fc49f
+        nm_conf_path = 'etc/NetworkManager/conf.d/99-cloud-init.conf'
7fc49f
+        self.networkmanager_conf_path = config.get('networkmanager_conf_path',
7fc49f
+                                                   nm_conf_path)
7fc49f
 
7fc49f
     @classmethod
7fc49f
     def _render_iface_shared(cls, iface, iface_cfg):
7fc49f
@@ -443,6 +447,21 @@ class Renderer(renderer.Renderer):
7fc49f
             content.add_search_domain(searchdomain)
7fc49f
         return "\n".join([_make_header(';'), str(content)])
7fc49f
 
7fc49f
+    @staticmethod
7fc49f
+    def _render_networkmanager_conf(network_state):
7fc49f
+        content = networkmanager_conf.NetworkManagerConf("")
7fc49f
+
7fc49f
+        # If DNS server information is provided, configure
7fc49f
+        # NetworkManager to not manage dns, so that /etc/resolv.conf
7fc49f
+        # does not get clobbered.
7fc49f
+        if network_state.dns_nameservers:
7fc49f
+            content.set_section_keypair('main', 'dns', 'none')
7fc49f
+
7fc49f
+        if len(content) == 0:
7fc49f
+            return None
7fc49f
+        out = "".join([_make_header(), "\n", "\n".join(content.write()), "\n"])
7fc49f
+        return out
7fc49f
+
7fc49f
     @classmethod
7fc49f
     def _render_bridge_interfaces(cls, network_state, iface_contents):
7fc49f
         bridge_filter = renderer.filter_by_type('bridge')
7fc49f
@@ -500,6 +519,11 @@ class Renderer(renderer.Renderer):
7fc49f
             resolv_content = self._render_dns(network_state,
7fc49f
                                               existing_dns_path=dns_path)
7fc49f
             util.write_file(dns_path, resolv_content)
7fc49f
+        if self.networkmanager_conf_path:
7fc49f
+            nm_conf_path = os.path.join(target, self.networkmanager_conf_path)
7fc49f
+            nm_conf_content = self._render_networkmanager_conf(network_state)
7fc49f
+            if nm_conf_content:
7fc49f
+                util.write_file(nm_conf_path, nm_conf_content)
7fc49f
         if self.netrules_path:
7fc49f
             netrules_content = self._render_persistent_net(network_state)
7fc49f
             netrules_path = os.path.join(target, self.netrules_path)
7fc49f
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
7fc49f
index 172d6046..379ac8bb 100755
7fc49f
--- a/tests/unittests/test_net.py
7fc49f
+++ b/tests/unittests/test_net.py
7fc49f
@@ -162,6 +162,13 @@ NETMASK0=0.0.0.0
7fc49f
 ;
7fc49f
 nameserver 172.19.0.12
7fc49f
 """.lstrip()),
7fc49f
+            ('etc/NetworkManager/conf.d/99-cloud-init.conf',
7fc49f
+             """
7fc49f
+# Created by cloud-init on instance boot automatically, do not edit.
7fc49f
+#
7fc49f
+[main]
7fc49f
+dns = none
7fc49f
+""".lstrip()),
7fc49f
             ('etc/udev/rules.d/70-persistent-net.rules',
7fc49f
              "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',
7fc49f
                       'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))]
7fc49f
@@ -222,6 +229,13 @@ USERCTL=no
7fc49f
 ;
7fc49f
 nameserver 172.19.0.12
7fc49f
 """.lstrip()),
7fc49f
+            ('etc/NetworkManager/conf.d/99-cloud-init.conf',
7fc49f
+             """
7fc49f
+# Created by cloud-init on instance boot automatically, do not edit.
7fc49f
+#
7fc49f
+[main]
7fc49f
+dns = none
7fc49f
+""".lstrip()),
7fc49f
             ('etc/udev/rules.d/70-persistent-net.rules',
7fc49f
              "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',
7fc49f
                       'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))]
7fc49f
@@ -304,6 +318,13 @@ USERCTL=no
7fc49f
 ;
7fc49f
 nameserver 172.19.0.12
7fc49f
 """.lstrip()),
7fc49f
+            ('etc/NetworkManager/conf.d/99-cloud-init.conf',
7fc49f
+             """
7fc49f
+# Created by cloud-init on instance boot automatically, do not edit.
7fc49f
+#
7fc49f
+[main]
7fc49f
+dns = none
7fc49f
+""".lstrip()),
7fc49f
             ('etc/udev/rules.d/70-persistent-net.rules',
7fc49f
              "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',
7fc49f
                       'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))]
7fc49f
-- 
7fc49f
2.13.6
7fc49f