Ryan McCabe has proposed merging ~rmccabe/cloud-init:bug1693251 into 
cloud-init:master.

Requested reviews:
  cloud-init commiters (cloud-init-dev)
Related bugs:
  Bug #1693251 in cloud-init: "cloud-init should configure networkmanager to 
not manage /etc/resolv.conf"
  https://bugs.launchpad.net/cloud-init/+bug/1693251

For more details, see:
https://code.launchpad.net/~rmccabe/cloud-init/+git/cloud-init/+merge/325325

In cases where the config json specifies nameserver entries, NetworkManager, if 
enabled, will clobber the /etc/resolv.conf that cloud-init has produced, which 
can break dns. If at least one interface is configured for dhcp, you might end 
up with a resolv.conf that doesn't function as intended, and if you don't have 
any interfaces that get dns from dhcp, NetworkManager could clobber resolv.conf 
with an empty file.

This patch adds a mechanism for dropping additional configuration into 
/etc/NetworkManager/conf.d/. I figured at some point, somebody will need to set 
some other keypair to stop NetworkManager from misbehaving, so I added 
something for that instead of simply writing the particular 2 lines of config 
to fix the resolv.conf issue.

This patch only writes out that configuration file if necessary (as opposed to 
creating an empty file when there is no config needed). I wasn't sure which was 
preferred, so if that's not ideal, I could write out the empty file for 
consistency.

This patch will also write that config "(dns=none") out, too, for the specific 
problem noted in #1693251

LP: #1693251
-- 
Your team cloud-init commiters is requested to review the proposed merge of 
~rmccabe/cloud-init:bug1693251 into cloud-init:master.
diff --git a/cloudinit/distros/parsers/networkmanager_conf.py b/cloudinit/distros/parsers/networkmanager_conf.py
new file mode 100644
index 0000000..ac51f12
--- /dev/null
+++ b/cloudinit/distros/parsers/networkmanager_conf.py
@@ -0,0 +1,23 @@
+# Copyright (C) 2017 Red Hat, Inc.
+#
+# Author: Ryan McCabe <[email protected]>
+#
+# This file is part of cloud-init. See LICENSE file for license information.
+
+import configobj
+
+# This module is used to set additional NetworkManager configuration
+# in /etc/NetworkManager/conf.d
+#
+
+
+class NetworkManagerConf(configobj.ConfigObj):
+    def __init__(self, contents):
+        configobj.ConfigObj.__init__(self, contents,
+                                     interpolation=False,
+                                     write_empty_values=False)
+
+    def set_section_keypair(self, section_name, key, value):
+        if section_name not in self.sections:
+            self.main[section_name] = {}
+        self.main[section_name] = {key: value}
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index f7d4548..b5c90a6 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -5,6 +5,7 @@ import re
 
 import six
 
+from cloudinit.distros.parsers import networkmanager_conf
 from cloudinit.distros.parsers import resolv_conf
 from cloudinit import util
 
@@ -252,6 +253,9 @@ class Renderer(renderer.Renderer):
         self.netrules_path = config.get(
             'netrules_path', 'etc/udev/rules.d/70-persistent-net.rules')
         self.dns_path = config.get('dns_path', 'etc/resolv.conf')
+        nm_conf_path = 'etc/NetworkManager/conf.d/99-cloud-init.conf'
+        self.networkmanager_conf_path = config.get('networkmanager_conf_path',
+                                                   nm_conf_path)
 
     @classmethod
     def _render_iface_shared(cls, iface, iface_cfg):
@@ -447,6 +451,21 @@ class Renderer(renderer.Renderer):
             content.add_search_domain(searchdomain)
         return "\n".join([_make_header(';'), str(content)])
 
+    @staticmethod
+    def _render_networkmanager_conf(network_state):
+        content = networkmanager_conf.NetworkManagerConf("")
+
+        # If DNS server information is provided, configure
+        # NetworkManager to not manage dns, so that /etc/resolv.conf
+        # does not get clobbered.
+        if network_state.dns_nameservers:
+            content.set_section_keypair('main', 'dns', 'none')
+
+        if len(content) == 0:
+            return None
+        out = "".join([_make_header(), "\n", "\n".join(content.write()), "\n"])
+        return out
+
     @classmethod
     def _render_bridge_interfaces(cls, network_state, iface_contents):
         bridge_filter = renderer.filter_by_type('bridge')
@@ -507,6 +526,12 @@ class Renderer(renderer.Renderer):
             resolv_content = self._render_dns(network_state,
                                               existing_dns_path=dns_path)
             util.write_file(dns_path, resolv_content, file_mode)
+        if self.networkmanager_conf_path:
+            nm_conf_path = util.target_path(target,
+                                            self.networkmanager_conf_path)
+            nm_conf_content = self._render_networkmanager_conf(network_state)
+            if nm_conf_content:
+                util.write_file(nm_conf_path, nm_conf_content, file_mode)
         if self.netrules_path:
             netrules_content = self._render_persistent_net(network_state)
             netrules_path = util.target_path(target, self.netrules_path)
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 0a88caf..c8c7628 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -156,6 +156,13 @@ USERCTL=no
 ;
 nameserver 172.19.0.12
 """.lstrip()),
+            ('etc/NetworkManager/conf.d/99-cloud-init.conf',
+             """
+# Created by cloud-init on instance boot automatically, do not edit.
+#
+[main]
+dns = none
+""".lstrip()),
             ('etc/udev/rules.d/70-persistent-net.rules',
              "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',
                       'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))]
@@ -217,6 +224,13 @@ USERCTL=no
 ;
 nameserver 172.19.0.12
 """.lstrip()),
+            ('etc/NetworkManager/conf.d/99-cloud-init.conf',
+             """
+# Created by cloud-init on instance boot automatically, do not edit.
+#
+[main]
+dns = none
+""".lstrip()),
             ('etc/udev/rules.d/70-persistent-net.rules',
              "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',
                       'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))]
@@ -300,6 +314,13 @@ USERCTL=no
 ;
 nameserver 172.19.0.12
 """.lstrip()),
+            ('etc/NetworkManager/conf.d/99-cloud-init.conf',
+             """
+# Created by cloud-init on instance boot automatically, do not edit.
+#
+[main]
+dns = none
+""".lstrip()),
             ('etc/udev/rules.d/70-persistent-net.rules',
              "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',
                       'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))]
_______________________________________________
Mailing list: https://launchpad.net/~cloud-init-dev
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~cloud-init-dev
More help   : https://help.launchpad.net/ListHelp

Reply via email to