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