Merge ~chad.smith/cloud-init:ubuntu/artful into cloud-init:ubuntu/artful
- Git
- lp:~chad.smith/cloud-init
- ubuntu/artful
- Merge into ubuntu/artful
Proposed by
Chad Smith
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Merged at revision: | e5596beef0618204dedd94e1cd593a8cfb6bb6c7 | ||||||||
Proposed branch: | ~chad.smith/cloud-init:ubuntu/artful | ||||||||
Merge into: | cloud-init:ubuntu/artful | ||||||||
Diff against target: |
656 lines (+630/-0) 4 files modified
debian/changelog (+9/-0) debian/patches/cpick-11172924-IBMCloud-Disable-config-drive-and-nocloud-only-if (+230/-0) debian/patches/cpick-6ef92c98-IBMCloud-recognize-provisioning-environment-during (+389/-0) debian/patches/series (+2/-0) |
||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Scott Moser | Pending | ||
Review via email: mp+344901@code.launchpad.net |
Description of the change
To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote : | # |
review:
Approve
(continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:e5596beef06
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:
https:/
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/debian/changelog b/debian/changelog |
2 | index 982d0c8..c438253 100644 |
3 | --- a/debian/changelog |
4 | +++ b/debian/changelog |
5 | @@ -1,3 +1,12 @@ |
6 | +cloud-init (18.2-4-g05926e48-0ubuntu1~17.10.2) artful-proposed; urgency=medium |
7 | + |
8 | + * cherry-pick 11172924: IBMCloud: Disable config-drive and nocloud |
9 | + only if IBMCloud (LP: #1766401) |
10 | + * cherry-pick 6ef92c98: IBMCloud: recognize provisioning environment |
11 | + during debug (LP: #1767166) |
12 | + |
13 | + -- Chad Smith <chad.smith@canonical.com> Tue, 01 May 2018 10:36:14 -0600 |
14 | + |
15 | cloud-init (18.2-4-g05926e48-0ubuntu1~17.10.1) artful-proposed; urgency=medium |
16 | |
17 | * debian/new-upstream-snapshot: Remove script, now maintained elsewhere. |
18 | diff --git a/debian/patches/cpick-11172924-IBMCloud-Disable-config-drive-and-nocloud-only-if b/debian/patches/cpick-11172924-IBMCloud-Disable-config-drive-and-nocloud-only-if |
19 | new file mode 100644 |
20 | index 0000000..534aa73 |
21 | --- /dev/null |
22 | +++ b/debian/patches/cpick-11172924-IBMCloud-Disable-config-drive-and-nocloud-only-if |
23 | @@ -0,0 +1,230 @@ |
24 | +From 11172924a48a47a7231d19d9cefe628dfddda8bf Mon Sep 17 00:00:00 2001 |
25 | +From: Scott Moser <smoser@ubuntu.com> |
26 | +Date: Mon, 30 Apr 2018 13:21:51 -0600 |
27 | +Subject: [PATCH] IBMCloud: Disable config-drive and nocloud only if IBMCloud |
28 | + is enabled. |
29 | + |
30 | +Ubuntu images on IBMCloud for 16.04 have some seed data in |
31 | +/var/lib/cloud/data/seed/nocloud-net. In order to have systems with |
32 | +IBMCloud enabled, we modified ds-identify detection to skip that seed |
33 | +if the system was on IBMCloud. That change did not consider the |
34 | +fact that IBMCloud might not be in the datasource list. |
35 | + |
36 | +There was similar logic in the ConfigDrive datasource in ds-identify |
37 | +and the datasource itself. |
38 | + |
39 | +Config drive is now updated to only check and avoid IBMCloud if IBMCloud |
40 | +is enabled. The check in ds-identify for nocloud was dropped. If a |
41 | +user provides a nocloud seed on IBMCloud, then that can be used. |
42 | + |
43 | +This means that systems running Xenial will continue to get their |
44 | +old datasources. |
45 | + |
46 | +LP: #1766401 |
47 | +--- |
48 | + cloudinit/sources/DataSourceConfigDrive.py | 11 +++-- |
49 | + tests/unittests/test_ds_identify.py | 77 +++++++++++++++++++++++++++--- |
50 | + tools/ds-identify | 17 +++++-- |
51 | + 3 files changed, 91 insertions(+), 14 deletions(-) |
52 | + |
53 | +--- a/cloudinit/sources/DataSourceConfigDrive.py |
54 | ++++ b/cloudinit/sources/DataSourceConfigDrive.py |
55 | +@@ -69,7 +69,8 @@ |
56 | + util.logexc(LOG, "Failed reading config drive from %s", sdir) |
57 | + |
58 | + if not found: |
59 | +- for dev in find_candidate_devs(): |
60 | ++ dslist = self.sys_cfg.get('datasource_list') |
61 | ++ for dev in find_candidate_devs(dslist=dslist): |
62 | + try: |
63 | + # Set mtype if freebsd and turn off sync |
64 | + if dev.startswith("/dev/cd"): |
65 | +@@ -211,7 +212,7 @@ |
66 | + util.logexc(LOG, "Failed writing file: %s", filename) |
67 | + |
68 | + |
69 | +-def find_candidate_devs(probe_optical=True): |
70 | ++def find_candidate_devs(probe_optical=True, dslist=None): |
71 | + """Return a list of devices that may contain the config drive. |
72 | + |
73 | + The returned list is sorted by search order where the first item has |
74 | +@@ -227,6 +228,9 @@ |
75 | + * either vfat or iso9660 formated |
76 | + * labeled with 'config-2' or 'CONFIG-2' |
77 | + """ |
78 | ++ if dslist is None: |
79 | ++ dslist = [] |
80 | ++ |
81 | + # query optical drive to get it in blkid cache for 2.6 kernels |
82 | + if probe_optical: |
83 | + for device in OPTICAL_DEVICES: |
84 | +@@ -257,7 +261,8 @@ |
85 | + devices = [d for d in candidates |
86 | + if d in by_label or not util.is_partition(d)] |
87 | + |
88 | +- if devices: |
89 | ++ LOG.debug("devices=%s dslist=%s", devices, dslist) |
90 | ++ if devices and "IBMCloud" in dslist: |
91 | + # IBMCloud uses config-2 label, but limited to a single UUID. |
92 | + ibm_platform, ibm_path = get_ibm_platform() |
93 | + if ibm_path in devices: |
94 | +--- a/tests/unittests/test_ds_identify.py |
95 | ++++ b/tests/unittests/test_ds_identify.py |
96 | +@@ -178,17 +178,18 @@ |
97 | + data, RC_FOUND, dslist=[data.get('ds'), DS_NONE]) |
98 | + |
99 | + def _check_via_dict(self, data, rc, dslist=None, **kwargs): |
100 | +- found_rc, out, err, cfg, files = self._call_via_dict(data, **kwargs) |
101 | ++ ret = self._call_via_dict(data, **kwargs) |
102 | + good = False |
103 | + try: |
104 | +- self.assertEqual(rc, found_rc) |
105 | ++ self.assertEqual(rc, ret.rc) |
106 | + if dslist is not None: |
107 | +- self.assertEqual(dslist, cfg['datasource_list']) |
108 | ++ self.assertEqual(dslist, ret.cfg['datasource_list']) |
109 | + good = True |
110 | + finally: |
111 | + if not good: |
112 | +- _print_run_output(rc, out, err, cfg, files) |
113 | +- return rc, out, err, cfg, files |
114 | ++ _print_run_output(ret.rc, ret.stdout, ret.stderr, ret.cfg, |
115 | ++ ret.files) |
116 | ++ return ret |
117 | + |
118 | + def test_wb_print_variables(self): |
119 | + """_print_info reports an array of discovered variables to stderr.""" |
120 | +@@ -237,13 +238,40 @@ |
121 | + def test_config_drive(self): |
122 | + """ConfigDrive datasource has a disk with LABEL=config-2.""" |
123 | + self._test_ds_found('ConfigDrive') |
124 | +- return |
125 | + |
126 | + def test_config_drive_upper(self): |
127 | + """ConfigDrive datasource has a disk with LABEL=CONFIG-2.""" |
128 | + self._test_ds_found('ConfigDriveUpper') |
129 | + return |
130 | + |
131 | ++ def test_config_drive_seed(self): |
132 | ++ """Config Drive seed directory.""" |
133 | ++ self._test_ds_found('ConfigDrive-seed') |
134 | ++ |
135 | ++ def test_config_drive_interacts_with_ibmcloud_config_disk(self): |
136 | ++ """Verify ConfigDrive interaction with IBMCloud. |
137 | ++ |
138 | ++ If ConfigDrive is enabled and not IBMCloud, then ConfigDrive |
139 | ++ should claim the ibmcloud 'config-2' disk. |
140 | ++ If IBMCloud is enabled, then ConfigDrive should skip.""" |
141 | ++ data = copy.deepcopy(VALID_CFG['IBMCloud-config-2']) |
142 | ++ files = data.get('files', {}) |
143 | ++ if not files: |
144 | ++ data['files'] = files |
145 | ++ cfgpath = 'etc/cloud/cloud.cfg.d/99_networklayer_common.cfg' |
146 | ++ |
147 | ++ # with list including IBMCloud, config drive should be not found. |
148 | ++ files[cfgpath] = 'datasource_list: [ ConfigDrive, IBMCloud ]\n' |
149 | ++ ret = self._check_via_dict(data, shell_true) |
150 | ++ self.assertEqual( |
151 | ++ ret.cfg.get('datasource_list'), ['IBMCloud', 'None']) |
152 | ++ |
153 | ++ # But if IBMCloud is not enabled, config drive should claim this. |
154 | ++ files[cfgpath] = 'datasource_list: [ ConfigDrive, NoCloud ]\n' |
155 | ++ ret = self._check_via_dict(data, shell_true) |
156 | ++ self.assertEqual( |
157 | ++ ret.cfg.get('datasource_list'), ['ConfigDrive', 'None']) |
158 | ++ |
159 | + def test_ibmcloud_template_userdata_in_provisioning(self): |
160 | + """Template provisioned with user-data during provisioning stage. |
161 | + |
162 | +@@ -295,6 +323,37 @@ |
163 | + self._check_via_dict( |
164 | + data, rc=RC_FOUND, dslist=['ConfigDrive', DS_NONE]) |
165 | + |
166 | ++ def test_ibmcloud_with_nocloud_seed(self): |
167 | ++ """NoCloud seed should be preferred over IBMCloud. |
168 | ++ |
169 | ++ A nocloud seed should be preferred over IBMCloud even if enabled. |
170 | ++ Ubuntu 16.04 images have <vlc>/seed/nocloud-net. LP: #1766401.""" |
171 | ++ data = copy.deepcopy(VALID_CFG['IBMCloud-config-2']) |
172 | ++ files = data.get('files', {}) |
173 | ++ if not files: |
174 | ++ data['files'] = files |
175 | ++ files.update(VALID_CFG['NoCloud-seed']['files']) |
176 | ++ ret = self._check_via_dict(data, shell_true) |
177 | ++ self.assertEqual( |
178 | ++ ['NoCloud', 'IBMCloud', 'None'], |
179 | ++ ret.cfg.get('datasource_list')) |
180 | ++ |
181 | ++ def test_ibmcloud_with_configdrive_seed(self): |
182 | ++ """ConfigDrive seed should be preferred over IBMCloud. |
183 | ++ |
184 | ++ A ConfigDrive seed should be preferred over IBMCloud even if enabled. |
185 | ++ Ubuntu 16.04 images have a fstab entry that mounts the |
186 | ++ METADATA disk into <vlc>/seed/config_drive. LP: ##1766401.""" |
187 | ++ data = copy.deepcopy(VALID_CFG['IBMCloud-config-2']) |
188 | ++ files = data.get('files', {}) |
189 | ++ if not files: |
190 | ++ data['files'] = files |
191 | ++ files.update(VALID_CFG['ConfigDrive-seed']['files']) |
192 | ++ ret = self._check_via_dict(data, shell_true) |
193 | ++ self.assertEqual( |
194 | ++ ['ConfigDrive', 'IBMCloud', 'None'], |
195 | ++ ret.cfg.get('datasource_list')) |
196 | ++ |
197 | + def test_policy_disabled(self): |
198 | + """A Builtin policy of 'disabled' should return not found. |
199 | + |
200 | +@@ -631,6 +690,12 @@ |
201 | + }, |
202 | + ], |
203 | + }, |
204 | ++ 'ConfigDrive-seed': { |
205 | ++ 'ds': 'ConfigDrive', |
206 | ++ 'files': { |
207 | ++ os.path.join(P_SEED_DIR, 'config_drive', 'openstack', |
208 | ++ 'latest', 'meta_data.json'): 'md\n'}, |
209 | ++ }, |
210 | + 'Hetzner': { |
211 | + 'ds': 'Hetzner', |
212 | + 'files': {P_SYS_VENDOR: 'Hetzner\n'}, |
213 | +--- a/tools/ds-identify |
214 | ++++ b/tools/ds-identify |
215 | +@@ -600,7 +600,6 @@ |
216 | + *\ ds=nocloud*) return ${DS_FOUND};; |
217 | + esac |
218 | + |
219 | +- is_ibm_cloud && return ${DS_NOT_FOUND} |
220 | + for d in nocloud nocloud-net; do |
221 | + check_seed_dir "$d" meta-data user-data && return ${DS_FOUND} |
222 | + check_writable_seed_dir "$d" meta-data user-data && return ${DS_FOUND} |
223 | +@@ -611,11 +610,12 @@ |
224 | + return ${DS_NOT_FOUND} |
225 | + } |
226 | + |
227 | ++is_ds_enabled() { |
228 | ++ local name="$1" pad=" ${DI_DSLIST} " |
229 | ++ [ "${pad#* $name }" != "${pad}" ] |
230 | ++} |
231 | ++ |
232 | + check_configdrive_v2() { |
233 | +- is_ibm_cloud && return ${DS_NOT_FOUND} |
234 | +- if has_fs_with_label CONFIG-2 config-2; then |
235 | +- return ${DS_FOUND} |
236 | +- fi |
237 | + # look in /config-drive <vlc>/seed/config_drive for a directory |
238 | + # openstack/YYYY-MM-DD format with a file meta_data.json |
239 | + local d="" |
240 | +@@ -630,6 +630,13 @@ |
241 | + debug 1 "config drive seeded directory had only 'latest'" |
242 | + return ${DS_FOUND} |
243 | + fi |
244 | ++ |
245 | ++ is_ds_enabled "IBMCloud" |
246 | ++ debug 1 "is_ds_enabled returned $?: $DI_DSLIST" |
247 | ++ is_ds_enabled "IBMCloud" && is_ibm_cloud && return ${DS_NOT_FOUND} |
248 | ++ if has_fs_with_label CONFIG-2 config-2; then |
249 | ++ return ${DS_FOUND} |
250 | ++ fi |
251 | + return ${DS_NOT_FOUND} |
252 | + } |
253 | + |
254 | diff --git a/debian/patches/cpick-6ef92c98-IBMCloud-recognize-provisioning-environment-during b/debian/patches/cpick-6ef92c98-IBMCloud-recognize-provisioning-environment-during |
255 | new file mode 100644 |
256 | index 0000000..b428120 |
257 | --- /dev/null |
258 | +++ b/debian/patches/cpick-6ef92c98-IBMCloud-recognize-provisioning-environment-during |
259 | @@ -0,0 +1,389 @@ |
260 | +From 6ef92c98c3d2b127b05d6708337efc8a81e00071 Mon Sep 17 00:00:00 2001 |
261 | +From: Scott Moser <smoser@ubuntu.com> |
262 | +Date: Thu, 26 Apr 2018 16:24:24 -0500 |
263 | +Subject: [PATCH] IBMCloud: recognize provisioning environment during debug |
264 | + boots. |
265 | + |
266 | +When images are deployed from template in a production environment |
267 | +the artifacts of the provisioning stage (provisioningConfiguration.cfg) |
268 | +that cloud-init referenced are cleaned up. However, when provisioned |
269 | +in "debug" mode (internal to IBM) the artifacts are left. |
270 | + |
271 | +This changes the 'is_ibm_provisioning' implementations in both |
272 | +ds-identify and in the IBM datasource to identify the provisioning |
273 | +stage more correctly. The change is to consider provisioning only |
274 | +if the provisioing file existed and there was no log file or |
275 | +the log file was older than this boot. |
276 | + |
277 | +LP: #1767166 |
278 | +--- |
279 | + cloudinit/sources/DataSourceIBMCloud.py | 42 +++++++++----- |
280 | + cloudinit/tests/helpers.py | 13 ++++- |
281 | + tests/unittests/test_datasource/test_ibmcloud.py | 50 ++++++++++++++++ |
282 | + tests/unittests/test_ds_identify.py | 72 +++++++++++++++++++++--- |
283 | + tools/ds-identify | 21 ++++++- |
284 | + 5 files changed, 175 insertions(+), 23 deletions(-) |
285 | + |
286 | +--- a/cloudinit/sources/DataSourceIBMCloud.py |
287 | ++++ b/cloudinit/sources/DataSourceIBMCloud.py |
288 | +@@ -8,17 +8,11 @@ |
289 | + * template: This is the legacy method of launching instances. |
290 | + When booting from an image template, the system boots first into |
291 | + a "provisioning" mode. There, host <-> guest mechanisms are utilized |
292 | +- to execute code in the guest and provision it. |
293 | ++ to execute code in the guest and configure it. The configuration |
294 | ++ includes configuring the system network and possibly installing |
295 | ++ packages and other software stack. |
296 | + |
297 | +- Cloud-init will disable itself when it detects that it is in the |
298 | +- provisioning mode. It detects this by the presence of |
299 | +- a file '/root/provisioningConfiguration.cfg'. |
300 | +- |
301 | +- When provided with user-data, the "first boot" will contain a |
302 | +- ConfigDrive-like disk labeled with 'METADATA'. If there is no user-data |
303 | +- provided, then there is no data-source. |
304 | +- |
305 | +- Cloud-init never does any network configuration in this mode. |
306 | ++ After the provisioning is finished, the system reboots. |
307 | + |
308 | + * os_code: Essentially "launch by OS Code" (Operating System Code). |
309 | + This is a more modern approach. There is no specific "provisioning" boot. |
310 | +@@ -138,8 +132,30 @@ |
311 | + return os.path.exists("/proc/xen") |
312 | + |
313 | + |
314 | +-def _is_ibm_provisioning(): |
315 | +- return os.path.exists("/root/provisioningConfiguration.cfg") |
316 | ++def _is_ibm_provisioning( |
317 | ++ prov_cfg="/root/provisioningConfiguration.cfg", |
318 | ++ inst_log="/root/swinstall.log", |
319 | ++ boot_ref="/proc/1/environ"): |
320 | ++ """Return boolean indicating if this boot is ibm provisioning boot.""" |
321 | ++ if os.path.exists(prov_cfg): |
322 | ++ msg = "config '%s' exists." % prov_cfg |
323 | ++ result = True |
324 | ++ if os.path.exists(inst_log): |
325 | ++ if os.path.exists(boot_ref): |
326 | ++ result = (os.stat(inst_log).st_mtime > |
327 | ++ os.stat(boot_ref).st_mtime) |
328 | ++ msg += (" log '%s' from %s boot." % |
329 | ++ (inst_log, "current" if result else "previous")) |
330 | ++ else: |
331 | ++ msg += (" log '%s' existed, but no reference file '%s'." % |
332 | ++ (inst_log, boot_ref)) |
333 | ++ result = False |
334 | ++ else: |
335 | ++ msg += " log '%s' did not exist." % inst_log |
336 | ++ else: |
337 | ++ result, msg = (False, "config '%s' did not exist." % prov_cfg) |
338 | ++ LOG.debug("ibm_provisioning=%s: %s", result, msg) |
339 | ++ return result |
340 | + |
341 | + |
342 | + def get_ibm_platform(): |
343 | +@@ -189,7 +205,7 @@ |
344 | + else: |
345 | + return (Platforms.TEMPLATE_LIVE_METADATA, metadata_path) |
346 | + elif _is_ibm_provisioning(): |
347 | +- return (Platforms.TEMPLATE_PROVISIONING_NODATA, None) |
348 | ++ return (Platforms.TEMPLATE_PROVISIONING_NODATA, None) |
349 | + return not_found |
350 | + |
351 | + |
352 | +--- a/cloudinit/tests/helpers.py |
353 | ++++ b/cloudinit/tests/helpers.py |
354 | +@@ -8,6 +8,7 @@ |
355 | + import shutil |
356 | + import sys |
357 | + import tempfile |
358 | ++import time |
359 | + import unittest |
360 | + |
361 | + import mock |
362 | +@@ -285,7 +286,8 @@ |
363 | + os.path: [('isfile', 1), ('exists', 1), |
364 | + ('islink', 1), ('isdir', 1), ('lexists', 1)], |
365 | + os: [('listdir', 1), ('mkdir', 1), |
366 | +- ('lstat', 1), ('symlink', 2)] |
367 | ++ ('lstat', 1), ('symlink', 2), |
368 | ++ ('stat', 1)] |
369 | + } |
370 | + |
371 | + if hasattr(os, 'scandir'): |
372 | +@@ -354,6 +356,15 @@ |
373 | + return ret |
374 | + |
375 | + |
376 | ++def populate_dir_with_ts(path, data): |
377 | ++ """data is {'file': ('contents', mtime)}. mtime relative to now.""" |
378 | ++ populate_dir(path, dict((k, v[0]) for k, v in data.items())) |
379 | ++ btime = time.time() |
380 | ++ for fpath, (_contents, mtime) in data.items(): |
381 | ++ ts = btime + mtime if mtime else btime |
382 | ++ os.utime(os.path.sep.join((path, fpath)), (ts, ts)) |
383 | ++ |
384 | ++ |
385 | + def dir2dict(startdir, prefix=None): |
386 | + flist = {} |
387 | + if prefix is None: |
388 | +--- a/tests/unittests/test_datasource/test_ibmcloud.py |
389 | ++++ b/tests/unittests/test_datasource/test_ibmcloud.py |
390 | +@@ -259,4 +259,54 @@ |
391 | + ret['metadata']) |
392 | + |
393 | + |
394 | ++class TestIsIBMProvisioning(test_helpers.FilesystemMockingTestCase): |
395 | ++ """Test the _is_ibm_provisioning method.""" |
396 | ++ inst_log = "/root/swinstall.log" |
397 | ++ prov_cfg = "/root/provisioningConfiguration.cfg" |
398 | ++ boot_ref = "/proc/1/environ" |
399 | ++ with_logs = True |
400 | ++ |
401 | ++ def _call_with_root(self, rootd): |
402 | ++ self.reRoot(rootd) |
403 | ++ return ibm._is_ibm_provisioning() |
404 | ++ |
405 | ++ def test_no_config(self): |
406 | ++ """No provisioning config means not provisioning.""" |
407 | ++ self.assertFalse(self._call_with_root(self.tmp_dir())) |
408 | ++ |
409 | ++ def test_config_only(self): |
410 | ++ """A provisioning config without a log means provisioning.""" |
411 | ++ rootd = self.tmp_dir() |
412 | ++ test_helpers.populate_dir(rootd, {self.prov_cfg: "key=value"}) |
413 | ++ self.assertTrue(self._call_with_root(rootd)) |
414 | ++ |
415 | ++ def test_config_with_old_log(self): |
416 | ++ """A config with a log from previous boot is not provisioning.""" |
417 | ++ rootd = self.tmp_dir() |
418 | ++ data = {self.prov_cfg: ("key=value\nkey2=val2\n", -10), |
419 | ++ self.inst_log: ("log data\n", -30), |
420 | ++ self.boot_ref: ("PWD=/", 0)} |
421 | ++ test_helpers.populate_dir_with_ts(rootd, data) |
422 | ++ self.assertFalse(self._call_with_root(rootd=rootd)) |
423 | ++ self.assertIn("from previous boot", self.logs.getvalue()) |
424 | ++ |
425 | ++ def test_config_with_new_log(self): |
426 | ++ """A config with a log from this boot is provisioning.""" |
427 | ++ rootd = self.tmp_dir() |
428 | ++ data = {self.prov_cfg: ("key=value\nkey2=val2\n", -10), |
429 | ++ self.inst_log: ("log data\n", 30), |
430 | ++ self.boot_ref: ("PWD=/", 0)} |
431 | ++ test_helpers.populate_dir_with_ts(rootd, data) |
432 | ++ self.assertTrue(self._call_with_root(rootd=rootd)) |
433 | ++ self.assertIn("from current boot", self.logs.getvalue()) |
434 | ++ |
435 | ++ def test_config_and_log_no_reference(self): |
436 | ++ """If the config and log existed, but no reference, assume not.""" |
437 | ++ rootd = self.tmp_dir() |
438 | ++ test_helpers.populate_dir( |
439 | ++ rootd, {self.prov_cfg: "key=value", self.inst_log: "log data\n"}) |
440 | ++ self.assertFalse(self._call_with_root(rootd=rootd)) |
441 | ++ self.assertIn("no reference file", self.logs.getvalue()) |
442 | ++ |
443 | ++ |
444 | + # vi: ts=4 expandtab |
445 | +--- a/tests/unittests/test_ds_identify.py |
446 | ++++ b/tests/unittests/test_ds_identify.py |
447 | +@@ -1,5 +1,6 @@ |
448 | + # This file is part of cloud-init. See LICENSE file for license information. |
449 | + |
450 | ++from collections import namedtuple |
451 | + import copy |
452 | + import os |
453 | + from uuid import uuid4 |
454 | +@@ -7,7 +8,7 @@ |
455 | + from cloudinit import safeyaml |
456 | + from cloudinit import util |
457 | + from cloudinit.tests.helpers import ( |
458 | +- CiTestCase, dir2dict, populate_dir) |
459 | ++ CiTestCase, dir2dict, populate_dir, populate_dir_with_ts) |
460 | + |
461 | + from cloudinit.sources import DataSourceIBMCloud as dsibm |
462 | + |
463 | +@@ -66,7 +67,6 @@ |
464 | + P_SEED_DIR = "var/lib/cloud/seed" |
465 | + P_DSID_CFG = "etc/cloud/ds-identify.cfg" |
466 | + |
467 | +-IBM_PROVISIONING_CHECK_PATH = "/root/provisioningConfiguration.cfg" |
468 | + IBM_CONFIG_UUID = "9796-932E" |
469 | + |
470 | + MOCK_VIRT_IS_KVM = {'name': 'detect_virt', 'RET': 'kvm', 'ret': 0} |
471 | +@@ -74,11 +74,17 @@ |
472 | + MOCK_VIRT_IS_XEN = {'name': 'detect_virt', 'RET': 'xen', 'ret': 0} |
473 | + MOCK_UNAME_IS_PPC64 = {'name': 'uname', 'out': UNAME_PPC64EL, 'ret': 0} |
474 | + |
475 | ++shell_true = 0 |
476 | ++shell_false = 1 |
477 | + |
478 | +-class TestDsIdentify(CiTestCase): |
479 | ++CallReturn = namedtuple('CallReturn', |
480 | ++ ['rc', 'stdout', 'stderr', 'cfg', 'files']) |
481 | ++ |
482 | ++ |
483 | ++class DsIdentifyBase(CiTestCase): |
484 | + dsid_path = os.path.realpath('tools/ds-identify') |
485 | + |
486 | +- def call(self, rootd=None, mocks=None, args=None, files=None, |
487 | ++ def call(self, rootd=None, mocks=None, func="main", args=None, files=None, |
488 | + policy_dmi=DI_DEFAULT_POLICY, |
489 | + policy_no_dmi=DI_DEFAULT_POLICY_NO_DMI, |
490 | + ec2_strict_id=DI_EC2_STRICT_ID_DEFAULT): |
491 | +@@ -135,7 +141,7 @@ |
492 | + mocklines.append(write_mock(d)) |
493 | + |
494 | + endlines = [ |
495 | +- 'main %s' % ' '.join(['"%s"' % s for s in args]) |
496 | ++ func + ' ' + ' '.join(['"%s"' % s for s in args]) |
497 | + ] |
498 | + |
499 | + with open(wrap, "w") as fp: |
500 | +@@ -159,7 +165,7 @@ |
501 | + cfg = {"_INVALID_YAML": contents, |
502 | + "_EXCEPTION": str(e)} |
503 | + |
504 | +- return rc, out, err, cfg, dir2dict(rootd) |
505 | ++ return CallReturn(rc, out, err, cfg, dir2dict(rootd)) |
506 | + |
507 | + def _call_via_dict(self, data, rootd=None, **kwargs): |
508 | + # return output of self.call with a dict input like VALID_CFG[item] |
509 | +@@ -191,6 +197,8 @@ |
510 | + ret.files) |
511 | + return ret |
512 | + |
513 | ++ |
514 | ++class TestDsIdentify(DsIdentifyBase): |
515 | + def test_wb_print_variables(self): |
516 | + """_print_info reports an array of discovered variables to stderr.""" |
517 | + data = VALID_CFG['Azure-dmi-detection'] |
518 | +@@ -278,7 +286,10 @@ |
519 | + Template provisioning with user-data has METADATA disk, |
520 | + datasource should return not found.""" |
521 | + data = copy.deepcopy(VALID_CFG['IBMCloud-metadata']) |
522 | +- data['files'] = {IBM_PROVISIONING_CHECK_PATH: 'xxx'} |
523 | ++ # change the 'is_ibm_provisioning' mock to return 1 (false) |
524 | ++ isprov_m = [m for m in data['mocks'] |
525 | ++ if m["name"] == "is_ibm_provisioning"][0] |
526 | ++ isprov_m['ret'] = shell_true |
527 | + return self._check_via_dict(data, RC_NOT_FOUND) |
528 | + |
529 | + def test_ibmcloud_template_userdata(self): |
530 | +@@ -293,7 +304,8 @@ |
531 | + |
532 | + no disks attached. Datasource should return not found.""" |
533 | + data = copy.deepcopy(VALID_CFG['IBMCloud-nodisks']) |
534 | +- data['files'] = {IBM_PROVISIONING_CHECK_PATH: 'xxx'} |
535 | ++ data['mocks'].append( |
536 | ++ {'name': 'is_ibm_provisioning', 'ret': shell_true}) |
537 | + return self._check_via_dict(data, RC_NOT_FOUND) |
538 | + |
539 | + def test_ibmcloud_template_no_userdata(self): |
540 | +@@ -505,6 +517,47 @@ |
541 | + self._test_ds_found('Hetzner') |
542 | + |
543 | + |
544 | ++class TestIsIBMProvisioning(DsIdentifyBase): |
545 | ++ """Test the is_ibm_provisioning method in ds-identify.""" |
546 | ++ |
547 | ++ inst_log = "/root/swinstall.log" |
548 | ++ prov_cfg = "/root/provisioningConfiguration.cfg" |
549 | ++ boot_ref = "/proc/1/environ" |
550 | ++ funcname = "is_ibm_provisioning" |
551 | ++ |
552 | ++ def test_no_config(self): |
553 | ++ """No provisioning config means not provisioning.""" |
554 | ++ ret = self.call(files={}, func=self.funcname) |
555 | ++ self.assertEqual(shell_false, ret.rc) |
556 | ++ |
557 | ++ def test_config_only(self): |
558 | ++ """A provisioning config without a log means provisioning.""" |
559 | ++ ret = self.call(files={self.prov_cfg: "key=value"}, func=self.funcname) |
560 | ++ self.assertEqual(shell_true, ret.rc) |
561 | ++ |
562 | ++ def test_config_with_old_log(self): |
563 | ++ """A config with a log from previous boot is not provisioning.""" |
564 | ++ rootd = self.tmp_dir() |
565 | ++ data = {self.prov_cfg: ("key=value\nkey2=val2\n", -10), |
566 | ++ self.inst_log: ("log data\n", -30), |
567 | ++ self.boot_ref: ("PWD=/", 0)} |
568 | ++ populate_dir_with_ts(rootd, data) |
569 | ++ ret = self.call(rootd=rootd, func=self.funcname) |
570 | ++ self.assertEqual(shell_false, ret.rc) |
571 | ++ self.assertIn("from previous boot", ret.stderr) |
572 | ++ |
573 | ++ def test_config_with_new_log(self): |
574 | ++ """A config with a log from this boot is provisioning.""" |
575 | ++ rootd = self.tmp_dir() |
576 | ++ data = {self.prov_cfg: ("key=value\nkey2=val2\n", -10), |
577 | ++ self.inst_log: ("log data\n", 30), |
578 | ++ self.boot_ref: ("PWD=/", 0)} |
579 | ++ populate_dir_with_ts(rootd, data) |
580 | ++ ret = self.call(rootd=rootd, func=self.funcname) |
581 | ++ self.assertEqual(shell_true, ret.rc) |
582 | ++ self.assertIn("from current boot", ret.stderr) |
583 | ++ |
584 | ++ |
585 | + def blkid_out(disks=None): |
586 | + """Convert a list of disk dictionaries into blkid content.""" |
587 | + if disks is None: |
588 | +@@ -704,6 +757,7 @@ |
589 | + 'ds': 'IBMCloud', |
590 | + 'mocks': [ |
591 | + MOCK_VIRT_IS_XEN, |
592 | ++ {'name': 'is_ibm_provisioning', 'ret': shell_false}, |
593 | + {'name': 'blkid', 'ret': 0, |
594 | + 'out': blkid_out( |
595 | + [{'DEVNAME': 'xvda1', 'TYPE': 'vfat', 'PARTUUID': uuid4()}, |
596 | +@@ -717,6 +771,7 @@ |
597 | + 'ds': 'IBMCloud', |
598 | + 'mocks': [ |
599 | + MOCK_VIRT_IS_XEN, |
600 | ++ {'name': 'is_ibm_provisioning', 'ret': shell_false}, |
601 | + {'name': 'blkid', 'ret': 0, |
602 | + 'out': blkid_out( |
603 | + [{'DEVNAME': 'xvda1', 'TYPE': 'ext3', 'PARTUUID': uuid4(), |
604 | +@@ -734,6 +789,7 @@ |
605 | + 'ds': 'IBMCloud', |
606 | + 'mocks': [ |
607 | + MOCK_VIRT_IS_XEN, |
608 | ++ {'name': 'is_ibm_provisioning', 'ret': shell_false}, |
609 | + {'name': 'blkid', 'ret': 0, |
610 | + 'out': blkid_out( |
611 | + [{'DEVNAME': 'xvda1', 'TYPE': 'vfat', 'PARTUUID': uuid4()}, |
612 | +--- a/tools/ds-identify |
613 | ++++ b/tools/ds-identify |
614 | +@@ -125,6 +125,7 @@ |
615 | + DI_EC2_STRICT_ID_DEFAULT="true" |
616 | + |
617 | + _IS_IBM_CLOUD="" |
618 | ++_IS_IBM_PROVISIONING="" |
619 | + |
620 | + error() { |
621 | + set -- "ERROR:" "$@"; |
622 | +@@ -1013,7 +1014,25 @@ |
623 | + } |
624 | + |
625 | + is_ibm_provisioning() { |
626 | +- [ -f "${PATH_ROOT}/root/provisioningConfiguration.cfg" ] |
627 | ++ local pcfg="${PATH_ROOT}/root/provisioningConfiguration.cfg" |
628 | ++ local logf="${PATH_ROOT}/root/swinstall.log" |
629 | ++ local is_prov=false msg="config '$pcfg' did not exist." |
630 | ++ if [ -f "$pcfg" ]; then |
631 | ++ msg="config '$pcfg' exists." |
632 | ++ is_prov=true |
633 | ++ if [ -f "$logf" ]; then |
634 | ++ if [ "$logf" -nt "$PATH_PROC_1_ENVIRON" ]; then |
635 | ++ msg="$msg log '$logf' from current boot." |
636 | ++ else |
637 | ++ is_prov=false |
638 | ++ msg="$msg log '$logf' from previous boot." |
639 | ++ fi |
640 | ++ else |
641 | ++ msg="$msg log '$logf' did not exist." |
642 | ++ fi |
643 | ++ fi |
644 | ++ debug 2 "ibm_provisioning=$is_prov: $msg" |
645 | ++ [ "$is_prov" = "true" ] |
646 | + } |
647 | + |
648 | + is_ibm_cloud() { |
649 | diff --git a/debian/patches/series b/debian/patches/series |
650 | new file mode 100644 |
651 | index 0000000..2f90183 |
652 | --- /dev/null |
653 | +++ b/debian/patches/series |
654 | @@ -0,0 +1,2 @@ |
655 | +cpick-11172924-IBMCloud-Disable-config-drive-and-nocloud-only-if |
656 | +cpick-6ef92c98-IBMCloud-recognize-provisioning-environment-during |
PASSED: Continuous integration, rev:5a8b71fdc0b 9313bfac2c8eedd e1ced0004d5d2a /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 1085/
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/ 1085/rebuild
https:/