Merge ~rmccabe/cloud-init:bug1693251 into cloud-init:master

Proposed by Ryan McCabe
Status: Merged
Merged at revision: 67bab5bb804e2346673430868935f6bbcdb88f13
Proposed branch: ~rmccabe/cloud-init:bug1693251
Merge into: cloud-init:master
Diff against target: 132 lines (+69/-0)
3 files modified
cloudinit/distros/parsers/networkmanager_conf.py (+23/-0)
cloudinit/net/sysconfig.py (+25/-0)
tests/unittests/test_net.py (+21/-0)
Reviewer Review Type Date Requested Status
Scott Moser Approve
Lars Kellogg-Stedman (community) Approve
Server Team CI bot continuous-integration Approve
Review via email: mp+325325@code.launchpad.net

Description of the change

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

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Lars Kellogg-Stedman (larsks) wrote :

I've added a couple of inline comments.

Revision history for this message
Lars Kellogg-Stedman (larsks) :
Revision history for this message
Lars Kellogg-Stedman (larsks) wrote :

Other than a minor quibble about where we are rendering the header for the NM configuration file, I think this looks fine.

review: Approve
Revision history for this message
Scott Moser (smoser) wrote :

Some replies.
I like to get those addressed.
Over all this is very good though. Thank you Ryan!

review: Needs Fixing
Revision history for this message
Ryan McCabe (rmccabe) wrote :

Regarding the first question, I tested for network_state.dns_nameservers instead of self.dns_path because the test will always evaluate to true if it were testing only self.dns_path, since 'etc/resolv.conf' is provided as a default, and we don't want to drop the additional config for NM when no dns servers are provided.

Regarding the second question, I did the rendering the way I did in the patch to be consistent with how the rest of the files are being rendered, since the _make_header() function that writes out the "Created cloud-init..." lines is defined at the top of that sysconfig.py file. I could move it elsewhere so it can be reused, though, if you'd prefer that.

Revision history for this message
Scott Moser (smoser) wrote :

I'm fine with what is here now.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/distros/parsers/networkmanager_conf.py b/cloudinit/distros/parsers/networkmanager_conf.py
2new file mode 100644
3index 0000000..ac51f12
4--- /dev/null
5+++ b/cloudinit/distros/parsers/networkmanager_conf.py
6@@ -0,0 +1,23 @@
7+# Copyright (C) 2017 Red Hat, Inc.
8+#
9+# Author: Ryan McCabe <rmccabe@redhat.com>
10+#
11+# This file is part of cloud-init. See LICENSE file for license information.
12+
13+import configobj
14+
15+# This module is used to set additional NetworkManager configuration
16+# in /etc/NetworkManager/conf.d
17+#
18+
19+
20+class NetworkManagerConf(configobj.ConfigObj):
21+ def __init__(self, contents):
22+ configobj.ConfigObj.__init__(self, contents,
23+ interpolation=False,
24+ write_empty_values=False)
25+
26+ def set_section_keypair(self, section_name, key, value):
27+ if section_name not in self.sections:
28+ self.main[section_name] = {}
29+ self.main[section_name] = {key: value}
30diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
31index f7d4548..b5c90a6 100644
32--- a/cloudinit/net/sysconfig.py
33+++ b/cloudinit/net/sysconfig.py
34@@ -5,6 +5,7 @@ import re
35
36 import six
37
38+from cloudinit.distros.parsers import networkmanager_conf
39 from cloudinit.distros.parsers import resolv_conf
40 from cloudinit import util
41
42@@ -252,6 +253,9 @@ class Renderer(renderer.Renderer):
43 self.netrules_path = config.get(
44 'netrules_path', 'etc/udev/rules.d/70-persistent-net.rules')
45 self.dns_path = config.get('dns_path', 'etc/resolv.conf')
46+ nm_conf_path = 'etc/NetworkManager/conf.d/99-cloud-init.conf'
47+ self.networkmanager_conf_path = config.get('networkmanager_conf_path',
48+ nm_conf_path)
49
50 @classmethod
51 def _render_iface_shared(cls, iface, iface_cfg):
52@@ -447,6 +451,21 @@ class Renderer(renderer.Renderer):
53 content.add_search_domain(searchdomain)
54 return "\n".join([_make_header(';'), str(content)])
55
56+ @staticmethod
57+ def _render_networkmanager_conf(network_state):
58+ content = networkmanager_conf.NetworkManagerConf("")
59+
60+ # If DNS server information is provided, configure
61+ # NetworkManager to not manage dns, so that /etc/resolv.conf
62+ # does not get clobbered.
63+ if network_state.dns_nameservers:
64+ content.set_section_keypair('main', 'dns', 'none')
65+
66+ if len(content) == 0:
67+ return None
68+ out = "".join([_make_header(), "\n", "\n".join(content.write()), "\n"])
69+ return out
70+
71 @classmethod
72 def _render_bridge_interfaces(cls, network_state, iface_contents):
73 bridge_filter = renderer.filter_by_type('bridge')
74@@ -507,6 +526,12 @@ class Renderer(renderer.Renderer):
75 resolv_content = self._render_dns(network_state,
76 existing_dns_path=dns_path)
77 util.write_file(dns_path, resolv_content, file_mode)
78+ if self.networkmanager_conf_path:
79+ nm_conf_path = util.target_path(target,
80+ self.networkmanager_conf_path)
81+ nm_conf_content = self._render_networkmanager_conf(network_state)
82+ if nm_conf_content:
83+ util.write_file(nm_conf_path, nm_conf_content, file_mode)
84 if self.netrules_path:
85 netrules_content = self._render_persistent_net(network_state)
86 netrules_path = util.target_path(target, self.netrules_path)
87diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
88index 0a88caf..c8c7628 100644
89--- a/tests/unittests/test_net.py
90+++ b/tests/unittests/test_net.py
91@@ -156,6 +156,13 @@ USERCTL=no
92 ;
93 nameserver 172.19.0.12
94 """.lstrip()),
95+ ('etc/NetworkManager/conf.d/99-cloud-init.conf',
96+ """
97+# Created by cloud-init on instance boot automatically, do not edit.
98+#
99+[main]
100+dns = none
101+""".lstrip()),
102 ('etc/udev/rules.d/70-persistent-net.rules',
103 "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',
104 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))]
105@@ -217,6 +224,13 @@ USERCTL=no
106 ;
107 nameserver 172.19.0.12
108 """.lstrip()),
109+ ('etc/NetworkManager/conf.d/99-cloud-init.conf',
110+ """
111+# Created by cloud-init on instance boot automatically, do not edit.
112+#
113+[main]
114+dns = none
115+""".lstrip()),
116 ('etc/udev/rules.d/70-persistent-net.rules',
117 "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',
118 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))]
119@@ -300,6 +314,13 @@ USERCTL=no
120 ;
121 nameserver 172.19.0.12
122 """.lstrip()),
123+ ('etc/NetworkManager/conf.d/99-cloud-init.conf',
124+ """
125+# Created by cloud-init on instance boot automatically, do not edit.
126+#
127+[main]
128+dns = none
129+""".lstrip()),
130 ('etc/udev/rules.d/70-persistent-net.rules',
131 "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',
132 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))]

Subscribers

People subscribed via source and target branches