Merge ~chad.smith/cloud-init:ubuntu/zesty into cloud-init:ubuntu/zesty
- Git
- lp:~chad.smith/cloud-init
- ubuntu/zesty
- Merge into ubuntu/zesty
Proposed by
Chad Smith
Status: | Merged |
---|---|
Approved by: | Scott Moser |
Approved revision: | 4c1bfc6669042db65ddd148e5c506f92ab565259 |
Merged at revision: | 4c1bfc6669042db65ddd148e5c506f92ab565259 |
Proposed branch: | ~chad.smith/cloud-init:ubuntu/zesty |
Merge into: | cloud-init:ubuntu/zesty |
Diff against target: |
1040 lines (+258/-289) 23 files modified
cloudinit/config/cc_lxd.py (+1/-1) cloudinit/config/cc_ntp.py (+3/-1) cloudinit/config/cc_resizefs.py (+13/-30) cloudinit/config/cc_users_groups.py (+2/-1) cloudinit/config/schema.py (+1/-1) debian/changelog (+14/-0) debian/patches/series (+0/-1) dev/null (+0/-181) doc/examples/cloud-config-user-groups.txt (+3/-3) tests/cloud_tests/testcases/__init__.py (+7/-0) tests/cloud_tests/testcases/base.py (+8/-4) tests/cloud_tests/testcases/examples/including_user_groups.py (+6/-0) tests/cloud_tests/testcases/examples/including_user_groups.yaml (+5/-2) tests/cloud_tests/testcases/main/command_output_simple.py (+16/-0) tests/cloud_tests/testcases/modules/ntp.yaml (+2/-2) tests/cloud_tests/testcases/modules/user_groups.py (+6/-0) tests/cloud_tests/testcases/modules/user_groups.yaml (+5/-2) tests/unittests/test_handler/test_handler_lxd.py (+8/-8) tests/unittests/test_handler/test_handler_ntp.py (+12/-11) tests/unittests/test_handler/test_handler_resizefs.py (+57/-34) tests/unittests/test_handler/test_schema.py (+36/-1) tools/read-dependencies (+36/-5) tools/run-centos (+17/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Scott Moser | Pending | ||
Review via email: mp+332671@code.launchpad.net |
Commit message
Description of the change
Upstream snapshot of master into zesty for SRU
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)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/cloudinit/config/cc_lxd.py b/cloudinit/config/cc_lxd.py |
2 | index e6262f8..09374d2 100644 |
3 | --- a/cloudinit/config/cc_lxd.py |
4 | +++ b/cloudinit/config/cc_lxd.py |
5 | @@ -72,7 +72,7 @@ def handle(name, cfg, cloud, log, args): |
6 | type(init_cfg)) |
7 | init_cfg = {} |
8 | |
9 | - bridge_cfg = lxd_cfg.get('bridge') |
10 | + bridge_cfg = lxd_cfg.get('bridge', {}) |
11 | if not isinstance(bridge_cfg, dict): |
12 | log.warn("lxd/bridge config must be a dictionary. found a '%s'", |
13 | type(bridge_cfg)) |
14 | diff --git a/cloudinit/config/cc_ntp.py b/cloudinit/config/cc_ntp.py |
15 | index 15ae1ec..d43d060 100644 |
16 | --- a/cloudinit/config/cc_ntp.py |
17 | +++ b/cloudinit/config/cc_ntp.py |
18 | @@ -100,7 +100,9 @@ def handle(name, cfg, cloud, log, _args): |
19 | LOG.debug( |
20 | "Skipping module named %s, not present or disabled by cfg", name) |
21 | return |
22 | - ntp_cfg = cfg.get('ntp', {}) |
23 | + ntp_cfg = cfg['ntp'] |
24 | + if ntp_cfg is None: |
25 | + ntp_cfg = {} # Allow empty config which will install the package |
26 | |
27 | # TODO drop this when validate_cloudconfig_schema is strict=True |
28 | if not isinstance(ntp_cfg, (dict)): |
29 | diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py |
30 | index f774baa..0d282e6 100644 |
31 | --- a/cloudinit/config/cc_resizefs.py |
32 | +++ b/cloudinit/config/cc_resizefs.py |
33 | @@ -145,25 +145,6 @@ RESIZE_FS_PRECHECK_CMDS = { |
34 | } |
35 | |
36 | |
37 | -def rootdev_from_cmdline(cmdline): |
38 | - found = None |
39 | - for tok in cmdline.split(): |
40 | - if tok.startswith("root="): |
41 | - found = tok[5:] |
42 | - break |
43 | - if found is None: |
44 | - return None |
45 | - |
46 | - if found.startswith("/dev/"): |
47 | - return found |
48 | - if found.startswith("LABEL="): |
49 | - return "/dev/disk/by-label/" + found[len("LABEL="):] |
50 | - if found.startswith("UUID="): |
51 | - return "/dev/disk/by-uuid/" + found[len("UUID="):] |
52 | - |
53 | - return "/dev/" + found |
54 | - |
55 | - |
56 | def can_skip_resize(fs_type, resize_what, devpth): |
57 | fstype_lc = fs_type.lower() |
58 | for i, func in RESIZE_FS_PRECHECK_CMDS.items(): |
59 | @@ -172,14 +153,15 @@ def can_skip_resize(fs_type, resize_what, devpth): |
60 | return False |
61 | |
62 | |
63 | -def is_device_path_writable_block(devpath, info, log): |
64 | - """Return True if devpath is a writable block device. |
65 | +def maybe_get_writable_device_path(devpath, info, log): |
66 | + """Return updated devpath if the devpath is a writable block device. |
67 | |
68 | - @param devpath: Path to the root device we want to resize. |
69 | + @param devpath: Requested path to the root device we want to resize. |
70 | @param info: String representing information about the requested device. |
71 | @param log: Logger to which logs will be added upon error. |
72 | |
73 | - @returns Boolean True if block device is writable |
74 | + @returns devpath or updated devpath per kernel commandline if the device |
75 | + path is a writable block device, returns None otherwise. |
76 | """ |
77 | container = util.is_container() |
78 | |
79 | @@ -189,12 +171,12 @@ def is_device_path_writable_block(devpath, info, log): |
80 | devpath = util.rootdev_from_cmdline(util.get_cmdline()) |
81 | if devpath is None: |
82 | log.warn("Unable to find device '/dev/root'") |
83 | - return False |
84 | + return None |
85 | log.debug("Converted /dev/root to '%s' per kernel cmdline", devpath) |
86 | |
87 | if devpath == 'overlayroot': |
88 | log.debug("Not attempting to resize devpath '%s': %s", devpath, info) |
89 | - return False |
90 | + return None |
91 | |
92 | try: |
93 | statret = os.stat(devpath) |
94 | @@ -207,7 +189,7 @@ def is_device_path_writable_block(devpath, info, log): |
95 | devpath, info) |
96 | else: |
97 | raise exc |
98 | - return False |
99 | + return None |
100 | |
101 | if not stat.S_ISBLK(statret.st_mode) and not stat.S_ISCHR(statret.st_mode): |
102 | if container: |
103 | @@ -216,8 +198,8 @@ def is_device_path_writable_block(devpath, info, log): |
104 | else: |
105 | log.warn("device '%s' not a block device. cannot resize: %s" % |
106 | (devpath, info)) |
107 | - return False |
108 | - return True |
109 | + return None |
110 | + return devpath # The writable block devpath |
111 | |
112 | |
113 | def handle(name, cfg, _cloud, log, args): |
114 | @@ -242,8 +224,9 @@ def handle(name, cfg, _cloud, log, args): |
115 | info = "dev=%s mnt_point=%s path=%s" % (devpth, mount_point, resize_what) |
116 | log.debug("resize_info: %s" % info) |
117 | |
118 | - if not is_device_path_writable_block(devpth, info, log): |
119 | - return |
120 | + devpth = maybe_get_writable_device_path(devpth, info, log) |
121 | + if not devpth: |
122 | + return # devpath was not a writable block device |
123 | |
124 | resizer = None |
125 | if can_skip_resize(fs_type, resize_what, devpth): |
126 | diff --git a/cloudinit/config/cc_users_groups.py b/cloudinit/config/cc_users_groups.py |
127 | index b80d1d3..f363000 100644 |
128 | --- a/cloudinit/config/cc_users_groups.py |
129 | +++ b/cloudinit/config/cc_users_groups.py |
130 | @@ -15,7 +15,8 @@ options, see the ``Including users and groups`` config example. |
131 | Groups to add to the system can be specified as a list under the ``groups`` |
132 | key. Each entry in the list should either contain a the group name as a string, |
133 | or a dictionary with the group name as the key and a list of users who should |
134 | -be members of the group as the value. |
135 | +be members of the group as the value. **Note**: Groups are added before users, |
136 | +so any users in a group list must already exist on the system. |
137 | |
138 | The ``users`` config key takes a list of users to configure. The first entry in |
139 | this list is used as the default user for the system. To preserve the standard |
140 | diff --git a/cloudinit/config/schema.py b/cloudinit/config/schema.py |
141 | index bb291ff..ca7d0d5 100644 |
142 | --- a/cloudinit/config/schema.py |
143 | +++ b/cloudinit/config/schema.py |
144 | @@ -74,7 +74,7 @@ def validate_cloudconfig_schema(config, schema, strict=False): |
145 | try: |
146 | from jsonschema import Draft4Validator, FormatChecker |
147 | except ImportError: |
148 | - logging.warning( |
149 | + logging.debug( |
150 | 'Ignoring schema validation. python-jsonschema is not present') |
151 | return |
152 | validator = Draft4Validator(schema, format_checker=FormatChecker()) |
153 | diff --git a/debian/changelog b/debian/changelog |
154 | index e595586..86a8d0c 100644 |
155 | --- a/debian/changelog |
156 | +++ b/debian/changelog |
157 | @@ -1,3 +1,17 @@ |
158 | +cloud-init (17.1-25-g17a15f9e-0ubuntu1~17.04.1) zesty-proposed; urgency=medium |
159 | + |
160 | + * New upstream snapshot. |
161 | + - resizefs: Fix regression when system booted with root=PARTUUID= |
162 | + (LP: #1725067) |
163 | + - tools: make yum package installation more reliable |
164 | + - citest: fix remaining warnings raised by integration tests. |
165 | + - citest: show the class actual class name in results. |
166 | + - ntp: fix config module schema to allow empty ntp config |
167 | + (LP: #1724951) |
168 | + - tools: disable fastestmirror if using proxy [Joshua Powers] |
169 | + |
170 | + -- Chad Smith <chad.smith@canonical.com> Mon, 23 Oct 2017 14:57:46 -0600 |
171 | + |
172 | cloud-init (17.1-18-gd4f70470-0ubuntu1~17.04.2) zesty-proposed; urgency=medium |
173 | |
174 | * cherry-pick 41152f1: schema: Log debug instead of warning when |
175 | diff --git a/debian/patches/cpick-41152f1-schema-Log-debug-instead-of-warning-when-jsonschema-is b/debian/patches/cpick-41152f1-schema-Log-debug-instead-of-warning-when-jsonschema-is |
176 | deleted file mode 100644 |
177 | index 6b9e784..0000000 |
178 | --- a/debian/patches/cpick-41152f1-schema-Log-debug-instead-of-warning-when-jsonschema-is |
179 | +++ /dev/null |
180 | @@ -1,181 +0,0 @@ |
181 | -From 41152f10ddbd8681cdac44b408038a4f23ab02df Mon Sep 17 00:00:00 2001 |
182 | -From: Scott Moser <smoser@brickies.net> |
183 | -Date: Tue, 17 Oct 2017 16:12:59 -0400 |
184 | -Subject: [PATCH] schema: Log debug instead of warning when jsonschema is not |
185 | - available. |
186 | - |
187 | -When operating in expected path, cloud-init should avoid logging with |
188 | -warning. That causes 'WARNING' messages in /var/log/cloud-init.log. |
189 | -By default, warnings also go to the console. |
190 | - |
191 | -Since jsonschema is a optional dependency, and not present on xenial |
192 | -and zesty, cloud-init should not warn there. |
193 | - |
194 | -Also here: |
195 | -* Add a test to integration tests to assert that there are no |
196 | - warnings in /var/log/cloud-init.log. |
197 | -* Update one integration test that did show warning and the related |
198 | - documentation and examples. |
199 | - |
200 | -LP: #1724354 |
201 | ---- |
202 | - cloudinit/config/cc_users_groups.py | 3 ++- |
203 | - cloudinit/config/schema.py | 2 +- |
204 | - doc/examples/cloud-config-user-groups.txt | 6 +++--- |
205 | - tests/cloud_tests/testcases/base.py | 4 ++++ |
206 | - tests/cloud_tests/testcases/examples/including_user_groups.py | 6 ++++++ |
207 | - tests/cloud_tests/testcases/examples/including_user_groups.yaml | 7 +++++-- |
208 | - tests/cloud_tests/testcases/modules/user_groups.py | 6 ++++++ |
209 | - tests/cloud_tests/testcases/modules/user_groups.yaml | 7 +++++-- |
210 | - 8 files changed, 32 insertions(+), 9 deletions(-) |
211 | - |
212 | -Index: cloud-init/cloudinit/config/cc_users_groups.py |
213 | -=================================================================== |
214 | ---- cloud-init.orig/cloudinit/config/cc_users_groups.py |
215 | -+++ cloud-init/cloudinit/config/cc_users_groups.py |
216 | -@@ -15,7 +15,8 @@ options, see the ``Including users and g |
217 | - Groups to add to the system can be specified as a list under the ``groups`` |
218 | - key. Each entry in the list should either contain a the group name as a string, |
219 | - or a dictionary with the group name as the key and a list of users who should |
220 | --be members of the group as the value. |
221 | -+be members of the group as the value. **Note**: Groups are added before users, |
222 | -+so any users in a group list must already exist on the system. |
223 | - |
224 | - The ``users`` config key takes a list of users to configure. The first entry in |
225 | - this list is used as the default user for the system. To preserve the standard |
226 | -Index: cloud-init/cloudinit/config/schema.py |
227 | -=================================================================== |
228 | ---- cloud-init.orig/cloudinit/config/schema.py |
229 | -+++ cloud-init/cloudinit/config/schema.py |
230 | -@@ -74,7 +74,7 @@ def validate_cloudconfig_schema(config, |
231 | - try: |
232 | - from jsonschema import Draft4Validator, FormatChecker |
233 | - except ImportError: |
234 | -- logging.warning( |
235 | -+ logging.debug( |
236 | - 'Ignoring schema validation. python-jsonschema is not present') |
237 | - return |
238 | - validator = Draft4Validator(schema, format_checker=FormatChecker()) |
239 | -Index: cloud-init/doc/examples/cloud-config-user-groups.txt |
240 | -=================================================================== |
241 | ---- cloud-init.orig/doc/examples/cloud-config-user-groups.txt |
242 | -+++ cloud-init/doc/examples/cloud-config-user-groups.txt |
243 | -@@ -1,8 +1,8 @@ |
244 | - # Add groups to the system |
245 | --# The following example adds the ubuntu group with members foo and bar and |
246 | --# the group cloud-users. |
247 | -+# The following example adds the ubuntu group with members 'root' and 'sys' |
248 | -+# and the empty group cloud-users. |
249 | - groups: |
250 | -- - ubuntu: [foo,bar] |
251 | -+ - ubuntu: [root,sys] |
252 | - - cloud-users |
253 | - |
254 | - # Add users to the system. Users are added after groups are added. |
255 | -Index: cloud-init/tests/cloud_tests/testcases/base.py |
256 | -=================================================================== |
257 | ---- cloud-init.orig/tests/cloud_tests/testcases/base.py |
258 | -+++ cloud-init/tests/cloud_tests/testcases/base.py |
259 | -@@ -72,6 +72,10 @@ class CloudTestCase(unittest.TestCase): |
260 | - result = self.get_status_data(self.get_data_file('result.json')) |
261 | - self.assertEqual(len(result['errors']), 0) |
262 | - |
263 | -+ def test_no_warnings_in_log(self): |
264 | -+ """Warnings should not be found in the log.""" |
265 | -+ self.assertNotIn("WARN", self.get_data_file('cloud-init.log')) |
266 | -+ |
267 | - |
268 | - class PasswordListTest(CloudTestCase): |
269 | - """Base password test case class.""" |
270 | -Index: cloud-init/tests/cloud_tests/testcases/examples/including_user_groups.py |
271 | -=================================================================== |
272 | ---- cloud-init.orig/tests/cloud_tests/testcases/examples/including_user_groups.py |
273 | -+++ cloud-init/tests/cloud_tests/testcases/examples/including_user_groups.py |
274 | -@@ -40,4 +40,10 @@ class TestUserGroups(base.CloudTestCase) |
275 | - out = self.get_data_file('user_cloudy') |
276 | - self.assertRegex(out, r'cloudy:x:[0-9]{3,4}:') |
277 | - |
278 | -+ def test_user_root_in_secret(self): |
279 | -+ """Test root user is in 'secret' group.""" |
280 | -+ user, _, groups = self.get_data_file('root_groups').partition(":") |
281 | -+ self.assertIn("secret", groups.split(), |
282 | -+ msg="User root is not in group 'secret'") |
283 | -+ |
284 | - # vi: ts=4 expandtab |
285 | -Index: cloud-init/tests/cloud_tests/testcases/examples/including_user_groups.yaml |
286 | -=================================================================== |
287 | ---- cloud-init.orig/tests/cloud_tests/testcases/examples/including_user_groups.yaml |
288 | -+++ cloud-init/tests/cloud_tests/testcases/examples/including_user_groups.yaml |
289 | -@@ -8,7 +8,7 @@ cloud_config: | |
290 | - #cloud-config |
291 | - # Add groups to the system |
292 | - groups: |
293 | -- - secret: [foobar,barfoo] |
294 | -+ - secret: [root] |
295 | - - cloud-users |
296 | - |
297 | - # Add users to the system. Users are added after groups are added. |
298 | -@@ -24,7 +24,7 @@ cloud_config: | |
299 | - - name: barfoo |
300 | - gecos: Bar B. Foo |
301 | - sudo: ALL=(ALL) NOPASSWD:ALL |
302 | -- groups: cloud-users |
303 | -+ groups: [cloud-users, secret] |
304 | - lock_passwd: true |
305 | - - name: cloudy |
306 | - gecos: Magic Cloud App Daemon User |
307 | -@@ -49,5 +49,8 @@ collect_scripts: |
308 | - user_cloudy: | |
309 | - #!/bin/bash |
310 | - getent passwd cloudy |
311 | -+ root_groups: | |
312 | -+ #!/bin/bash |
313 | -+ groups root |
314 | - |
315 | - # vi: ts=4 expandtab |
316 | -Index: cloud-init/tests/cloud_tests/testcases/modules/user_groups.py |
317 | -=================================================================== |
318 | ---- cloud-init.orig/tests/cloud_tests/testcases/modules/user_groups.py |
319 | -+++ cloud-init/tests/cloud_tests/testcases/modules/user_groups.py |
320 | -@@ -40,4 +40,10 @@ class TestUserGroups(base.CloudTestCase) |
321 | - out = self.get_data_file('user_cloudy') |
322 | - self.assertRegex(out, r'cloudy:x:[0-9]{3,4}:') |
323 | - |
324 | -+ def test_user_root_in_secret(self): |
325 | -+ """Test root user is in 'secret' group.""" |
326 | -+ user, _, groups = self.get_data_file('root_groups').partition(":") |
327 | -+ self.assertIn("secret", groups.split(), |
328 | -+ msg="User root is not in group 'secret'") |
329 | -+ |
330 | - # vi: ts=4 expandtab |
331 | -Index: cloud-init/tests/cloud_tests/testcases/modules/user_groups.yaml |
332 | -=================================================================== |
333 | ---- cloud-init.orig/tests/cloud_tests/testcases/modules/user_groups.yaml |
334 | -+++ cloud-init/tests/cloud_tests/testcases/modules/user_groups.yaml |
335 | -@@ -7,7 +7,7 @@ cloud_config: | |
336 | - #cloud-config |
337 | - # Add groups to the system |
338 | - groups: |
339 | -- - secret: [foobar,barfoo] |
340 | -+ - secret: [root] |
341 | - - cloud-users |
342 | - |
343 | - # Add users to the system. Users are added after groups are added. |
344 | -@@ -23,7 +23,7 @@ cloud_config: | |
345 | - - name: barfoo |
346 | - gecos: Bar B. Foo |
347 | - sudo: ALL=(ALL) NOPASSWD:ALL |
348 | -- groups: cloud-users |
349 | -+ groups: [cloud-users, secret] |
350 | - lock_passwd: true |
351 | - - name: cloudy |
352 | - gecos: Magic Cloud App Daemon User |
353 | -@@ -48,5 +48,8 @@ collect_scripts: |
354 | - user_cloudy: | |
355 | - #!/bin/bash |
356 | - getent passwd cloudy |
357 | -+ root_groups: | |
358 | -+ #!/bin/bash |
359 | -+ groups root |
360 | - |
361 | - # vi: ts=4 expandtab |
362 | diff --git a/debian/patches/series b/debian/patches/series |
363 | index 4e403ce..fe91dbd 100644 |
364 | --- a/debian/patches/series |
365 | +++ b/debian/patches/series |
366 | @@ -1,2 +1 @@ |
367 | stable-release-no-jsonschema-dep.patch |
368 | -cpick-41152f1-schema-Log-debug-instead-of-warning-when-jsonschema-is |
369 | diff --git a/doc/examples/cloud-config-user-groups.txt b/doc/examples/cloud-config-user-groups.txt |
370 | index 9c5202f..0554d1f 100644 |
371 | --- a/doc/examples/cloud-config-user-groups.txt |
372 | +++ b/doc/examples/cloud-config-user-groups.txt |
373 | @@ -1,8 +1,8 @@ |
374 | # Add groups to the system |
375 | -# The following example adds the ubuntu group with members foo and bar and |
376 | -# the group cloud-users. |
377 | +# The following example adds the ubuntu group with members 'root' and 'sys' |
378 | +# and the empty group cloud-users. |
379 | groups: |
380 | - - ubuntu: [foo,bar] |
381 | + - ubuntu: [root,sys] |
382 | - cloud-users |
383 | |
384 | # Add users to the system. Users are added after groups are added. |
385 | diff --git a/tests/cloud_tests/testcases/__init__.py b/tests/cloud_tests/testcases/__init__.py |
386 | index 47217ce..a29a092 100644 |
387 | --- a/tests/cloud_tests/testcases/__init__.py |
388 | +++ b/tests/cloud_tests/testcases/__init__.py |
389 | @@ -5,6 +5,7 @@ |
390 | import importlib |
391 | import inspect |
392 | import unittest |
393 | +from unittest.util import strclass |
394 | |
395 | from tests.cloud_tests import config |
396 | from tests.cloud_tests.testcases.base import CloudTestCase as base_test |
397 | @@ -37,6 +38,12 @@ def get_suite(test_name, data, conf): |
398 | |
399 | class tmp(test_class): |
400 | |
401 | + _realclass = test_class |
402 | + |
403 | + def __str__(self): |
404 | + return "%s (%s)" % (self._testMethodName, |
405 | + strclass(self._realclass)) |
406 | + |
407 | @classmethod |
408 | def setUpClass(cls): |
409 | cls.data = data |
410 | diff --git a/tests/cloud_tests/testcases/base.py b/tests/cloud_tests/testcases/base.py |
411 | index bb545ab..1706f59 100644 |
412 | --- a/tests/cloud_tests/testcases/base.py |
413 | +++ b/tests/cloud_tests/testcases/base.py |
414 | @@ -16,10 +16,6 @@ class CloudTestCase(unittest.TestCase): |
415 | conf = None |
416 | _cloud_config = None |
417 | |
418 | - def shortDescription(self): |
419 | - """Prevent nose from using docstrings.""" |
420 | - return None |
421 | - |
422 | @property |
423 | def cloud_config(self): |
424 | """Get the cloud-config used by the test.""" |
425 | @@ -72,6 +68,14 @@ class CloudTestCase(unittest.TestCase): |
426 | result = self.get_status_data(self.get_data_file('result.json')) |
427 | self.assertEqual(len(result['errors']), 0) |
428 | |
429 | + def test_no_warnings_in_log(self): |
430 | + """Warnings should not be found in the log.""" |
431 | + self.assertEqual( |
432 | + [], |
433 | + [l for l in self.get_data_file('cloud-init.log').splitlines() |
434 | + if 'WARN' in l], |
435 | + msg="'WARN' found inside cloud-init.log") |
436 | + |
437 | |
438 | class PasswordListTest(CloudTestCase): |
439 | """Base password test case class.""" |
440 | diff --git a/tests/cloud_tests/testcases/examples/including_user_groups.py b/tests/cloud_tests/testcases/examples/including_user_groups.py |
441 | index 67af527..93b7a82 100644 |
442 | --- a/tests/cloud_tests/testcases/examples/including_user_groups.py |
443 | +++ b/tests/cloud_tests/testcases/examples/including_user_groups.py |
444 | @@ -40,4 +40,10 @@ class TestUserGroups(base.CloudTestCase): |
445 | out = self.get_data_file('user_cloudy') |
446 | self.assertRegex(out, r'cloudy:x:[0-9]{3,4}:') |
447 | |
448 | + def test_user_root_in_secret(self): |
449 | + """Test root user is in 'secret' group.""" |
450 | + user, _, groups = self.get_data_file('root_groups').partition(":") |
451 | + self.assertIn("secret", groups.split(), |
452 | + msg="User root is not in group 'secret'") |
453 | + |
454 | # vi: ts=4 expandtab |
455 | diff --git a/tests/cloud_tests/testcases/examples/including_user_groups.yaml b/tests/cloud_tests/testcases/examples/including_user_groups.yaml |
456 | index 0aa7ad2..469d03c 100644 |
457 | --- a/tests/cloud_tests/testcases/examples/including_user_groups.yaml |
458 | +++ b/tests/cloud_tests/testcases/examples/including_user_groups.yaml |
459 | @@ -8,7 +8,7 @@ cloud_config: | |
460 | #cloud-config |
461 | # Add groups to the system |
462 | groups: |
463 | - - secret: [foobar,barfoo] |
464 | + - secret: [root] |
465 | - cloud-users |
466 | |
467 | # Add users to the system. Users are added after groups are added. |
468 | @@ -24,7 +24,7 @@ cloud_config: | |
469 | - name: barfoo |
470 | gecos: Bar B. Foo |
471 | sudo: ALL=(ALL) NOPASSWD:ALL |
472 | - groups: cloud-users |
473 | + groups: [cloud-users, secret] |
474 | lock_passwd: true |
475 | - name: cloudy |
476 | gecos: Magic Cloud App Daemon User |
477 | @@ -49,5 +49,8 @@ collect_scripts: |
478 | user_cloudy: | |
479 | #!/bin/bash |
480 | getent passwd cloudy |
481 | + root_groups: | |
482 | + #!/bin/bash |
483 | + groups root |
484 | |
485 | # vi: ts=4 expandtab |
486 | diff --git a/tests/cloud_tests/testcases/main/command_output_simple.py b/tests/cloud_tests/testcases/main/command_output_simple.py |
487 | index fe4c767..857881c 100644 |
488 | --- a/tests/cloud_tests/testcases/main/command_output_simple.py |
489 | +++ b/tests/cloud_tests/testcases/main/command_output_simple.py |
490 | @@ -15,4 +15,20 @@ class TestCommandOutputSimple(base.CloudTestCase): |
491 | data.splitlines()[-1].strip()) |
492 | # TODO: need to test that all stages redirected here |
493 | |
494 | + def test_no_warnings_in_log(self): |
495 | + """Warnings should not be found in the log. |
496 | + |
497 | + This class redirected stderr and stdout, so it expects to find |
498 | + a warning in cloud-init.log to that effect.""" |
499 | + redirect_msg = 'Stdout, stderr changing to' |
500 | + warnings = [ |
501 | + l for l in self.get_data_file('cloud-init.log').splitlines() |
502 | + if 'WARN' in l] |
503 | + self.assertEqual( |
504 | + [], [w for w in warnings if redirect_msg not in w], |
505 | + msg="'WARN' found inside cloud-init.log") |
506 | + self.assertEqual( |
507 | + 1, len(warnings), |
508 | + msg="Did not find %s in cloud-init.log" % redirect_msg) |
509 | + |
510 | # vi: ts=4 expandtab |
511 | diff --git a/tests/cloud_tests/testcases/modules/ntp.yaml b/tests/cloud_tests/testcases/modules/ntp.yaml |
512 | index fbef431..2530d72 100644 |
513 | --- a/tests/cloud_tests/testcases/modules/ntp.yaml |
514 | +++ b/tests/cloud_tests/testcases/modules/ntp.yaml |
515 | @@ -4,8 +4,8 @@ |
516 | cloud_config: | |
517 | #cloud-config |
518 | ntp: |
519 | - pools: {} |
520 | - servers: {} |
521 | + pools: [] |
522 | + servers: [] |
523 | collect_scripts: |
524 | ntp_installed: | |
525 | #!/bin/bash |
526 | diff --git a/tests/cloud_tests/testcases/modules/user_groups.py b/tests/cloud_tests/testcases/modules/user_groups.py |
527 | index 67af527..93b7a82 100644 |
528 | --- a/tests/cloud_tests/testcases/modules/user_groups.py |
529 | +++ b/tests/cloud_tests/testcases/modules/user_groups.py |
530 | @@ -40,4 +40,10 @@ class TestUserGroups(base.CloudTestCase): |
531 | out = self.get_data_file('user_cloudy') |
532 | self.assertRegex(out, r'cloudy:x:[0-9]{3,4}:') |
533 | |
534 | + def test_user_root_in_secret(self): |
535 | + """Test root user is in 'secret' group.""" |
536 | + user, _, groups = self.get_data_file('root_groups').partition(":") |
537 | + self.assertIn("secret", groups.split(), |
538 | + msg="User root is not in group 'secret'") |
539 | + |
540 | # vi: ts=4 expandtab |
541 | diff --git a/tests/cloud_tests/testcases/modules/user_groups.yaml b/tests/cloud_tests/testcases/modules/user_groups.yaml |
542 | index 71cc9da..22b5d70 100644 |
543 | --- a/tests/cloud_tests/testcases/modules/user_groups.yaml |
544 | +++ b/tests/cloud_tests/testcases/modules/user_groups.yaml |
545 | @@ -7,7 +7,7 @@ cloud_config: | |
546 | #cloud-config |
547 | # Add groups to the system |
548 | groups: |
549 | - - secret: [foobar,barfoo] |
550 | + - secret: [root] |
551 | - cloud-users |
552 | |
553 | # Add users to the system. Users are added after groups are added. |
554 | @@ -23,7 +23,7 @@ cloud_config: | |
555 | - name: barfoo |
556 | gecos: Bar B. Foo |
557 | sudo: ALL=(ALL) NOPASSWD:ALL |
558 | - groups: cloud-users |
559 | + groups: [cloud-users, secret] |
560 | lock_passwd: true |
561 | - name: cloudy |
562 | gecos: Magic Cloud App Daemon User |
563 | @@ -48,5 +48,8 @@ collect_scripts: |
564 | user_cloudy: | |
565 | #!/bin/bash |
566 | getent passwd cloudy |
567 | + root_groups: | |
568 | + #!/bin/bash |
569 | + groups root |
570 | |
571 | # vi: ts=4 expandtab |
572 | diff --git a/tests/unittests/test_handler/test_handler_lxd.py b/tests/unittests/test_handler/test_handler_lxd.py |
573 | index f132a77..e0d9ab6 100644 |
574 | --- a/tests/unittests/test_handler/test_handler_lxd.py |
575 | +++ b/tests/unittests/test_handler/test_handler_lxd.py |
576 | @@ -5,17 +5,16 @@ from cloudinit.sources import DataSourceNoCloud |
577 | from cloudinit import (distros, helpers, cloud) |
578 | from cloudinit.tests import helpers as t_help |
579 | |
580 | -import logging |
581 | - |
582 | try: |
583 | from unittest import mock |
584 | except ImportError: |
585 | import mock |
586 | |
587 | -LOG = logging.getLogger(__name__) |
588 | |
589 | +class TestLxd(t_help.CiTestCase): |
590 | + |
591 | + with_logs = True |
592 | |
593 | -class TestLxd(t_help.TestCase): |
594 | lxd_cfg = { |
595 | 'lxd': { |
596 | 'init': { |
597 | @@ -41,7 +40,7 @@ class TestLxd(t_help.TestCase): |
598 | def test_lxd_init(self, mock_util): |
599 | cc = self._get_cloud('ubuntu') |
600 | mock_util.which.return_value = True |
601 | - cc_lxd.handle('cc_lxd', self.lxd_cfg, cc, LOG, []) |
602 | + cc_lxd.handle('cc_lxd', self.lxd_cfg, cc, self.logger, []) |
603 | self.assertTrue(mock_util.which.called) |
604 | init_call = mock_util.subp.call_args_list[0][0][0] |
605 | self.assertEqual(init_call, |
606 | @@ -55,7 +54,8 @@ class TestLxd(t_help.TestCase): |
607 | cc = self._get_cloud('ubuntu') |
608 | cc.distro = mock.MagicMock() |
609 | mock_util.which.return_value = None |
610 | - cc_lxd.handle('cc_lxd', self.lxd_cfg, cc, LOG, []) |
611 | + cc_lxd.handle('cc_lxd', self.lxd_cfg, cc, self.logger, []) |
612 | + self.assertNotIn('WARN', self.logs.getvalue()) |
613 | self.assertTrue(cc.distro.install_packages.called) |
614 | install_pkg = cc.distro.install_packages.call_args_list[0][0][0] |
615 | self.assertEqual(sorted(install_pkg), ['lxd', 'zfs']) |
616 | @@ -64,7 +64,7 @@ class TestLxd(t_help.TestCase): |
617 | def test_no_init_does_nothing(self, mock_util): |
618 | cc = self._get_cloud('ubuntu') |
619 | cc.distro = mock.MagicMock() |
620 | - cc_lxd.handle('cc_lxd', {'lxd': {}}, cc, LOG, []) |
621 | + cc_lxd.handle('cc_lxd', {'lxd': {}}, cc, self.logger, []) |
622 | self.assertFalse(cc.distro.install_packages.called) |
623 | self.assertFalse(mock_util.subp.called) |
624 | |
625 | @@ -72,7 +72,7 @@ class TestLxd(t_help.TestCase): |
626 | def test_no_lxd_does_nothing(self, mock_util): |
627 | cc = self._get_cloud('ubuntu') |
628 | cc.distro = mock.MagicMock() |
629 | - cc_lxd.handle('cc_lxd', {'package_update': True}, cc, LOG, []) |
630 | + cc_lxd.handle('cc_lxd', {'package_update': True}, cc, self.logger, []) |
631 | self.assertFalse(cc.distro.install_packages.called) |
632 | self.assertFalse(mock_util.subp.called) |
633 | |
634 | diff --git a/tests/unittests/test_handler/test_handler_ntp.py b/tests/unittests/test_handler/test_handler_ntp.py |
635 | index 4f29124..3abe578 100644 |
636 | --- a/tests/unittests/test_handler/test_handler_ntp.py |
637 | +++ b/tests/unittests/test_handler/test_handler_ntp.py |
638 | @@ -293,23 +293,24 @@ class TestNtp(FilesystemMockingTestCase): |
639 | |
640 | def test_ntp_handler_schema_validation_allows_empty_ntp_config(self): |
641 | """Ntp schema validation allows for an empty ntp: configuration.""" |
642 | - invalid_config = {'ntp': {}} |
643 | + valid_empty_configs = [{'ntp': {}}, {'ntp': None}] |
644 | distro = 'ubuntu' |
645 | cc = self._get_cloud(distro) |
646 | ntp_conf = os.path.join(self.new_root, 'ntp.conf') |
647 | with open('{0}.tmpl'.format(ntp_conf), 'wb') as stream: |
648 | stream.write(NTP_TEMPLATE) |
649 | - with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf): |
650 | - cc_ntp.handle('cc_ntp', invalid_config, cc, None, []) |
651 | + for valid_empty_config in valid_empty_configs: |
652 | + with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf): |
653 | + cc_ntp.handle('cc_ntp', valid_empty_config, cc, None, []) |
654 | + with open(ntp_conf) as stream: |
655 | + content = stream.read() |
656 | + default_pools = [ |
657 | + "{0}.{1}.pool.ntp.org".format(x, distro) |
658 | + for x in range(0, cc_ntp.NR_POOL_SERVERS)] |
659 | + self.assertEqual( |
660 | + "servers []\npools {0}\n".format(default_pools), |
661 | + content) |
662 | self.assertNotIn('Invalid config:', self.logs.getvalue()) |
663 | - with open(ntp_conf) as stream: |
664 | - content = stream.read() |
665 | - default_pools = [ |
666 | - "{0}.{1}.pool.ntp.org".format(x, distro) |
667 | - for x in range(0, cc_ntp.NR_POOL_SERVERS)] |
668 | - self.assertEqual( |
669 | - "servers []\npools {0}\n".format(default_pools), |
670 | - content) |
671 | |
672 | @skipIf(_missing_jsonschema_dep, "No python-jsonschema dependency") |
673 | def test_ntp_handler_schema_validation_warns_non_string_item_type(self): |
674 | diff --git a/tests/unittests/test_handler/test_handler_resizefs.py b/tests/unittests/test_handler/test_handler_resizefs.py |
675 | index 3e5d436..29d5574 100644 |
676 | --- a/tests/unittests/test_handler/test_handler_resizefs.py |
677 | +++ b/tests/unittests/test_handler/test_handler_resizefs.py |
678 | @@ -1,9 +1,9 @@ |
679 | # This file is part of cloud-init. See LICENSE file for license information. |
680 | |
681 | from cloudinit.config.cc_resizefs import ( |
682 | - can_skip_resize, handle, is_device_path_writable_block, |
683 | - rootdev_from_cmdline) |
684 | + can_skip_resize, handle, maybe_get_writable_device_path) |
685 | |
686 | +from collections import namedtuple |
687 | import logging |
688 | import textwrap |
689 | |
690 | @@ -138,47 +138,48 @@ class TestRootDevFromCmdline(CiTestCase): |
691 | invalid_cases = [ |
692 | 'BOOT_IMAGE=/adsf asdfa werasef root adf', 'BOOT_IMAGE=/adsf', ''] |
693 | for case in invalid_cases: |
694 | - self.assertIsNone(rootdev_from_cmdline(case)) |
695 | + self.assertIsNone(util.rootdev_from_cmdline(case)) |
696 | |
697 | def test_rootdev_from_cmdline_with_root_startswith_dev(self): |
698 | """Return the cmdline root when the path starts with /dev.""" |
699 | self.assertEqual( |
700 | - '/dev/this', rootdev_from_cmdline('asdf root=/dev/this')) |
701 | + '/dev/this', util.rootdev_from_cmdline('asdf root=/dev/this')) |
702 | |
703 | def test_rootdev_from_cmdline_with_root_without_dev_prefix(self): |
704 | """Add /dev prefix to cmdline root when the path lacks the prefix.""" |
705 | - self.assertEqual('/dev/this', rootdev_from_cmdline('asdf root=this')) |
706 | + self.assertEqual( |
707 | + '/dev/this', util.rootdev_from_cmdline('asdf root=this')) |
708 | |
709 | def test_rootdev_from_cmdline_with_root_with_label(self): |
710 | """When cmdline root contains a LABEL, our root is disk/by-label.""" |
711 | self.assertEqual( |
712 | '/dev/disk/by-label/unique', |
713 | - rootdev_from_cmdline('asdf root=LABEL=unique')) |
714 | + util.rootdev_from_cmdline('asdf root=LABEL=unique')) |
715 | |
716 | def test_rootdev_from_cmdline_with_root_with_uuid(self): |
717 | """When cmdline root contains a UUID, our root is disk/by-uuid.""" |
718 | self.assertEqual( |
719 | '/dev/disk/by-uuid/adsfdsaf-adsf', |
720 | - rootdev_from_cmdline('asdf root=UUID=adsfdsaf-adsf')) |
721 | + util.rootdev_from_cmdline('asdf root=UUID=adsfdsaf-adsf')) |
722 | |
723 | |
724 | -class TestIsDevicePathWritableBlock(CiTestCase): |
725 | +class TestMaybeGetDevicePathAsWritableBlock(CiTestCase): |
726 | |
727 | with_logs = True |
728 | |
729 | - def test_is_device_path_writable_block_false_on_overlayroot(self): |
730 | + def test_maybe_get_writable_device_path_none_on_overlayroot(self): |
731 | """When devpath is overlayroot (on MAAS), is_dev_writable is False.""" |
732 | info = 'does not matter' |
733 | - is_writable = wrap_and_call( |
734 | + devpath = wrap_and_call( |
735 | 'cloudinit.config.cc_resizefs.util', |
736 | {'is_container': {'return_value': False}}, |
737 | - is_device_path_writable_block, 'overlayroot', info, LOG) |
738 | - self.assertFalse(is_writable) |
739 | + maybe_get_writable_device_path, 'overlayroot', info, LOG) |
740 | + self.assertIsNone(devpath) |
741 | self.assertIn( |
742 | "Not attempting to resize devpath 'overlayroot'", |
743 | self.logs.getvalue()) |
744 | |
745 | - def test_is_device_path_writable_block_warns_missing_cmdline_root(self): |
746 | + def test_maybe_get_writable_device_path_warns_missing_cmdline_root(self): |
747 | """When root does not exist isn't in the cmdline, log warning.""" |
748 | info = 'does not matter' |
749 | |
750 | @@ -190,43 +191,43 @@ class TestIsDevicePathWritableBlock(CiTestCase): |
751 | exists_mock_path = 'cloudinit.config.cc_resizefs.os.path.exists' |
752 | with mock.patch(exists_mock_path) as m_exists: |
753 | m_exists.return_value = False |
754 | - is_writable = wrap_and_call( |
755 | + devpath = wrap_and_call( |
756 | 'cloudinit.config.cc_resizefs.util', |
757 | {'is_container': {'return_value': False}, |
758 | 'get_mount_info': {'side_effect': fake_mount_info}, |
759 | 'get_cmdline': {'return_value': 'BOOT_IMAGE=/vmlinuz.efi'}}, |
760 | - is_device_path_writable_block, '/dev/root', info, LOG) |
761 | - self.assertFalse(is_writable) |
762 | + maybe_get_writable_device_path, '/dev/root', info, LOG) |
763 | + self.assertIsNone(devpath) |
764 | logs = self.logs.getvalue() |
765 | self.assertIn("WARNING: Unable to find device '/dev/root'", logs) |
766 | |
767 | - def test_is_device_path_writable_block_does_not_exist(self): |
768 | + def test_maybe_get_writable_device_path_does_not_exist(self): |
769 | """When devpath does not exist, a warning is logged.""" |
770 | info = 'dev=/I/dont/exist mnt_point=/ path=/dev/none' |
771 | - is_writable = wrap_and_call( |
772 | + devpath = wrap_and_call( |
773 | 'cloudinit.config.cc_resizefs.util', |
774 | {'is_container': {'return_value': False}}, |
775 | - is_device_path_writable_block, '/I/dont/exist', info, LOG) |
776 | - self.assertFalse(is_writable) |
777 | + maybe_get_writable_device_path, '/I/dont/exist', info, LOG) |
778 | + self.assertIsNone(devpath) |
779 | self.assertIn( |
780 | "WARNING: Device '/I/dont/exist' did not exist." |
781 | ' cannot resize: %s' % info, |
782 | self.logs.getvalue()) |
783 | |
784 | - def test_is_device_path_writable_block_does_not_exist_in_container(self): |
785 | + def test_maybe_get_writable_device_path_does_not_exist_in_container(self): |
786 | """When devpath does not exist in a container, log a debug message.""" |
787 | info = 'dev=/I/dont/exist mnt_point=/ path=/dev/none' |
788 | - is_writable = wrap_and_call( |
789 | + devpath = wrap_and_call( |
790 | 'cloudinit.config.cc_resizefs.util', |
791 | {'is_container': {'return_value': True}}, |
792 | - is_device_path_writable_block, '/I/dont/exist', info, LOG) |
793 | - self.assertFalse(is_writable) |
794 | + maybe_get_writable_device_path, '/I/dont/exist', info, LOG) |
795 | + self.assertIsNone(devpath) |
796 | self.assertIn( |
797 | "DEBUG: Device '/I/dont/exist' did not exist in container." |
798 | ' cannot resize: %s' % info, |
799 | self.logs.getvalue()) |
800 | |
801 | - def test_is_device_path_writable_block_raises_oserror(self): |
802 | + def test_maybe_get_writable_device_path_raises_oserror(self): |
803 | """When unexpected OSError is raises by os.stat it is reraised.""" |
804 | info = 'dev=/I/dont/exist mnt_point=/ path=/dev/none' |
805 | with self.assertRaises(OSError) as context_manager: |
806 | @@ -234,41 +235,63 @@ class TestIsDevicePathWritableBlock(CiTestCase): |
807 | 'cloudinit.config.cc_resizefs', |
808 | {'util.is_container': {'return_value': True}, |
809 | 'os.stat': {'side_effect': OSError('Something unexpected')}}, |
810 | - is_device_path_writable_block, '/I/dont/exist', info, LOG) |
811 | + maybe_get_writable_device_path, '/I/dont/exist', info, LOG) |
812 | self.assertEqual( |
813 | 'Something unexpected', str(context_manager.exception)) |
814 | |
815 | - def test_is_device_path_writable_block_non_block(self): |
816 | + def test_maybe_get_writable_device_path_non_block(self): |
817 | """When device is not a block device, emit warning return False.""" |
818 | fake_devpath = self.tmp_path('dev/readwrite') |
819 | util.write_file(fake_devpath, '', mode=0o600) # read-write |
820 | info = 'dev=/dev/root mnt_point=/ path={0}'.format(fake_devpath) |
821 | |
822 | - is_writable = wrap_and_call( |
823 | + devpath = wrap_and_call( |
824 | 'cloudinit.config.cc_resizefs.util', |
825 | {'is_container': {'return_value': False}}, |
826 | - is_device_path_writable_block, fake_devpath, info, LOG) |
827 | - self.assertFalse(is_writable) |
828 | + maybe_get_writable_device_path, fake_devpath, info, LOG) |
829 | + self.assertIsNone(devpath) |
830 | self.assertIn( |
831 | "WARNING: device '{0}' not a block device. cannot resize".format( |
832 | fake_devpath), |
833 | self.logs.getvalue()) |
834 | |
835 | - def test_is_device_path_writable_block_non_block_on_container(self): |
836 | + def test_maybe_get_writable_device_path_non_block_on_container(self): |
837 | """When device is non-block device in container, emit debug log.""" |
838 | fake_devpath = self.tmp_path('dev/readwrite') |
839 | util.write_file(fake_devpath, '', mode=0o600) # read-write |
840 | info = 'dev=/dev/root mnt_point=/ path={0}'.format(fake_devpath) |
841 | |
842 | - is_writable = wrap_and_call( |
843 | + devpath = wrap_and_call( |
844 | 'cloudinit.config.cc_resizefs.util', |
845 | {'is_container': {'return_value': True}}, |
846 | - is_device_path_writable_block, fake_devpath, info, LOG) |
847 | - self.assertFalse(is_writable) |
848 | + maybe_get_writable_device_path, fake_devpath, info, LOG) |
849 | + self.assertIsNone(devpath) |
850 | self.assertIn( |
851 | "DEBUG: device '{0}' not a block device in container." |
852 | ' cannot resize'.format(fake_devpath), |
853 | self.logs.getvalue()) |
854 | |
855 | + def test_maybe_get_writable_device_path_returns_cmdline_root(self): |
856 | + """When root device is UUID in kernel commandline, update devpath.""" |
857 | + # XXX Long-term we want to use FilesystemMocking test to avoid |
858 | + # touching os.stat. |
859 | + FakeStat = namedtuple( |
860 | + 'FakeStat', ['st_mode', 'st_size', 'st_mtime']) # minimal def. |
861 | + info = 'dev=/dev/root mnt_point=/ path=/does/not/matter' |
862 | + devpath = wrap_and_call( |
863 | + 'cloudinit.config.cc_resizefs', |
864 | + {'util.get_cmdline': {'return_value': 'asdf root=UUID=my-uuid'}, |
865 | + 'util.is_container': False, |
866 | + 'os.path.exists': False, # /dev/root doesn't exist |
867 | + 'os.stat': { |
868 | + 'return_value': FakeStat(25008, 0, 1)} # char block device |
869 | + }, |
870 | + maybe_get_writable_device_path, '/dev/root', info, LOG) |
871 | + self.assertEqual('/dev/disk/by-uuid/my-uuid', devpath) |
872 | + self.assertIn( |
873 | + "DEBUG: Converted /dev/root to '/dev/disk/by-uuid/my-uuid'" |
874 | + " per kernel cmdline", |
875 | + self.logs.getvalue()) |
876 | + |
877 | |
878 | # vi: ts=4 expandtab |
879 | diff --git a/tests/unittests/test_handler/test_schema.py b/tests/unittests/test_handler/test_schema.py |
880 | index b8fc893..648573f 100644 |
881 | --- a/tests/unittests/test_handler/test_schema.py |
882 | +++ b/tests/unittests/test_handler/test_schema.py |
883 | @@ -4,11 +4,12 @@ from cloudinit.config.schema import ( |
884 | CLOUD_CONFIG_HEADER, SchemaValidationError, annotated_cloudconfig_file, |
885 | get_schema_doc, get_schema, validate_cloudconfig_file, |
886 | validate_cloudconfig_schema, main) |
887 | -from cloudinit.util import write_file |
888 | +from cloudinit.util import subp, write_file |
889 | |
890 | from cloudinit.tests.helpers import CiTestCase, mock, skipIf |
891 | |
892 | from copy import copy |
893 | +import os |
894 | from six import StringIO |
895 | from textwrap import dedent |
896 | from yaml import safe_load |
897 | @@ -364,4 +365,38 @@ class MainTest(CiTestCase): |
898 | self.assertIn( |
899 | 'Valid cloud-config file {0}'.format(myyaml), m_stdout.getvalue()) |
900 | |
901 | + |
902 | +class CloudTestsIntegrationTest(CiTestCase): |
903 | + """Validate all cloud-config yaml schema provided in integration tests. |
904 | + |
905 | + It is less expensive to have unittests validate schema of all cloud-config |
906 | + yaml provided to integration tests, than to run an integration test which |
907 | + raises Warnings or errors on invalid cloud-config schema. |
908 | + """ |
909 | + |
910 | + @skipIf(_missing_jsonschema_dep, "No python-jsonschema dependency") |
911 | + def test_all_integration_test_cloud_config_schema(self): |
912 | + """Validate schema of cloud_tests yaml files looking for warnings.""" |
913 | + schema = get_schema() |
914 | + testsdir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) |
915 | + integration_testdir = os.path.sep.join( |
916 | + [testsdir, 'cloud_tests', 'testcases']) |
917 | + errors = [] |
918 | + out, _ = subp(['find', integration_testdir, '-name', '*yaml']) |
919 | + for filename in out.splitlines(): |
920 | + test_cfg = safe_load(open(filename)) |
921 | + cloud_config = test_cfg.get('cloud_config') |
922 | + if cloud_config: |
923 | + cloud_config = safe_load( |
924 | + cloud_config.replace("#cloud-config\n", "")) |
925 | + try: |
926 | + validate_cloudconfig_schema( |
927 | + cloud_config, schema, strict=True) |
928 | + except SchemaValidationError as e: |
929 | + errors.append( |
930 | + '{0}: {1}'.format( |
931 | + filename, e)) |
932 | + if errors: |
933 | + raise AssertionError(', '.join(errors)) |
934 | + |
935 | # vi: ts=4 expandtab syntax=python |
936 | diff --git a/tools/read-dependencies b/tools/read-dependencies |
937 | index 2a64868..421f470 100755 |
938 | --- a/tools/read-dependencies |
939 | +++ b/tools/read-dependencies |
940 | @@ -30,9 +30,35 @@ DISTRO_PKG_TYPE_MAP = { |
941 | 'suse': 'suse' |
942 | } |
943 | |
944 | -DISTRO_INSTALL_PKG_CMD = { |
945 | +MAYBE_RELIABLE_YUM_INSTALL = [ |
946 | + 'sh', '-c', |
947 | + """ |
948 | + error() { echo "$@" 1>&2; } |
949 | + n=0; max=10; |
950 | + bcmd="yum install --downloadonly --assumeyes --setopt=keepcache=1" |
951 | + while n=$(($n+1)); do |
952 | + error ":: running $bcmd $* [$n/$max]" |
953 | + $bcmd "$@" |
954 | + r=$? |
955 | + [ $r -eq 0 ] && break |
956 | + [ $n -ge $max ] && { error "gave up on $bcmd"; exit $r; } |
957 | + nap=$(($n*5)) |
958 | + error ":: failed [$r] ($n/$max). sleeping $nap." |
959 | + sleep $nap |
960 | + done |
961 | + error ":: running yum install --cacheonly --assumeyes $*" |
962 | + yum install --cacheonly --assumeyes "$@" |
963 | + """, |
964 | + 'reliable-yum-install'] |
965 | + |
966 | +DRY_DISTRO_INSTALL_PKG_CMD = { |
967 | 'centos': ['yum', 'install', '--assumeyes'], |
968 | 'redhat': ['yum', 'install', '--assumeyes'], |
969 | +} |
970 | + |
971 | +DISTRO_INSTALL_PKG_CMD = { |
972 | + 'centos': MAYBE_RELIABLE_YUM_INSTALL, |
973 | + 'redhat': MAYBE_RELIABLE_YUM_INSTALL, |
974 | 'debian': ['apt', 'install', '-y'], |
975 | 'ubuntu': ['apt', 'install', '-y'], |
976 | 'opensuse': ['zypper', 'install'], |
977 | @@ -80,8 +106,8 @@ def get_parser(): |
978 | help='Additionally install continuous integration system packages ' |
979 | 'required for build and test automation.') |
980 | parser.add_argument( |
981 | - '-v', '--python-version', type=str, dest='python_version', default=None, |
982 | - choices=["2", "3"], |
983 | + '-v', '--python-version', type=str, dest='python_version', |
984 | + default=None, choices=["2", "3"], |
985 | help='Override the version of python we want to generate system ' |
986 | 'package dependencies for. Defaults to the version of python ' |
987 | 'this script is called with') |
988 | @@ -219,10 +245,15 @@ def pkg_install(pkg_list, distro, test_distro=False, dry_run=False): |
989 | '(dryrun)' if dry_run else '', ' '.join(pkg_list))) |
990 | install_cmd = [] |
991 | if dry_run: |
992 | - install_cmd.append('echo') |
993 | + install_cmd.append('echo') |
994 | if os.geteuid() != 0: |
995 | install_cmd.append('sudo') |
996 | - install_cmd.extend(DISTRO_INSTALL_PKG_CMD[distro]) |
997 | + |
998 | + cmd = DISTRO_INSTALL_PKG_CMD[distro] |
999 | + if dry_run and distro in DRY_DISTRO_INSTALL_PKG_CMD: |
1000 | + cmd = DRY_DISTRO_INSTALL_PKG_CMD[distro] |
1001 | + install_cmd.extend(cmd) |
1002 | + |
1003 | if distro in ['centos', 'redhat']: |
1004 | # CentOS and Redhat need epel-release to access oauthlib and jsonschema |
1005 | subprocess.check_call(install_cmd + ['epel-release']) |
1006 | diff --git a/tools/run-centos b/tools/run-centos |
1007 | index d44d514..d58ef3e 100755 |
1008 | --- a/tools/run-centos |
1009 | +++ b/tools/run-centos |
1010 | @@ -123,7 +123,22 @@ prep() { |
1011 | return 0 |
1012 | fi |
1013 | error "Installing prep packages: ${needed}" |
1014 | - yum install --assumeyes ${needed} |
1015 | + set -- $needed |
1016 | + local n max r |
1017 | + n=0; max=10; |
1018 | + bcmd="yum install --downloadonly --assumeyes --setopt=keepcache=1" |
1019 | + while n=$(($n+1)); do |
1020 | + error ":: running $bcmd $* [$n/$max]" |
1021 | + $bcmd "$@" |
1022 | + r=$? |
1023 | + [ $r -eq 0 ] && break |
1024 | + [ $n -ge $max ] && { error "gave up on $bcmd"; exit $r; } |
1025 | + nap=$(($n*5)) |
1026 | + error ":: failed [$r] ($n/$max). sleeping $nap." |
1027 | + sleep $nap |
1028 | + done |
1029 | + error ":: running yum install --cacheonly --assumeyes $*" |
1030 | + yum install --cacheonly --assumeyes "$@" |
1031 | } |
1032 | |
1033 | start_container() { |
1034 | @@ -153,6 +168,7 @@ start_container() { |
1035 | if [ ! -z "${http_proxy-}" ]; then |
1036 | debug 1 "configuring proxy ${http_proxy}" |
1037 | inside "$name" sh -c "echo proxy=$http_proxy >> /etc/yum.conf" |
1038 | + inside "$name" sed -i s/enabled=1/enabled=0/ /etc/yum/pluginconf.d/fastestmirror.conf |
1039 | fi |
1040 | } |
1041 |
PASSED: Continuous integration, rev:4c1bfc66690 42db65ddd148e5c 506f92ab565259 /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 435/
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 435/rebuild
https:/