Merge ~smoser/cloud-init:bug/1663045-archlinux-empty-dns into cloud-init:master

Proposed by Scott Moser
Status: Merged
Merged at revision: 512145cd16b0dfa0cbbe8a20d732e6f2d943b869
Proposed branch: ~smoser/cloud-init:bug/1663045-archlinux-empty-dns
Merge into: cloud-init:master
Diff against target: 193 lines (+125/-31)
3 files modified
cloudinit/distros/arch.py (+59/-31)
tests/unittests/test_distros/__init__.py (+21/-0)
tests/unittests/test_distros/test_arch.py (+45/-0)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Rich Lees (community) visual, basic testing Approve
cloud-init Commiters Pending
Review via email: mp+328114@code.launchpad.net

Commit message

archlinux: fix some network config bugs.

Fix empty dns on writing network config

If no dns nameservers were provided a stack trace would occur.
The changes here add some unit tests for the arch distro.

LP: #1663045
LP: #1706593

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:2a45f6adc2647eca9b4338313cb7229524a913de
https://jenkins.ubuntu.com/server/job/cloud-init-ci/97/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/97/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:0c21a8340afa85120dd07adea54ea6b91b0460f8
https://jenkins.ubuntu.com/server/job/cloud-init-ci/98/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/98/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:b20e131d6e2752fd11cab71feb48447ba93b8090
https://jenkins.ubuntu.com/server/job/cloud-init-ci/99/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: CentOS 6 & 7: Build & Test
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/99/rebuild

review: Approve (continuous-integration)
Revision history for this message
Rich Lees (rlees85) wrote :

Looks good. Also did some basic tests and it fixes all the problems I had found with CloudInit 0.7.9 on ArchLinux.

review: Approve (visual, basic testing)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:3933cd1d2cff1099a1ad96295ca441da4e652f24
https://jenkins.ubuntu.com/server/job/cloud-init-ci/107/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: CentOS 6 & 7: Build & Test
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/107/rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/distros/arch.py b/cloudinit/distros/arch.py
2index b4c0ba7..f87a343 100644
3--- a/cloudinit/distros/arch.py
4+++ b/cloudinit/distros/arch.py
5@@ -14,6 +14,8 @@ from cloudinit.distros.parsers.hostname import HostnameConf
6
7 from cloudinit.settings import PER_INSTANCE
8
9+import os
10+
11 LOG = logging.getLogger(__name__)
12
13
14@@ -52,31 +54,10 @@ class Distro(distros.Distro):
15 entries = net_util.translate_network(settings)
16 LOG.debug("Translated ubuntu style network settings %s into %s",
17 settings, entries)
18- dev_names = entries.keys()
19- # Format for netctl
20- for (dev, info) in entries.items():
21- nameservers = []
22- net_fn = self.network_conf_dir + dev
23- net_cfg = {
24- 'Connection': 'ethernet',
25- 'Interface': dev,
26- 'IP': info.get('bootproto'),
27- 'Address': "('%s/%s')" % (info.get('address'),
28- info.get('netmask')),
29- 'Gateway': info.get('gateway'),
30- 'DNS': str(tuple(info.get('dns-nameservers'))).replace(',', '')
31- }
32- util.write_file(net_fn, convert_netctl(net_cfg))
33- if info.get('auto'):
34- self._enable_interface(dev)
35- if 'dns-nameservers' in info:
36- nameservers.extend(info['dns-nameservers'])
37-
38- if nameservers:
39- util.write_file(self.resolve_conf_fn,
40- convert_resolv_conf(nameservers))
41-
42- return dev_names
43+ return _render_network(
44+ entries, resolv_conf=self.resolve_conf_fn,
45+ conf_dir=self.network_conf_dir,
46+ enable_func=self._enable_interface)
47
48 def _enable_interface(self, device_name):
49 cmd = ['netctl', 'reenable', device_name]
50@@ -173,13 +154,60 @@ class Distro(distros.Distro):
51 ["-y"], freq=PER_INSTANCE)
52
53
54+def _render_network(entries, target="/", conf_dir="etc/netctl",
55+ resolv_conf="etc/resolv.conf", enable_func=None):
56+ """Render the translate_network format into netctl files in target.
57+ Paths will be rendered under target.
58+ """
59+
60+ devs = []
61+ nameservers = []
62+ resolv_conf = util.target_path(target, resolv_conf)
63+ conf_dir = util.target_path(target, conf_dir)
64+
65+ for (dev, info) in entries.items():
66+ if dev == 'lo':
67+ # no configuration should be rendered for 'lo'
68+ continue
69+ devs.append(dev)
70+ net_fn = os.path.join(conf_dir, dev)
71+ net_cfg = {
72+ 'Connection': 'ethernet',
73+ 'Interface': dev,
74+ 'IP': info.get('bootproto'),
75+ 'Address': "%s/%s" % (info.get('address'),
76+ info.get('netmask')),
77+ 'Gateway': info.get('gateway'),
78+ 'DNS': info.get('dns-nameservers', []),
79+ }
80+ util.write_file(net_fn, convert_netctl(net_cfg))
81+ if enable_func and info.get('auto'):
82+ enable_func(dev)
83+ if 'dns-nameservers' in info:
84+ nameservers.extend(info['dns-nameservers'])
85+
86+ if nameservers:
87+ util.write_file(resolv_conf,
88+ convert_resolv_conf(nameservers))
89+ return devs
90+
91+
92 def convert_netctl(settings):
93- """Returns a settings string formatted for netctl."""
94- result = ''
95- if isinstance(settings, dict):
96- for k, v in settings.items():
97- result = result + '%s=%s\n' % (k, v)
98- return result
99+ """Given a dictionary, returns a string in netctl profile format.
100+
101+ netctl profile is described at:
102+ https://git.archlinux.org/netctl.git/tree/docs/netctl.profile.5.txt
103+
104+ Note that the 'Special Quoting Rules' are not handled here."""
105+ result = []
106+ for key in sorted(settings):
107+ val = settings[key]
108+ if val is None:
109+ val = ""
110+ elif isinstance(val, (tuple, list)):
111+ val = "(" + ' '.join("'%s'" % v for v in val) + ")"
112+ result.append("%s=%s\n" % (key, val))
113+ return ''.join(result)
114
115
116 def convert_resolv_conf(settings):
117diff --git a/tests/unittests/test_distros/__init__.py b/tests/unittests/test_distros/__init__.py
118index e69de29..5394aa5 100644
119--- a/tests/unittests/test_distros/__init__.py
120+++ b/tests/unittests/test_distros/__init__.py
121@@ -0,0 +1,21 @@
122+# This file is part of cloud-init. See LICENSE file for license information.
123+import copy
124+
125+from cloudinit import distros
126+from cloudinit import helpers
127+from cloudinit import settings
128+
129+
130+def _get_distro(dtype, system_info=None):
131+ """Return a Distro class of distro 'dtype'.
132+
133+ cfg is format of CFG_BUILTIN['system_info'].
134+
135+ example: _get_distro("debian")
136+ """
137+ if system_info is None:
138+ system_info = copy.deepcopy(settings.CFG_BUILTIN['system_info'])
139+ system_info['distro'] = dtype
140+ paths = helpers.Paths(system_info['paths'])
141+ distro_cls = distros.fetch(dtype)
142+ return distro_cls(dtype, system_info, paths)
143diff --git a/tests/unittests/test_distros/test_arch.py b/tests/unittests/test_distros/test_arch.py
144new file mode 100644
145index 0000000..3d4c9a7
146--- /dev/null
147+++ b/tests/unittests/test_distros/test_arch.py
148@@ -0,0 +1,45 @@
149+# This file is part of cloud-init. See LICENSE file for license information.
150+
151+from cloudinit.distros.arch import _render_network
152+from cloudinit import util
153+
154+from ..helpers import (CiTestCase, dir2dict)
155+
156+from . import _get_distro
157+
158+
159+class TestArch(CiTestCase):
160+
161+ def test_get_distro(self):
162+ distro = _get_distro("arch")
163+ hostname = "myhostname"
164+ hostfile = self.tmp_path("hostfile")
165+ distro._write_hostname(hostname, hostfile)
166+ self.assertEqual(hostname + "\n", util.load_file(hostfile))
167+
168+
169+class TestRenderNetwork(CiTestCase):
170+ def test_basic_static(self):
171+ """Just the most basic static config.
172+
173+ note 'lo' should not be rendered as an interface."""
174+ entries = {'eth0': {'auto': True,
175+ 'dns-nameservers': ['8.8.8.8'],
176+ 'bootproto': 'static',
177+ 'address': '10.0.0.2',
178+ 'gateway': '10.0.0.1',
179+ 'netmask': '255.255.255.0'},
180+ 'lo': {'auto': True}}
181+ target = self.tmp_dir()
182+ devs = _render_network(entries, target=target)
183+ files = dir2dict(target, prefix=target)
184+ self.assertEqual(['eth0'], devs)
185+ self.assertEqual(
186+ {'/etc/netctl/eth0': '\n'.join([
187+ "Address=10.0.0.2/255.255.255.0",
188+ "Connection=ethernet",
189+ "DNS=('8.8.8.8')",
190+ "Gateway=10.0.0.1",
191+ "IP=static",
192+ "Interface=eth0", ""]),
193+ '/etc/resolv.conf': 'nameserver 8.8.8.8\n'}, files)

Subscribers

People subscribed via source and target branches