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