Merge ~chad.smith/cloud-init:ubuntu/xenial into cloud-init:ubuntu/xenial

Proposed by Chad Smith
Status: Merged
Merged at revision: b5ca0b1343faaf1a802ff3ff1c4231e1304ad770
Proposed branch: ~chad.smith/cloud-init:ubuntu/xenial
Merge into: cloud-init:ubuntu/xenial
Diff against target: 1042 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)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Scott Moser Pending
Review via email: mp+332670@code.launchpad.net

Description of the change

Upstream snapshot or master for SRU in xenial

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

PASSED: Continuous integration, rev:b5ca0b1343faaf1a802ff3ff1c4231e1304ad770
https://jenkins.ubuntu.com/server/job/cloud-init-ci/436/
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:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/436/rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/cloudinit/config/cc_lxd.py b/cloudinit/config/cc_lxd.py
index e6262f8..09374d2 100644
--- a/cloudinit/config/cc_lxd.py
+++ b/cloudinit/config/cc_lxd.py
@@ -72,7 +72,7 @@ def handle(name, cfg, cloud, log, args):
72 type(init_cfg))72 type(init_cfg))
73 init_cfg = {}73 init_cfg = {}
7474
75 bridge_cfg = lxd_cfg.get('bridge')75 bridge_cfg = lxd_cfg.get('bridge', {})
76 if not isinstance(bridge_cfg, dict):76 if not isinstance(bridge_cfg, dict):
77 log.warn("lxd/bridge config must be a dictionary. found a '%s'",77 log.warn("lxd/bridge config must be a dictionary. found a '%s'",
78 type(bridge_cfg))78 type(bridge_cfg))
diff --git a/cloudinit/config/cc_ntp.py b/cloudinit/config/cc_ntp.py
index 15ae1ec..d43d060 100644
--- a/cloudinit/config/cc_ntp.py
+++ b/cloudinit/config/cc_ntp.py
@@ -100,7 +100,9 @@ def handle(name, cfg, cloud, log, _args):
100 LOG.debug(100 LOG.debug(
101 "Skipping module named %s, not present or disabled by cfg", name)101 "Skipping module named %s, not present or disabled by cfg", name)
102 return102 return
103 ntp_cfg = cfg.get('ntp', {})103 ntp_cfg = cfg['ntp']
104 if ntp_cfg is None:
105 ntp_cfg = {} # Allow empty config which will install the package
104106
105 # TODO drop this when validate_cloudconfig_schema is strict=True107 # TODO drop this when validate_cloudconfig_schema is strict=True
106 if not isinstance(ntp_cfg, (dict)):108 if not isinstance(ntp_cfg, (dict)):
diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py
index f774baa..0d282e6 100644
--- a/cloudinit/config/cc_resizefs.py
+++ b/cloudinit/config/cc_resizefs.py
@@ -145,25 +145,6 @@ RESIZE_FS_PRECHECK_CMDS = {
145}145}
146146
147147
148def rootdev_from_cmdline(cmdline):
149 found = None
150 for tok in cmdline.split():
151 if tok.startswith("root="):
152 found = tok[5:]
153 break
154 if found is None:
155 return None
156
157 if found.startswith("/dev/"):
158 return found
159 if found.startswith("LABEL="):
160 return "/dev/disk/by-label/" + found[len("LABEL="):]
161 if found.startswith("UUID="):
162 return "/dev/disk/by-uuid/" + found[len("UUID="):]
163
164 return "/dev/" + found
165
166
167def can_skip_resize(fs_type, resize_what, devpth):148def can_skip_resize(fs_type, resize_what, devpth):
168 fstype_lc = fs_type.lower()149 fstype_lc = fs_type.lower()
169 for i, func in RESIZE_FS_PRECHECK_CMDS.items():150 for i, func in RESIZE_FS_PRECHECK_CMDS.items():
@@ -172,14 +153,15 @@ def can_skip_resize(fs_type, resize_what, devpth):
172 return False153 return False
173154
174155
175def is_device_path_writable_block(devpath, info, log):156def maybe_get_writable_device_path(devpath, info, log):
176 """Return True if devpath is a writable block device.157 """Return updated devpath if the devpath is a writable block device.
177158
178 @param devpath: Path to the root device we want to resize.159 @param devpath: Requested path to the root device we want to resize.
179 @param info: String representing information about the requested device.160 @param info: String representing information about the requested device.
180 @param log: Logger to which logs will be added upon error.161 @param log: Logger to which logs will be added upon error.
181162
182 @returns Boolean True if block device is writable163 @returns devpath or updated devpath per kernel commandline if the device
164 path is a writable block device, returns None otherwise.
183 """165 """
184 container = util.is_container()166 container = util.is_container()
185167
@@ -189,12 +171,12 @@ def is_device_path_writable_block(devpath, info, log):
189 devpath = util.rootdev_from_cmdline(util.get_cmdline())171 devpath = util.rootdev_from_cmdline(util.get_cmdline())
190 if devpath is None:172 if devpath is None:
191 log.warn("Unable to find device '/dev/root'")173 log.warn("Unable to find device '/dev/root'")
192 return False174 return None
193 log.debug("Converted /dev/root to '%s' per kernel cmdline", devpath)175 log.debug("Converted /dev/root to '%s' per kernel cmdline", devpath)
194176
195 if devpath == 'overlayroot':177 if devpath == 'overlayroot':
196 log.debug("Not attempting to resize devpath '%s': %s", devpath, info)178 log.debug("Not attempting to resize devpath '%s': %s", devpath, info)
197 return False179 return None
198180
199 try:181 try:
200 statret = os.stat(devpath)182 statret = os.stat(devpath)
@@ -207,7 +189,7 @@ def is_device_path_writable_block(devpath, info, log):
207 devpath, info)189 devpath, info)
208 else:190 else:
209 raise exc191 raise exc
210 return False192 return None
211193
212 if not stat.S_ISBLK(statret.st_mode) and not stat.S_ISCHR(statret.st_mode):194 if not stat.S_ISBLK(statret.st_mode) and not stat.S_ISCHR(statret.st_mode):
213 if container:195 if container:
@@ -216,8 +198,8 @@ def is_device_path_writable_block(devpath, info, log):
216 else:198 else:
217 log.warn("device '%s' not a block device. cannot resize: %s" %199 log.warn("device '%s' not a block device. cannot resize: %s" %
218 (devpath, info))200 (devpath, info))
219 return False201 return None
220 return True202 return devpath # The writable block devpath
221203
222204
223def handle(name, cfg, _cloud, log, args):205def handle(name, cfg, _cloud, log, args):
@@ -242,8 +224,9 @@ def handle(name, cfg, _cloud, log, args):
242 info = "dev=%s mnt_point=%s path=%s" % (devpth, mount_point, resize_what)224 info = "dev=%s mnt_point=%s path=%s" % (devpth, mount_point, resize_what)
243 log.debug("resize_info: %s" % info)225 log.debug("resize_info: %s" % info)
244226
245 if not is_device_path_writable_block(devpth, info, log):227 devpth = maybe_get_writable_device_path(devpth, info, log)
246 return228 if not devpth:
229 return # devpath was not a writable block device
247230
248 resizer = None231 resizer = None
249 if can_skip_resize(fs_type, resize_what, devpth):232 if can_skip_resize(fs_type, resize_what, devpth):
diff --git a/cloudinit/config/cc_users_groups.py b/cloudinit/config/cc_users_groups.py
index b80d1d3..f363000 100644
--- a/cloudinit/config/cc_users_groups.py
+++ b/cloudinit/config/cc_users_groups.py
@@ -15,7 +15,8 @@ options, see the ``Including users and groups`` config example.
15Groups to add to the system can be specified as a list under the ``groups``15Groups to add to the system can be specified as a list under the ``groups``
16key. Each entry in the list should either contain a the group name as a string,16key. Each entry in the list should either contain a the group name as a string,
17or a dictionary with the group name as the key and a list of users who should17or a dictionary with the group name as the key and a list of users who should
18be members of the group as the value.18be members of the group as the value. **Note**: Groups are added before users,
19so any users in a group list must already exist on the system.
1920
20The ``users`` config key takes a list of users to configure. The first entry in21The ``users`` config key takes a list of users to configure. The first entry in
21this list is used as the default user for the system. To preserve the standard22this list is used as the default user for the system. To preserve the standard
diff --git a/cloudinit/config/schema.py b/cloudinit/config/schema.py
index bb291ff..ca7d0d5 100644
--- a/cloudinit/config/schema.py
+++ b/cloudinit/config/schema.py
@@ -74,7 +74,7 @@ def validate_cloudconfig_schema(config, schema, strict=False):
74 try:74 try:
75 from jsonschema import Draft4Validator, FormatChecker75 from jsonschema import Draft4Validator, FormatChecker
76 except ImportError:76 except ImportError:
77 logging.warning(77 logging.debug(
78 'Ignoring schema validation. python-jsonschema is not present')78 'Ignoring schema validation. python-jsonschema is not present')
79 return79 return
80 validator = Draft4Validator(schema, format_checker=FormatChecker())80 validator = Draft4Validator(schema, format_checker=FormatChecker())
diff --git a/debian/changelog b/debian/changelog
index 5a66def..e7ffc6f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,17 @@
1cloud-init (17.1-25-g17a15f9e-0ubuntu1~16.04.1) xenial-proposed; urgency=medium
2
3 * New upstream snapshot.
4 - resizefs: Fix regression when system booted with root=PARTUUID=
5 (LP: #1725067)
6 - tools: make yum package installation more reliable
7 - citest: fix remaining warnings raised by integration tests.
8 - citest: show the class actual class name in results.
9 - ntp: fix config module schema to allow empty ntp config
10 (LP: #1724951)
11 - tools: disable fastestmirror if using proxy [Joshua Powers]
12
13 -- Chad Smith <chad.smith@canonical.com> Mon, 23 Oct 2017 14:54:05 -0600
14
1cloud-init (17.1-18-gd4f70470-0ubuntu1~16.04.2) xenial-proposed; urgency=medium15cloud-init (17.1-18-gd4f70470-0ubuntu1~16.04.2) xenial-proposed; urgency=medium
216
3 * cherry-pick 41152f1: schema: Log debug instead of warning when17 * cherry-pick 41152f1: schema: Log debug instead of warning when
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
4deleted file mode 10064418deleted file mode 100644
index 6b9e784..0000000
--- a/debian/patches/cpick-41152f1-schema-Log-debug-instead-of-warning-when-jsonschema-is
+++ /dev/null
@@ -1,181 +0,0 @@
1From 41152f10ddbd8681cdac44b408038a4f23ab02df Mon Sep 17 00:00:00 2001
2From: Scott Moser <smoser@brickies.net>
3Date: Tue, 17 Oct 2017 16:12:59 -0400
4Subject: [PATCH] schema: Log debug instead of warning when jsonschema is not
5 available.
6
7When operating in expected path, cloud-init should avoid logging with
8warning. That causes 'WARNING' messages in /var/log/cloud-init.log.
9By default, warnings also go to the console.
10
11Since jsonschema is a optional dependency, and not present on xenial
12and zesty, cloud-init should not warn there.
13
14Also here:
15* Add a test to integration tests to assert that there are no
16 warnings in /var/log/cloud-init.log.
17* Update one integration test that did show warning and the related
18 documentation and examples.
19
20LP: #1724354
21---
22 cloudinit/config/cc_users_groups.py | 3 ++-
23 cloudinit/config/schema.py | 2 +-
24 doc/examples/cloud-config-user-groups.txt | 6 +++---
25 tests/cloud_tests/testcases/base.py | 4 ++++
26 tests/cloud_tests/testcases/examples/including_user_groups.py | 6 ++++++
27 tests/cloud_tests/testcases/examples/including_user_groups.yaml | 7 +++++--
28 tests/cloud_tests/testcases/modules/user_groups.py | 6 ++++++
29 tests/cloud_tests/testcases/modules/user_groups.yaml | 7 +++++--
30 8 files changed, 32 insertions(+), 9 deletions(-)
31
32Index: cloud-init/cloudinit/config/cc_users_groups.py
33===================================================================
34--- cloud-init.orig/cloudinit/config/cc_users_groups.py
35+++ cloud-init/cloudinit/config/cc_users_groups.py
36@@ -15,7 +15,8 @@ options, see the ``Including users and g
37 Groups to add to the system can be specified as a list under the ``groups``
38 key. Each entry in the list should either contain a the group name as a string,
39 or a dictionary with the group name as the key and a list of users who should
40-be members of the group as the value.
41+be members of the group as the value. **Note**: Groups are added before users,
42+so any users in a group list must already exist on the system.
43
44 The ``users`` config key takes a list of users to configure. The first entry in
45 this list is used as the default user for the system. To preserve the standard
46Index: cloud-init/cloudinit/config/schema.py
47===================================================================
48--- cloud-init.orig/cloudinit/config/schema.py
49+++ cloud-init/cloudinit/config/schema.py
50@@ -74,7 +74,7 @@ def validate_cloudconfig_schema(config,
51 try:
52 from jsonschema import Draft4Validator, FormatChecker
53 except ImportError:
54- logging.warning(
55+ logging.debug(
56 'Ignoring schema validation. python-jsonschema is not present')
57 return
58 validator = Draft4Validator(schema, format_checker=FormatChecker())
59Index: cloud-init/doc/examples/cloud-config-user-groups.txt
60===================================================================
61--- cloud-init.orig/doc/examples/cloud-config-user-groups.txt
62+++ cloud-init/doc/examples/cloud-config-user-groups.txt
63@@ -1,8 +1,8 @@
64 # Add groups to the system
65-# The following example adds the ubuntu group with members foo and bar and
66-# the group cloud-users.
67+# The following example adds the ubuntu group with members 'root' and 'sys'
68+# and the empty group cloud-users.
69 groups:
70- - ubuntu: [foo,bar]
71+ - ubuntu: [root,sys]
72 - cloud-users
73
74 # Add users to the system. Users are added after groups are added.
75Index: cloud-init/tests/cloud_tests/testcases/base.py
76===================================================================
77--- cloud-init.orig/tests/cloud_tests/testcases/base.py
78+++ cloud-init/tests/cloud_tests/testcases/base.py
79@@ -72,6 +72,10 @@ class CloudTestCase(unittest.TestCase):
80 result = self.get_status_data(self.get_data_file('result.json'))
81 self.assertEqual(len(result['errors']), 0)
82
83+ def test_no_warnings_in_log(self):
84+ """Warnings should not be found in the log."""
85+ self.assertNotIn("WARN", self.get_data_file('cloud-init.log'))
86+
87
88 class PasswordListTest(CloudTestCase):
89 """Base password test case class."""
90Index: cloud-init/tests/cloud_tests/testcases/examples/including_user_groups.py
91===================================================================
92--- cloud-init.orig/tests/cloud_tests/testcases/examples/including_user_groups.py
93+++ cloud-init/tests/cloud_tests/testcases/examples/including_user_groups.py
94@@ -40,4 +40,10 @@ class TestUserGroups(base.CloudTestCase)
95 out = self.get_data_file('user_cloudy')
96 self.assertRegex(out, r'cloudy:x:[0-9]{3,4}:')
97
98+ def test_user_root_in_secret(self):
99+ """Test root user is in 'secret' group."""
100+ user, _, groups = self.get_data_file('root_groups').partition(":")
101+ self.assertIn("secret", groups.split(),
102+ msg="User root is not in group 'secret'")
103+
104 # vi: ts=4 expandtab
105Index: cloud-init/tests/cloud_tests/testcases/examples/including_user_groups.yaml
106===================================================================
107--- cloud-init.orig/tests/cloud_tests/testcases/examples/including_user_groups.yaml
108+++ cloud-init/tests/cloud_tests/testcases/examples/including_user_groups.yaml
109@@ -8,7 +8,7 @@ cloud_config: |
110 #cloud-config
111 # Add groups to the system
112 groups:
113- - secret: [foobar,barfoo]
114+ - secret: [root]
115 - cloud-users
116
117 # Add users to the system. Users are added after groups are added.
118@@ -24,7 +24,7 @@ cloud_config: |
119 - name: barfoo
120 gecos: Bar B. Foo
121 sudo: ALL=(ALL) NOPASSWD:ALL
122- groups: cloud-users
123+ groups: [cloud-users, secret]
124 lock_passwd: true
125 - name: cloudy
126 gecos: Magic Cloud App Daemon User
127@@ -49,5 +49,8 @@ collect_scripts:
128 user_cloudy: |
129 #!/bin/bash
130 getent passwd cloudy
131+ root_groups: |
132+ #!/bin/bash
133+ groups root
134
135 # vi: ts=4 expandtab
136Index: cloud-init/tests/cloud_tests/testcases/modules/user_groups.py
137===================================================================
138--- cloud-init.orig/tests/cloud_tests/testcases/modules/user_groups.py
139+++ cloud-init/tests/cloud_tests/testcases/modules/user_groups.py
140@@ -40,4 +40,10 @@ class TestUserGroups(base.CloudTestCase)
141 out = self.get_data_file('user_cloudy')
142 self.assertRegex(out, r'cloudy:x:[0-9]{3,4}:')
143
144+ def test_user_root_in_secret(self):
145+ """Test root user is in 'secret' group."""
146+ user, _, groups = self.get_data_file('root_groups').partition(":")
147+ self.assertIn("secret", groups.split(),
148+ msg="User root is not in group 'secret'")
149+
150 # vi: ts=4 expandtab
151Index: cloud-init/tests/cloud_tests/testcases/modules/user_groups.yaml
152===================================================================
153--- cloud-init.orig/tests/cloud_tests/testcases/modules/user_groups.yaml
154+++ cloud-init/tests/cloud_tests/testcases/modules/user_groups.yaml
155@@ -7,7 +7,7 @@ cloud_config: |
156 #cloud-config
157 # Add groups to the system
158 groups:
159- - secret: [foobar,barfoo]
160+ - secret: [root]
161 - cloud-users
162
163 # Add users to the system. Users are added after groups are added.
164@@ -23,7 +23,7 @@ cloud_config: |
165 - name: barfoo
166 gecos: Bar B. Foo
167 sudo: ALL=(ALL) NOPASSWD:ALL
168- groups: cloud-users
169+ groups: [cloud-users, secret]
170 lock_passwd: true
171 - name: cloudy
172 gecos: Magic Cloud App Daemon User
173@@ -48,5 +48,8 @@ collect_scripts:
174 user_cloudy: |
175 #!/bin/bash
176 getent passwd cloudy
177+ root_groups: |
178+ #!/bin/bash
179+ groups root
180
181 # vi: ts=4 expandtab
diff --git a/debian/patches/series b/debian/patches/series
index 8bba1e4..7e909af 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,4 +1,3 @@
1azure-use-walinux-agent.patch1azure-use-walinux-agent.patch
2ds-identify-behavior-xenial.patch2ds-identify-behavior-xenial.patch
3stable-release-no-jsonschema-dep.patch3stable-release-no-jsonschema-dep.patch
4cpick-41152f1-schema-Log-debug-instead-of-warning-when-jsonschema-is
diff --git a/doc/examples/cloud-config-user-groups.txt b/doc/examples/cloud-config-user-groups.txt
index 9c5202f..0554d1f 100644
--- a/doc/examples/cloud-config-user-groups.txt
+++ b/doc/examples/cloud-config-user-groups.txt
@@ -1,8 +1,8 @@
1# Add groups to the system1# Add groups to the system
2# The following example adds the ubuntu group with members foo and bar and2# The following example adds the ubuntu group with members 'root' and 'sys'
3# the group cloud-users.3# and the empty group cloud-users.
4groups:4groups:
5 - ubuntu: [foo,bar]5 - ubuntu: [root,sys]
6 - cloud-users6 - cloud-users
77
8# Add users to the system. Users are added after groups are added.8# Add users to the system. Users are added after groups are added.
diff --git a/tests/cloud_tests/testcases/__init__.py b/tests/cloud_tests/testcases/__init__.py
index 47217ce..a29a092 100644
--- a/tests/cloud_tests/testcases/__init__.py
+++ b/tests/cloud_tests/testcases/__init__.py
@@ -5,6 +5,7 @@
5import importlib5import importlib
6import inspect6import inspect
7import unittest7import unittest
8from unittest.util import strclass
89
9from tests.cloud_tests import config10from tests.cloud_tests import config
10from tests.cloud_tests.testcases.base import CloudTestCase as base_test11from tests.cloud_tests.testcases.base import CloudTestCase as base_test
@@ -37,6 +38,12 @@ def get_suite(test_name, data, conf):
3738
38 class tmp(test_class):39 class tmp(test_class):
3940
41 _realclass = test_class
42
43 def __str__(self):
44 return "%s (%s)" % (self._testMethodName,
45 strclass(self._realclass))
46
40 @classmethod47 @classmethod
41 def setUpClass(cls):48 def setUpClass(cls):
42 cls.data = data49 cls.data = data
diff --git a/tests/cloud_tests/testcases/base.py b/tests/cloud_tests/testcases/base.py
index bb545ab..1706f59 100644
--- a/tests/cloud_tests/testcases/base.py
+++ b/tests/cloud_tests/testcases/base.py
@@ -16,10 +16,6 @@ class CloudTestCase(unittest.TestCase):
16 conf = None16 conf = None
17 _cloud_config = None17 _cloud_config = None
1818
19 def shortDescription(self):
20 """Prevent nose from using docstrings."""
21 return None
22
23 @property19 @property
24 def cloud_config(self):20 def cloud_config(self):
25 """Get the cloud-config used by the test."""21 """Get the cloud-config used by the test."""
@@ -72,6 +68,14 @@ class CloudTestCase(unittest.TestCase):
72 result = self.get_status_data(self.get_data_file('result.json'))68 result = self.get_status_data(self.get_data_file('result.json'))
73 self.assertEqual(len(result['errors']), 0)69 self.assertEqual(len(result['errors']), 0)
7470
71 def test_no_warnings_in_log(self):
72 """Warnings should not be found in the log."""
73 self.assertEqual(
74 [],
75 [l for l in self.get_data_file('cloud-init.log').splitlines()
76 if 'WARN' in l],
77 msg="'WARN' found inside cloud-init.log")
78
7579
76class PasswordListTest(CloudTestCase):80class PasswordListTest(CloudTestCase):
77 """Base password test case class."""81 """Base password test case class."""
diff --git a/tests/cloud_tests/testcases/examples/including_user_groups.py b/tests/cloud_tests/testcases/examples/including_user_groups.py
index 67af527..93b7a82 100644
--- a/tests/cloud_tests/testcases/examples/including_user_groups.py
+++ b/tests/cloud_tests/testcases/examples/including_user_groups.py
@@ -40,4 +40,10 @@ class TestUserGroups(base.CloudTestCase):
40 out = self.get_data_file('user_cloudy')40 out = self.get_data_file('user_cloudy')
41 self.assertRegex(out, r'cloudy:x:[0-9]{3,4}:')41 self.assertRegex(out, r'cloudy:x:[0-9]{3,4}:')
4242
43 def test_user_root_in_secret(self):
44 """Test root user is in 'secret' group."""
45 user, _, groups = self.get_data_file('root_groups').partition(":")
46 self.assertIn("secret", groups.split(),
47 msg="User root is not in group 'secret'")
48
43# vi: ts=4 expandtab49# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/testcases/examples/including_user_groups.yaml b/tests/cloud_tests/testcases/examples/including_user_groups.yaml
index 0aa7ad2..469d03c 100644
--- a/tests/cloud_tests/testcases/examples/including_user_groups.yaml
+++ b/tests/cloud_tests/testcases/examples/including_user_groups.yaml
@@ -8,7 +8,7 @@ cloud_config: |
8 #cloud-config8 #cloud-config
9 # Add groups to the system9 # Add groups to the system
10 groups:10 groups:
11 - secret: [foobar,barfoo]11 - secret: [root]
12 - cloud-users12 - cloud-users
1313
14 # Add users to the system. Users are added after groups are added.14 # Add users to the system. Users are added after groups are added.
@@ -24,7 +24,7 @@ cloud_config: |
24 - name: barfoo24 - name: barfoo
25 gecos: Bar B. Foo25 gecos: Bar B. Foo
26 sudo: ALL=(ALL) NOPASSWD:ALL26 sudo: ALL=(ALL) NOPASSWD:ALL
27 groups: cloud-users27 groups: [cloud-users, secret]
28 lock_passwd: true28 lock_passwd: true
29 - name: cloudy29 - name: cloudy
30 gecos: Magic Cloud App Daemon User30 gecos: Magic Cloud App Daemon User
@@ -49,5 +49,8 @@ collect_scripts:
49 user_cloudy: |49 user_cloudy: |
50 #!/bin/bash50 #!/bin/bash
51 getent passwd cloudy51 getent passwd cloudy
52 root_groups: |
53 #!/bin/bash
54 groups root
5255
53# vi: ts=4 expandtab56# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/testcases/main/command_output_simple.py b/tests/cloud_tests/testcases/main/command_output_simple.py
index fe4c767..857881c 100644
--- a/tests/cloud_tests/testcases/main/command_output_simple.py
+++ b/tests/cloud_tests/testcases/main/command_output_simple.py
@@ -15,4 +15,20 @@ class TestCommandOutputSimple(base.CloudTestCase):
15 data.splitlines()[-1].strip())15 data.splitlines()[-1].strip())
16 # TODO: need to test that all stages redirected here16 # TODO: need to test that all stages redirected here
1717
18 def test_no_warnings_in_log(self):
19 """Warnings should not be found in the log.
20
21 This class redirected stderr and stdout, so it expects to find
22 a warning in cloud-init.log to that effect."""
23 redirect_msg = 'Stdout, stderr changing to'
24 warnings = [
25 l for l in self.get_data_file('cloud-init.log').splitlines()
26 if 'WARN' in l]
27 self.assertEqual(
28 [], [w for w in warnings if redirect_msg not in w],
29 msg="'WARN' found inside cloud-init.log")
30 self.assertEqual(
31 1, len(warnings),
32 msg="Did not find %s in cloud-init.log" % redirect_msg)
33
18# vi: ts=4 expandtab34# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/testcases/modules/ntp.yaml b/tests/cloud_tests/testcases/modules/ntp.yaml
index fbef431..2530d72 100644
--- a/tests/cloud_tests/testcases/modules/ntp.yaml
+++ b/tests/cloud_tests/testcases/modules/ntp.yaml
@@ -4,8 +4,8 @@
4cloud_config: |4cloud_config: |
5 #cloud-config5 #cloud-config
6 ntp:6 ntp:
7 pools: {}7 pools: []
8 servers: {}8 servers: []
9collect_scripts:9collect_scripts:
10 ntp_installed: |10 ntp_installed: |
11 #!/bin/bash11 #!/bin/bash
diff --git a/tests/cloud_tests/testcases/modules/user_groups.py b/tests/cloud_tests/testcases/modules/user_groups.py
index 67af527..93b7a82 100644
--- a/tests/cloud_tests/testcases/modules/user_groups.py
+++ b/tests/cloud_tests/testcases/modules/user_groups.py
@@ -40,4 +40,10 @@ class TestUserGroups(base.CloudTestCase):
40 out = self.get_data_file('user_cloudy')40 out = self.get_data_file('user_cloudy')
41 self.assertRegex(out, r'cloudy:x:[0-9]{3,4}:')41 self.assertRegex(out, r'cloudy:x:[0-9]{3,4}:')
4242
43 def test_user_root_in_secret(self):
44 """Test root user is in 'secret' group."""
45 user, _, groups = self.get_data_file('root_groups').partition(":")
46 self.assertIn("secret", groups.split(),
47 msg="User root is not in group 'secret'")
48
43# vi: ts=4 expandtab49# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/testcases/modules/user_groups.yaml b/tests/cloud_tests/testcases/modules/user_groups.yaml
index 71cc9da..22b5d70 100644
--- a/tests/cloud_tests/testcases/modules/user_groups.yaml
+++ b/tests/cloud_tests/testcases/modules/user_groups.yaml
@@ -7,7 +7,7 @@ cloud_config: |
7 #cloud-config7 #cloud-config
8 # Add groups to the system8 # Add groups to the system
9 groups:9 groups:
10 - secret: [foobar,barfoo]10 - secret: [root]
11 - cloud-users11 - cloud-users
1212
13 # Add users to the system. Users are added after groups are added.13 # Add users to the system. Users are added after groups are added.
@@ -23,7 +23,7 @@ cloud_config: |
23 - name: barfoo23 - name: barfoo
24 gecos: Bar B. Foo24 gecos: Bar B. Foo
25 sudo: ALL=(ALL) NOPASSWD:ALL25 sudo: ALL=(ALL) NOPASSWD:ALL
26 groups: cloud-users26 groups: [cloud-users, secret]
27 lock_passwd: true27 lock_passwd: true
28 - name: cloudy28 - name: cloudy
29 gecos: Magic Cloud App Daemon User29 gecos: Magic Cloud App Daemon User
@@ -48,5 +48,8 @@ collect_scripts:
48 user_cloudy: |48 user_cloudy: |
49 #!/bin/bash49 #!/bin/bash
50 getent passwd cloudy50 getent passwd cloudy
51 root_groups: |
52 #!/bin/bash
53 groups root
5154
52# vi: ts=4 expandtab55# vi: ts=4 expandtab
diff --git a/tests/unittests/test_handler/test_handler_lxd.py b/tests/unittests/test_handler/test_handler_lxd.py
index f132a77..e0d9ab6 100644
--- a/tests/unittests/test_handler/test_handler_lxd.py
+++ b/tests/unittests/test_handler/test_handler_lxd.py
@@ -5,17 +5,16 @@ from cloudinit.sources import DataSourceNoCloud
5from cloudinit import (distros, helpers, cloud)5from cloudinit import (distros, helpers, cloud)
6from cloudinit.tests import helpers as t_help6from cloudinit.tests import helpers as t_help
77
8import logging
9
10try:8try:
11 from unittest import mock9 from unittest import mock
12except ImportError:10except ImportError:
13 import mock11 import mock
1412
15LOG = logging.getLogger(__name__)
1613
14class TestLxd(t_help.CiTestCase):
15
16 with_logs = True
1717
18class TestLxd(t_help.TestCase):
19 lxd_cfg = {18 lxd_cfg = {
20 'lxd': {19 'lxd': {
21 'init': {20 'init': {
@@ -41,7 +40,7 @@ class TestLxd(t_help.TestCase):
41 def test_lxd_init(self, mock_util):40 def test_lxd_init(self, mock_util):
42 cc = self._get_cloud('ubuntu')41 cc = self._get_cloud('ubuntu')
43 mock_util.which.return_value = True42 mock_util.which.return_value = True
44 cc_lxd.handle('cc_lxd', self.lxd_cfg, cc, LOG, [])43 cc_lxd.handle('cc_lxd', self.lxd_cfg, cc, self.logger, [])
45 self.assertTrue(mock_util.which.called)44 self.assertTrue(mock_util.which.called)
46 init_call = mock_util.subp.call_args_list[0][0][0]45 init_call = mock_util.subp.call_args_list[0][0][0]
47 self.assertEqual(init_call,46 self.assertEqual(init_call,
@@ -55,7 +54,8 @@ class TestLxd(t_help.TestCase):
55 cc = self._get_cloud('ubuntu')54 cc = self._get_cloud('ubuntu')
56 cc.distro = mock.MagicMock()55 cc.distro = mock.MagicMock()
57 mock_util.which.return_value = None56 mock_util.which.return_value = None
58 cc_lxd.handle('cc_lxd', self.lxd_cfg, cc, LOG, [])57 cc_lxd.handle('cc_lxd', self.lxd_cfg, cc, self.logger, [])
58 self.assertNotIn('WARN', self.logs.getvalue())
59 self.assertTrue(cc.distro.install_packages.called)59 self.assertTrue(cc.distro.install_packages.called)
60 install_pkg = cc.distro.install_packages.call_args_list[0][0][0]60 install_pkg = cc.distro.install_packages.call_args_list[0][0][0]
61 self.assertEqual(sorted(install_pkg), ['lxd', 'zfs'])61 self.assertEqual(sorted(install_pkg), ['lxd', 'zfs'])
@@ -64,7 +64,7 @@ class TestLxd(t_help.TestCase):
64 def test_no_init_does_nothing(self, mock_util):64 def test_no_init_does_nothing(self, mock_util):
65 cc = self._get_cloud('ubuntu')65 cc = self._get_cloud('ubuntu')
66 cc.distro = mock.MagicMock()66 cc.distro = mock.MagicMock()
67 cc_lxd.handle('cc_lxd', {'lxd': {}}, cc, LOG, [])67 cc_lxd.handle('cc_lxd', {'lxd': {}}, cc, self.logger, [])
68 self.assertFalse(cc.distro.install_packages.called)68 self.assertFalse(cc.distro.install_packages.called)
69 self.assertFalse(mock_util.subp.called)69 self.assertFalse(mock_util.subp.called)
7070
@@ -72,7 +72,7 @@ class TestLxd(t_help.TestCase):
72 def test_no_lxd_does_nothing(self, mock_util):72 def test_no_lxd_does_nothing(self, mock_util):
73 cc = self._get_cloud('ubuntu')73 cc = self._get_cloud('ubuntu')
74 cc.distro = mock.MagicMock()74 cc.distro = mock.MagicMock()
75 cc_lxd.handle('cc_lxd', {'package_update': True}, cc, LOG, [])75 cc_lxd.handle('cc_lxd', {'package_update': True}, cc, self.logger, [])
76 self.assertFalse(cc.distro.install_packages.called)76 self.assertFalse(cc.distro.install_packages.called)
77 self.assertFalse(mock_util.subp.called)77 self.assertFalse(mock_util.subp.called)
7878
diff --git a/tests/unittests/test_handler/test_handler_ntp.py b/tests/unittests/test_handler/test_handler_ntp.py
index 4f29124..3abe578 100644
--- a/tests/unittests/test_handler/test_handler_ntp.py
+++ b/tests/unittests/test_handler/test_handler_ntp.py
@@ -293,23 +293,24 @@ class TestNtp(FilesystemMockingTestCase):
293293
294 def test_ntp_handler_schema_validation_allows_empty_ntp_config(self):294 def test_ntp_handler_schema_validation_allows_empty_ntp_config(self):
295 """Ntp schema validation allows for an empty ntp: configuration."""295 """Ntp schema validation allows for an empty ntp: configuration."""
296 invalid_config = {'ntp': {}}296 valid_empty_configs = [{'ntp': {}}, {'ntp': None}]
297 distro = 'ubuntu'297 distro = 'ubuntu'
298 cc = self._get_cloud(distro)298 cc = self._get_cloud(distro)
299 ntp_conf = os.path.join(self.new_root, 'ntp.conf')299 ntp_conf = os.path.join(self.new_root, 'ntp.conf')
300 with open('{0}.tmpl'.format(ntp_conf), 'wb') as stream:300 with open('{0}.tmpl'.format(ntp_conf), 'wb') as stream:
301 stream.write(NTP_TEMPLATE)301 stream.write(NTP_TEMPLATE)
302 with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf):302 for valid_empty_config in valid_empty_configs:
303 cc_ntp.handle('cc_ntp', invalid_config, cc, None, [])303 with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf):
304 cc_ntp.handle('cc_ntp', valid_empty_config, cc, None, [])
305 with open(ntp_conf) as stream:
306 content = stream.read()
307 default_pools = [
308 "{0}.{1}.pool.ntp.org".format(x, distro)
309 for x in range(0, cc_ntp.NR_POOL_SERVERS)]
310 self.assertEqual(
311 "servers []\npools {0}\n".format(default_pools),
312 content)
304 self.assertNotIn('Invalid config:', self.logs.getvalue())313 self.assertNotIn('Invalid config:', self.logs.getvalue())
305 with open(ntp_conf) as stream:
306 content = stream.read()
307 default_pools = [
308 "{0}.{1}.pool.ntp.org".format(x, distro)
309 for x in range(0, cc_ntp.NR_POOL_SERVERS)]
310 self.assertEqual(
311 "servers []\npools {0}\n".format(default_pools),
312 content)
313314
314 @skipIf(_missing_jsonschema_dep, "No python-jsonschema dependency")315 @skipIf(_missing_jsonschema_dep, "No python-jsonschema dependency")
315 def test_ntp_handler_schema_validation_warns_non_string_item_type(self):316 def test_ntp_handler_schema_validation_warns_non_string_item_type(self):
diff --git a/tests/unittests/test_handler/test_handler_resizefs.py b/tests/unittests/test_handler/test_handler_resizefs.py
index 3e5d436..29d5574 100644
--- a/tests/unittests/test_handler/test_handler_resizefs.py
+++ b/tests/unittests/test_handler/test_handler_resizefs.py
@@ -1,9 +1,9 @@
1# This file is part of cloud-init. See LICENSE file for license information.1# This file is part of cloud-init. See LICENSE file for license information.
22
3from cloudinit.config.cc_resizefs import (3from cloudinit.config.cc_resizefs import (
4 can_skip_resize, handle, is_device_path_writable_block,4 can_skip_resize, handle, maybe_get_writable_device_path)
5 rootdev_from_cmdline)
65
6from collections import namedtuple
7import logging7import logging
8import textwrap8import textwrap
99
@@ -138,47 +138,48 @@ class TestRootDevFromCmdline(CiTestCase):
138 invalid_cases = [138 invalid_cases = [
139 'BOOT_IMAGE=/adsf asdfa werasef root adf', 'BOOT_IMAGE=/adsf', '']139 'BOOT_IMAGE=/adsf asdfa werasef root adf', 'BOOT_IMAGE=/adsf', '']
140 for case in invalid_cases:140 for case in invalid_cases:
141 self.assertIsNone(rootdev_from_cmdline(case))141 self.assertIsNone(util.rootdev_from_cmdline(case))
142142
143 def test_rootdev_from_cmdline_with_root_startswith_dev(self):143 def test_rootdev_from_cmdline_with_root_startswith_dev(self):
144 """Return the cmdline root when the path starts with /dev."""144 """Return the cmdline root when the path starts with /dev."""
145 self.assertEqual(145 self.assertEqual(
146 '/dev/this', rootdev_from_cmdline('asdf root=/dev/this'))146 '/dev/this', util.rootdev_from_cmdline('asdf root=/dev/this'))
147147
148 def test_rootdev_from_cmdline_with_root_without_dev_prefix(self):148 def test_rootdev_from_cmdline_with_root_without_dev_prefix(self):
149 """Add /dev prefix to cmdline root when the path lacks the prefix."""149 """Add /dev prefix to cmdline root when the path lacks the prefix."""
150 self.assertEqual('/dev/this', rootdev_from_cmdline('asdf root=this'))150 self.assertEqual(
151 '/dev/this', util.rootdev_from_cmdline('asdf root=this'))
151152
152 def test_rootdev_from_cmdline_with_root_with_label(self):153 def test_rootdev_from_cmdline_with_root_with_label(self):
153 """When cmdline root contains a LABEL, our root is disk/by-label."""154 """When cmdline root contains a LABEL, our root is disk/by-label."""
154 self.assertEqual(155 self.assertEqual(
155 '/dev/disk/by-label/unique',156 '/dev/disk/by-label/unique',
156 rootdev_from_cmdline('asdf root=LABEL=unique'))157 util.rootdev_from_cmdline('asdf root=LABEL=unique'))
157158
158 def test_rootdev_from_cmdline_with_root_with_uuid(self):159 def test_rootdev_from_cmdline_with_root_with_uuid(self):
159 """When cmdline root contains a UUID, our root is disk/by-uuid."""160 """When cmdline root contains a UUID, our root is disk/by-uuid."""
160 self.assertEqual(161 self.assertEqual(
161 '/dev/disk/by-uuid/adsfdsaf-adsf',162 '/dev/disk/by-uuid/adsfdsaf-adsf',
162 rootdev_from_cmdline('asdf root=UUID=adsfdsaf-adsf'))163 util.rootdev_from_cmdline('asdf root=UUID=adsfdsaf-adsf'))
163164
164165
165class TestIsDevicePathWritableBlock(CiTestCase):166class TestMaybeGetDevicePathAsWritableBlock(CiTestCase):
166167
167 with_logs = True168 with_logs = True
168169
169 def test_is_device_path_writable_block_false_on_overlayroot(self):170 def test_maybe_get_writable_device_path_none_on_overlayroot(self):
170 """When devpath is overlayroot (on MAAS), is_dev_writable is False."""171 """When devpath is overlayroot (on MAAS), is_dev_writable is False."""
171 info = 'does not matter'172 info = 'does not matter'
172 is_writable = wrap_and_call(173 devpath = wrap_and_call(
173 'cloudinit.config.cc_resizefs.util',174 'cloudinit.config.cc_resizefs.util',
174 {'is_container': {'return_value': False}},175 {'is_container': {'return_value': False}},
175 is_device_path_writable_block, 'overlayroot', info, LOG)176 maybe_get_writable_device_path, 'overlayroot', info, LOG)
176 self.assertFalse(is_writable)177 self.assertIsNone(devpath)
177 self.assertIn(178 self.assertIn(
178 "Not attempting to resize devpath 'overlayroot'",179 "Not attempting to resize devpath 'overlayroot'",
179 self.logs.getvalue())180 self.logs.getvalue())
180181
181 def test_is_device_path_writable_block_warns_missing_cmdline_root(self):182 def test_maybe_get_writable_device_path_warns_missing_cmdline_root(self):
182 """When root does not exist isn't in the cmdline, log warning."""183 """When root does not exist isn't in the cmdline, log warning."""
183 info = 'does not matter'184 info = 'does not matter'
184185
@@ -190,43 +191,43 @@ class TestIsDevicePathWritableBlock(CiTestCase):
190 exists_mock_path = 'cloudinit.config.cc_resizefs.os.path.exists'191 exists_mock_path = 'cloudinit.config.cc_resizefs.os.path.exists'
191 with mock.patch(exists_mock_path) as m_exists:192 with mock.patch(exists_mock_path) as m_exists:
192 m_exists.return_value = False193 m_exists.return_value = False
193 is_writable = wrap_and_call(194 devpath = wrap_and_call(
194 'cloudinit.config.cc_resizefs.util',195 'cloudinit.config.cc_resizefs.util',
195 {'is_container': {'return_value': False},196 {'is_container': {'return_value': False},
196 'get_mount_info': {'side_effect': fake_mount_info},197 'get_mount_info': {'side_effect': fake_mount_info},
197 'get_cmdline': {'return_value': 'BOOT_IMAGE=/vmlinuz.efi'}},198 'get_cmdline': {'return_value': 'BOOT_IMAGE=/vmlinuz.efi'}},
198 is_device_path_writable_block, '/dev/root', info, LOG)199 maybe_get_writable_device_path, '/dev/root', info, LOG)
199 self.assertFalse(is_writable)200 self.assertIsNone(devpath)
200 logs = self.logs.getvalue()201 logs = self.logs.getvalue()
201 self.assertIn("WARNING: Unable to find device '/dev/root'", logs)202 self.assertIn("WARNING: Unable to find device '/dev/root'", logs)
202203
203 def test_is_device_path_writable_block_does_not_exist(self):204 def test_maybe_get_writable_device_path_does_not_exist(self):
204 """When devpath does not exist, a warning is logged."""205 """When devpath does not exist, a warning is logged."""
205 info = 'dev=/I/dont/exist mnt_point=/ path=/dev/none'206 info = 'dev=/I/dont/exist mnt_point=/ path=/dev/none'
206 is_writable = wrap_and_call(207 devpath = wrap_and_call(
207 'cloudinit.config.cc_resizefs.util',208 'cloudinit.config.cc_resizefs.util',
208 {'is_container': {'return_value': False}},209 {'is_container': {'return_value': False}},
209 is_device_path_writable_block, '/I/dont/exist', info, LOG)210 maybe_get_writable_device_path, '/I/dont/exist', info, LOG)
210 self.assertFalse(is_writable)211 self.assertIsNone(devpath)
211 self.assertIn(212 self.assertIn(
212 "WARNING: Device '/I/dont/exist' did not exist."213 "WARNING: Device '/I/dont/exist' did not exist."
213 ' cannot resize: %s' % info,214 ' cannot resize: %s' % info,
214 self.logs.getvalue())215 self.logs.getvalue())
215216
216 def test_is_device_path_writable_block_does_not_exist_in_container(self):217 def test_maybe_get_writable_device_path_does_not_exist_in_container(self):
217 """When devpath does not exist in a container, log a debug message."""218 """When devpath does not exist in a container, log a debug message."""
218 info = 'dev=/I/dont/exist mnt_point=/ path=/dev/none'219 info = 'dev=/I/dont/exist mnt_point=/ path=/dev/none'
219 is_writable = wrap_and_call(220 devpath = wrap_and_call(
220 'cloudinit.config.cc_resizefs.util',221 'cloudinit.config.cc_resizefs.util',
221 {'is_container': {'return_value': True}},222 {'is_container': {'return_value': True}},
222 is_device_path_writable_block, '/I/dont/exist', info, LOG)223 maybe_get_writable_device_path, '/I/dont/exist', info, LOG)
223 self.assertFalse(is_writable)224 self.assertIsNone(devpath)
224 self.assertIn(225 self.assertIn(
225 "DEBUG: Device '/I/dont/exist' did not exist in container."226 "DEBUG: Device '/I/dont/exist' did not exist in container."
226 ' cannot resize: %s' % info,227 ' cannot resize: %s' % info,
227 self.logs.getvalue())228 self.logs.getvalue())
228229
229 def test_is_device_path_writable_block_raises_oserror(self):230 def test_maybe_get_writable_device_path_raises_oserror(self):
230 """When unexpected OSError is raises by os.stat it is reraised."""231 """When unexpected OSError is raises by os.stat it is reraised."""
231 info = 'dev=/I/dont/exist mnt_point=/ path=/dev/none'232 info = 'dev=/I/dont/exist mnt_point=/ path=/dev/none'
232 with self.assertRaises(OSError) as context_manager:233 with self.assertRaises(OSError) as context_manager:
@@ -234,41 +235,63 @@ class TestIsDevicePathWritableBlock(CiTestCase):
234 'cloudinit.config.cc_resizefs',235 'cloudinit.config.cc_resizefs',
235 {'util.is_container': {'return_value': True},236 {'util.is_container': {'return_value': True},
236 'os.stat': {'side_effect': OSError('Something unexpected')}},237 'os.stat': {'side_effect': OSError('Something unexpected')}},
237 is_device_path_writable_block, '/I/dont/exist', info, LOG)238 maybe_get_writable_device_path, '/I/dont/exist', info, LOG)
238 self.assertEqual(239 self.assertEqual(
239 'Something unexpected', str(context_manager.exception))240 'Something unexpected', str(context_manager.exception))
240241
241 def test_is_device_path_writable_block_non_block(self):242 def test_maybe_get_writable_device_path_non_block(self):
242 """When device is not a block device, emit warning return False."""243 """When device is not a block device, emit warning return False."""
243 fake_devpath = self.tmp_path('dev/readwrite')244 fake_devpath = self.tmp_path('dev/readwrite')
244 util.write_file(fake_devpath, '', mode=0o600) # read-write245 util.write_file(fake_devpath, '', mode=0o600) # read-write
245 info = 'dev=/dev/root mnt_point=/ path={0}'.format(fake_devpath)246 info = 'dev=/dev/root mnt_point=/ path={0}'.format(fake_devpath)
246247
247 is_writable = wrap_and_call(248 devpath = wrap_and_call(
248 'cloudinit.config.cc_resizefs.util',249 'cloudinit.config.cc_resizefs.util',
249 {'is_container': {'return_value': False}},250 {'is_container': {'return_value': False}},
250 is_device_path_writable_block, fake_devpath, info, LOG)251 maybe_get_writable_device_path, fake_devpath, info, LOG)
251 self.assertFalse(is_writable)252 self.assertIsNone(devpath)
252 self.assertIn(253 self.assertIn(
253 "WARNING: device '{0}' not a block device. cannot resize".format(254 "WARNING: device '{0}' not a block device. cannot resize".format(
254 fake_devpath),255 fake_devpath),
255 self.logs.getvalue())256 self.logs.getvalue())
256257
257 def test_is_device_path_writable_block_non_block_on_container(self):258 def test_maybe_get_writable_device_path_non_block_on_container(self):
258 """When device is non-block device in container, emit debug log."""259 """When device is non-block device in container, emit debug log."""
259 fake_devpath = self.tmp_path('dev/readwrite')260 fake_devpath = self.tmp_path('dev/readwrite')
260 util.write_file(fake_devpath, '', mode=0o600) # read-write261 util.write_file(fake_devpath, '', mode=0o600) # read-write
261 info = 'dev=/dev/root mnt_point=/ path={0}'.format(fake_devpath)262 info = 'dev=/dev/root mnt_point=/ path={0}'.format(fake_devpath)
262263
263 is_writable = wrap_and_call(264 devpath = wrap_and_call(
264 'cloudinit.config.cc_resizefs.util',265 'cloudinit.config.cc_resizefs.util',
265 {'is_container': {'return_value': True}},266 {'is_container': {'return_value': True}},
266 is_device_path_writable_block, fake_devpath, info, LOG)267 maybe_get_writable_device_path, fake_devpath, info, LOG)
267 self.assertFalse(is_writable)268 self.assertIsNone(devpath)
268 self.assertIn(269 self.assertIn(
269 "DEBUG: device '{0}' not a block device in container."270 "DEBUG: device '{0}' not a block device in container."
270 ' cannot resize'.format(fake_devpath),271 ' cannot resize'.format(fake_devpath),
271 self.logs.getvalue())272 self.logs.getvalue())
272273
274 def test_maybe_get_writable_device_path_returns_cmdline_root(self):
275 """When root device is UUID in kernel commandline, update devpath."""
276 # XXX Long-term we want to use FilesystemMocking test to avoid
277 # touching os.stat.
278 FakeStat = namedtuple(
279 'FakeStat', ['st_mode', 'st_size', 'st_mtime']) # minimal def.
280 info = 'dev=/dev/root mnt_point=/ path=/does/not/matter'
281 devpath = wrap_and_call(
282 'cloudinit.config.cc_resizefs',
283 {'util.get_cmdline': {'return_value': 'asdf root=UUID=my-uuid'},
284 'util.is_container': False,
285 'os.path.exists': False, # /dev/root doesn't exist
286 'os.stat': {
287 'return_value': FakeStat(25008, 0, 1)} # char block device
288 },
289 maybe_get_writable_device_path, '/dev/root', info, LOG)
290 self.assertEqual('/dev/disk/by-uuid/my-uuid', devpath)
291 self.assertIn(
292 "DEBUG: Converted /dev/root to '/dev/disk/by-uuid/my-uuid'"
293 " per kernel cmdline",
294 self.logs.getvalue())
295
273296
274# vi: ts=4 expandtab297# vi: ts=4 expandtab
diff --git a/tests/unittests/test_handler/test_schema.py b/tests/unittests/test_handler/test_schema.py
index b8fc893..648573f 100644
--- a/tests/unittests/test_handler/test_schema.py
+++ b/tests/unittests/test_handler/test_schema.py
@@ -4,11 +4,12 @@ from cloudinit.config.schema import (
4 CLOUD_CONFIG_HEADER, SchemaValidationError, annotated_cloudconfig_file,4 CLOUD_CONFIG_HEADER, SchemaValidationError, annotated_cloudconfig_file,
5 get_schema_doc, get_schema, validate_cloudconfig_file,5 get_schema_doc, get_schema, validate_cloudconfig_file,
6 validate_cloudconfig_schema, main)6 validate_cloudconfig_schema, main)
7from cloudinit.util import write_file7from cloudinit.util import subp, write_file
88
9from cloudinit.tests.helpers import CiTestCase, mock, skipIf9from cloudinit.tests.helpers import CiTestCase, mock, skipIf
1010
11from copy import copy11from copy import copy
12import os
12from six import StringIO13from six import StringIO
13from textwrap import dedent14from textwrap import dedent
14from yaml import safe_load15from yaml import safe_load
@@ -364,4 +365,38 @@ class MainTest(CiTestCase):
364 self.assertIn(365 self.assertIn(
365 'Valid cloud-config file {0}'.format(myyaml), m_stdout.getvalue())366 'Valid cloud-config file {0}'.format(myyaml), m_stdout.getvalue())
366367
368
369class CloudTestsIntegrationTest(CiTestCase):
370 """Validate all cloud-config yaml schema provided in integration tests.
371
372 It is less expensive to have unittests validate schema of all cloud-config
373 yaml provided to integration tests, than to run an integration test which
374 raises Warnings or errors on invalid cloud-config schema.
375 """
376
377 @skipIf(_missing_jsonschema_dep, "No python-jsonschema dependency")
378 def test_all_integration_test_cloud_config_schema(self):
379 """Validate schema of cloud_tests yaml files looking for warnings."""
380 schema = get_schema()
381 testsdir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
382 integration_testdir = os.path.sep.join(
383 [testsdir, 'cloud_tests', 'testcases'])
384 errors = []
385 out, _ = subp(['find', integration_testdir, '-name', '*yaml'])
386 for filename in out.splitlines():
387 test_cfg = safe_load(open(filename))
388 cloud_config = test_cfg.get('cloud_config')
389 if cloud_config:
390 cloud_config = safe_load(
391 cloud_config.replace("#cloud-config\n", ""))
392 try:
393 validate_cloudconfig_schema(
394 cloud_config, schema, strict=True)
395 except SchemaValidationError as e:
396 errors.append(
397 '{0}: {1}'.format(
398 filename, e))
399 if errors:
400 raise AssertionError(', '.join(errors))
401
367# vi: ts=4 expandtab syntax=python402# vi: ts=4 expandtab syntax=python
diff --git a/tools/read-dependencies b/tools/read-dependencies
index 2a64868..421f470 100755
--- a/tools/read-dependencies
+++ b/tools/read-dependencies
@@ -30,9 +30,35 @@ DISTRO_PKG_TYPE_MAP = {
30 'suse': 'suse'30 'suse': 'suse'
31}31}
3232
33DISTRO_INSTALL_PKG_CMD = {33MAYBE_RELIABLE_YUM_INSTALL = [
34 'sh', '-c',
35 """
36 error() { echo "$@" 1>&2; }
37 n=0; max=10;
38 bcmd="yum install --downloadonly --assumeyes --setopt=keepcache=1"
39 while n=$(($n+1)); do
40 error ":: running $bcmd $* [$n/$max]"
41 $bcmd "$@"
42 r=$?
43 [ $r -eq 0 ] && break
44 [ $n -ge $max ] && { error "gave up on $bcmd"; exit $r; }
45 nap=$(($n*5))
46 error ":: failed [$r] ($n/$max). sleeping $nap."
47 sleep $nap
48 done
49 error ":: running yum install --cacheonly --assumeyes $*"
50 yum install --cacheonly --assumeyes "$@"
51 """,
52 'reliable-yum-install']
53
54DRY_DISTRO_INSTALL_PKG_CMD = {
34 'centos': ['yum', 'install', '--assumeyes'],55 'centos': ['yum', 'install', '--assumeyes'],
35 'redhat': ['yum', 'install', '--assumeyes'],56 'redhat': ['yum', 'install', '--assumeyes'],
57}
58
59DISTRO_INSTALL_PKG_CMD = {
60 'centos': MAYBE_RELIABLE_YUM_INSTALL,
61 'redhat': MAYBE_RELIABLE_YUM_INSTALL,
36 'debian': ['apt', 'install', '-y'],62 'debian': ['apt', 'install', '-y'],
37 'ubuntu': ['apt', 'install', '-y'],63 'ubuntu': ['apt', 'install', '-y'],
38 'opensuse': ['zypper', 'install'],64 'opensuse': ['zypper', 'install'],
@@ -80,8 +106,8 @@ def get_parser():
80 help='Additionally install continuous integration system packages '106 help='Additionally install continuous integration system packages '
81 'required for build and test automation.')107 'required for build and test automation.')
82 parser.add_argument(108 parser.add_argument(
83 '-v', '--python-version', type=str, dest='python_version', default=None,109 '-v', '--python-version', type=str, dest='python_version',
84 choices=["2", "3"],110 default=None, choices=["2", "3"],
85 help='Override the version of python we want to generate system '111 help='Override the version of python we want to generate system '
86 'package dependencies for. Defaults to the version of python '112 'package dependencies for. Defaults to the version of python '
87 'this script is called with')113 'this script is called with')
@@ -219,10 +245,15 @@ def pkg_install(pkg_list, distro, test_distro=False, dry_run=False):
219 '(dryrun)' if dry_run else '', ' '.join(pkg_list)))245 '(dryrun)' if dry_run else '', ' '.join(pkg_list)))
220 install_cmd = []246 install_cmd = []
221 if dry_run:247 if dry_run:
222 install_cmd.append('echo')248 install_cmd.append('echo')
223 if os.geteuid() != 0:249 if os.geteuid() != 0:
224 install_cmd.append('sudo')250 install_cmd.append('sudo')
225 install_cmd.extend(DISTRO_INSTALL_PKG_CMD[distro])251
252 cmd = DISTRO_INSTALL_PKG_CMD[distro]
253 if dry_run and distro in DRY_DISTRO_INSTALL_PKG_CMD:
254 cmd = DRY_DISTRO_INSTALL_PKG_CMD[distro]
255 install_cmd.extend(cmd)
256
226 if distro in ['centos', 'redhat']:257 if distro in ['centos', 'redhat']:
227 # CentOS and Redhat need epel-release to access oauthlib and jsonschema258 # CentOS and Redhat need epel-release to access oauthlib and jsonschema
228 subprocess.check_call(install_cmd + ['epel-release'])259 subprocess.check_call(install_cmd + ['epel-release'])
diff --git a/tools/run-centos b/tools/run-centos
index d44d514..d58ef3e 100755
--- a/tools/run-centos
+++ b/tools/run-centos
@@ -123,7 +123,22 @@ prep() {
123 return 0123 return 0
124 fi124 fi
125 error "Installing prep packages: ${needed}"125 error "Installing prep packages: ${needed}"
126 yum install --assumeyes ${needed}126 set -- $needed
127 local n max r
128 n=0; max=10;
129 bcmd="yum install --downloadonly --assumeyes --setopt=keepcache=1"
130 while n=$(($n+1)); do
131 error ":: running $bcmd $* [$n/$max]"
132 $bcmd "$@"
133 r=$?
134 [ $r -eq 0 ] && break
135 [ $n -ge $max ] && { error "gave up on $bcmd"; exit $r; }
136 nap=$(($n*5))
137 error ":: failed [$r] ($n/$max). sleeping $nap."
138 sleep $nap
139 done
140 error ":: running yum install --cacheonly --assumeyes $*"
141 yum install --cacheonly --assumeyes "$@"
127}142}
128143
129start_container() {144start_container() {
@@ -153,6 +168,7 @@ start_container() {
153 if [ ! -z "${http_proxy-}" ]; then168 if [ ! -z "${http_proxy-}" ]; then
154 debug 1 "configuring proxy ${http_proxy}"169 debug 1 "configuring proxy ${http_proxy}"
155 inside "$name" sh -c "echo proxy=$http_proxy >> /etc/yum.conf"170 inside "$name" sh -c "echo proxy=$http_proxy >> /etc/yum.conf"
171 inside "$name" sed -i s/enabled=1/enabled=0/ /etc/yum/pluginconf.d/fastestmirror.conf
156 fi172 fi
157}173}
158174

Subscribers

People subscribed via source and target branches