Merge ~chad.smith/cloud-init:datasource-ibmcloud into cloud-init:master
- Git
- lp:~chad.smith/cloud-init
- datasource-ibmcloud
- Merge into master
Status: | Merged |
---|---|
Approved by: | Chad Smith |
Approved revision: | 332275089657698b78826e8adbdc5ed62069ced8 |
Merge reported by: | Chad Smith |
Merged at revision: | e0f644b7c8c76bd63d242558685722cc70d9c51d |
Proposed branch: | ~chad.smith/cloud-init:datasource-ibmcloud |
Merge into: | cloud-init:master |
Diff against target: |
1049 lines (+857/-12) 7 files modified
cloudinit/sources/DataSourceConfigDrive.py (+10/-0) cloudinit/sources/DataSourceIBMCloud.py (+325/-0) cloudinit/tests/test_util.py (+72/-0) cloudinit/util.py (+31/-0) tests/unittests/test_datasource/test_ibmcloud.py (+262/-0) tests/unittests/test_ds_identify.py (+101/-3) tools/ds-identify (+56/-9) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Chad Smith | Approve | ||
Review via email: mp+342009@code.launchpad.net |
Commit message
IBMCloud: Initial IBM Cloud datasource.
This adds a specific IBM Cloud datasource.
IBM Cloud is identified by:
a.) running on xen
b.) one of a LABEL=METADATA disk or a LABEL=config-2 disk with
UUID=9796-932E
The datasource contains its own config-drive reader that reads
only the currently supported portion of config-drive needed for
ibm cloud.
During the provisioning boot, cloud-init is disabled.
See the docstring in DataSourceIBMCl
Description of the change
see commit message. Shepherd smoser's branch into tip
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:4641cef07c6
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
FAILED: Ubuntu LTS: Integration
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:cca002cca78
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:/
Chad Smith (chad.smith) wrote : | # |
An upstream commit landed for this bug.
To view that commit see the following URL:
https:/
Preview Diff
1 | diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py |
2 | index b8db626..c7b5fe5 100644 |
3 | --- a/cloudinit/sources/DataSourceConfigDrive.py |
4 | +++ b/cloudinit/sources/DataSourceConfigDrive.py |
5 | @@ -14,6 +14,7 @@ from cloudinit import util |
6 | |
7 | from cloudinit.net import eni |
8 | |
9 | +from cloudinit.sources.DataSourceIBMCloud import get_ibm_platform |
10 | from cloudinit.sources.helpers import openstack |
11 | |
12 | LOG = logging.getLogger(__name__) |
13 | @@ -255,6 +256,15 @@ def find_candidate_devs(probe_optical=True): |
14 | # an unpartitioned block device (ex sda, not sda1) |
15 | devices = [d for d in candidates |
16 | if d in by_label or not util.is_partition(d)] |
17 | + |
18 | + if devices: |
19 | + # IBMCloud uses config-2 label, but limited to a single UUID. |
20 | + ibm_platform, ibm_path = get_ibm_platform() |
21 | + if ibm_path in devices: |
22 | + devices.remove(ibm_path) |
23 | + LOG.debug("IBMCloud device '%s' (%s) removed from candidate list", |
24 | + ibm_path, ibm_platform) |
25 | + |
26 | return devices |
27 | |
28 | |
29 | diff --git a/cloudinit/sources/DataSourceIBMCloud.py b/cloudinit/sources/DataSourceIBMCloud.py |
30 | new file mode 100644 |
31 | index 0000000..02b3d56 |
32 | --- /dev/null |
33 | +++ b/cloudinit/sources/DataSourceIBMCloud.py |
34 | @@ -0,0 +1,325 @@ |
35 | +# This file is part of cloud-init. See LICENSE file for license information. |
36 | +"""Datasource for IBMCloud. |
37 | + |
38 | +IBMCloud is also know as SoftLayer or BlueMix. |
39 | +IBMCloud hypervisor is xen (2018-03-10). |
40 | + |
41 | +There are 2 different api exposed launch methods. |
42 | + * template: This is the legacy method of launching instances. |
43 | + When booting from an image template, the system boots first into |
44 | + a "provisioning" mode. There, host <-> guest mechanisms are utilized |
45 | + to execute code in the guest and provision it. |
46 | + |
47 | + Cloud-init will disable itself when it detects that it is in the |
48 | + provisioning mode. It detects this by the presence of |
49 | + a file '/root/provisioningConfiguration.cfg'. |
50 | + |
51 | + When provided with user-data, the "first boot" will contain a |
52 | + ConfigDrive-like disk labeled with 'METADATA'. If there is no user-data |
53 | + provided, then there is no data-source. |
54 | + |
55 | + Cloud-init never does any network configuration in this mode. |
56 | + |
57 | + * os_code: Essentially "launch by OS Code" (Operating System Code). |
58 | + This is a more modern approach. There is no specific "provisioning" boot. |
59 | + Instead, cloud-init does all the customization. With or without |
60 | + user-data provided, an OpenStack ConfigDrive like disk is attached. |
61 | + |
62 | + Only disks with label 'config-2' and UUID '9796-932E' are considered. |
63 | + This is to avoid this datasource claiming ConfigDrive. This does |
64 | + mean that 1 in 8^16 (~4 billion) Xen ConfigDrive systems will be |
65 | + incorrectly identified as IBMCloud. |
66 | + |
67 | +TODO: |
68 | + * is uuid (/sys/hypervisor/uuid) stable for life of an instance? |
69 | + it seems it is not the same as data's uuid in the os_code case |
70 | + but is in the template case. |
71 | + |
72 | +""" |
73 | +import base64 |
74 | +import json |
75 | +import os |
76 | + |
77 | +from cloudinit import log as logging |
78 | +from cloudinit import sources |
79 | +from cloudinit.sources.helpers import openstack |
80 | +from cloudinit import util |
81 | + |
82 | +LOG = logging.getLogger(__name__) |
83 | + |
84 | +IBM_CONFIG_UUID = "9796-932E" |
85 | + |
86 | + |
87 | +class Platforms(object): |
88 | + TEMPLATE_LIVE_METADATA = "Template/Live/Metadata" |
89 | + TEMPLATE_LIVE_NODATA = "UNABLE TO BE IDENTIFIED." |
90 | + TEMPLATE_PROVISIONING_METADATA = "Template/Provisioning/Metadata" |
91 | + TEMPLATE_PROVISIONING_NODATA = "Template/Provisioning/No-Metadata" |
92 | + OS_CODE = "OS-Code/Live" |
93 | + |
94 | + |
95 | +PROVISIONING = ( |
96 | + Platforms.TEMPLATE_PROVISIONING_METADATA, |
97 | + Platforms.TEMPLATE_PROVISIONING_NODATA) |
98 | + |
99 | + |
100 | +class DataSourceIBMCloud(sources.DataSource): |
101 | + |
102 | + dsname = 'IBMCloud' |
103 | + system_uuid = None |
104 | + |
105 | + def __init__(self, sys_cfg, distro, paths): |
106 | + super(DataSourceIBMCloud, self).__init__(sys_cfg, distro, paths) |
107 | + self.source = None |
108 | + self._network_config = None |
109 | + self.network_json = None |
110 | + self.platform = None |
111 | + |
112 | + def __str__(self): |
113 | + root = super(DataSourceIBMCloud, self).__str__() |
114 | + mstr = "%s [%s %s]" % (root, self.platform, self.source) |
115 | + return mstr |
116 | + |
117 | + def _get_data(self): |
118 | + results = read_md() |
119 | + if results is None: |
120 | + return False |
121 | + |
122 | + self.source = results['source'] |
123 | + self.platform = results['platform'] |
124 | + self.metadata = results['metadata'] |
125 | + self.userdata_raw = results.get('userdata') |
126 | + self.network_json = results.get('networkdata') |
127 | + vd = results.get('vendordata') |
128 | + self.vendordata_pure = vd |
129 | + self.system_uuid = results['system-uuid'] |
130 | + try: |
131 | + self.vendordata_raw = sources.convert_vendordata(vd) |
132 | + except ValueError as e: |
133 | + LOG.warning("Invalid content in vendor-data: %s", e) |
134 | + self.vendordata_raw = None |
135 | + |
136 | + return True |
137 | + |
138 | + def check_instance_id(self, sys_cfg): |
139 | + """quickly (local check only) if self.instance_id is still valid |
140 | + |
141 | + in Template mode, the system uuid (/sys/hypervisor/uuid) is the |
142 | + same as found in the METADATA disk. But that is not true in OS_CODE |
143 | + mode. So we read the system_uuid and keep that for later compare.""" |
144 | + if self.system_uuid is None: |
145 | + return False |
146 | + return self.system_uuid == _read_system_uuid() |
147 | + |
148 | + @property |
149 | + def network_config(self): |
150 | + if self.platform != Platforms.OS_CODE: |
151 | + # If deployed from template, an agent in the provisioning |
152 | + # environment handles networking configuration. Not cloud-init. |
153 | + return {'config': 'disabled', 'version': 1} |
154 | + if self._network_config is None: |
155 | + if self.network_json is not None: |
156 | + LOG.debug("network config provided via network_json") |
157 | + self._network_config = openstack.convert_net_json( |
158 | + self.network_json, known_macs=None) |
159 | + else: |
160 | + LOG.debug("no network configuration available.") |
161 | + return self._network_config |
162 | + |
163 | + |
164 | +def _read_system_uuid(): |
165 | + uuid_path = "/sys/hypervisor/uuid" |
166 | + if not os.path.isfile(uuid_path): |
167 | + return None |
168 | + return util.load_file(uuid_path).strip().lower() |
169 | + |
170 | + |
171 | +def _is_xen(): |
172 | + return os.path.exists("/proc/xen") |
173 | + |
174 | + |
175 | +def _is_ibm_provisioning(): |
176 | + return os.path.exists("/root/provisioningConfiguration.cfg") |
177 | + |
178 | + |
179 | +def get_ibm_platform(): |
180 | + """Return a tuple (Platform, path) |
181 | + |
182 | + If this is Not IBM cloud, then the return value is (None, None). |
183 | + An instance in provisioning mode is considered running on IBM cloud.""" |
184 | + label_mdata = "METADATA" |
185 | + label_cfg2 = "CONFIG-2" |
186 | + not_found = (None, None) |
187 | + |
188 | + if not _is_xen(): |
189 | + return not_found |
190 | + |
191 | + # fslabels contains only the first entry with a given label. |
192 | + fslabels = {} |
193 | + try: |
194 | + devs = util.blkid() |
195 | + except util.ProcessExecutionError as e: |
196 | + LOG.warning("Failed to run blkid: %s", e) |
197 | + return (None, None) |
198 | + |
199 | + for dev in sorted(devs.keys()): |
200 | + data = devs[dev] |
201 | + label = data.get("LABEL", "").upper() |
202 | + uuid = data.get("UUID", "").upper() |
203 | + if label not in (label_mdata, label_cfg2): |
204 | + continue |
205 | + if label in fslabels: |
206 | + LOG.warning("Duplicate fslabel '%s'. existing=%s current=%s", |
207 | + label, fslabels[label], data) |
208 | + continue |
209 | + if label == label_cfg2 and uuid != IBM_CONFIG_UUID: |
210 | + LOG.debug("Skipping %s with LABEL=%s due to uuid != %s: %s", |
211 | + dev, label, uuid, data) |
212 | + continue |
213 | + fslabels[label] = data |
214 | + |
215 | + metadata_path = fslabels.get(label_mdata, {}).get('DEVNAME') |
216 | + cfg2_path = fslabels.get(label_cfg2, {}).get('DEVNAME') |
217 | + |
218 | + if cfg2_path: |
219 | + return (Platforms.OS_CODE, cfg2_path) |
220 | + elif metadata_path: |
221 | + if _is_ibm_provisioning(): |
222 | + return (Platforms.TEMPLATE_PROVISIONING_METADATA, metadata_path) |
223 | + else: |
224 | + return (Platforms.TEMPLATE_LIVE_METADATA, metadata_path) |
225 | + elif _is_ibm_provisioning(): |
226 | + return (Platforms.TEMPLATE_PROVISIONING_NODATA, None) |
227 | + return not_found |
228 | + |
229 | + |
230 | +def read_md(): |
231 | + """Read data from IBM Cloud. |
232 | + |
233 | + @return: None if not running on IBM Cloud. |
234 | + dictionary with guaranteed fields: metadata, version |
235 | + and optional fields: userdata, vendordata, networkdata. |
236 | + Also includes the system uuid from /sys/hypervisor/uuid.""" |
237 | + platform, path = get_ibm_platform() |
238 | + if platform is None: |
239 | + LOG.debug("This is not an IBMCloud platform.") |
240 | + return None |
241 | + elif platform in PROVISIONING: |
242 | + LOG.debug("Cloud-init is disabled during provisioning: %s.", |
243 | + platform) |
244 | + return None |
245 | + |
246 | + ret = {'platform': platform, 'source': path, |
247 | + 'system-uuid': _read_system_uuid()} |
248 | + |
249 | + try: |
250 | + if os.path.isdir(path): |
251 | + results = metadata_from_dir(path) |
252 | + else: |
253 | + results = util.mount_cb(path, metadata_from_dir) |
254 | + except BrokenMetadata as e: |
255 | + raise RuntimeError( |
256 | + "Failed reading IBM config disk (platform=%s path=%s): %s" % |
257 | + (platform, path, e)) |
258 | + |
259 | + ret.update(results) |
260 | + return ret |
261 | + |
262 | + |
263 | +class BrokenMetadata(IOError): |
264 | + pass |
265 | + |
266 | + |
267 | +def metadata_from_dir(source_dir): |
268 | + """Walk source_dir extracting standardized metadata. |
269 | + |
270 | + Certain metadata keys are renamed to present a standardized set of metadata |
271 | + keys. |
272 | + |
273 | + This function has a lot in common with ConfigDriveReader.read_v2 but |
274 | + there are a number of inconsistencies, such key renames and as only |
275 | + presenting a 'latest' version which make it an unlikely candidate to share |
276 | + code. |
277 | + |
278 | + @return: Dict containing translated metadata, userdata, vendordata, |
279 | + networkdata as present. |
280 | + """ |
281 | + |
282 | + def opath(fname): |
283 | + return os.path.join("openstack", "latest", fname) |
284 | + |
285 | + def load_json_bytes(blob): |
286 | + return json.loads(blob.decode('utf-8')) |
287 | + |
288 | + files = [ |
289 | + # tuples of (results_name, path, translator) |
290 | + ('metadata_raw', opath('meta_data.json'), load_json_bytes), |
291 | + ('userdata', opath('user_data'), None), |
292 | + ('vendordata', opath('vendor_data.json'), load_json_bytes), |
293 | + ('networkdata', opath('network_data.json'), load_json_bytes), |
294 | + ] |
295 | + |
296 | + results = {} |
297 | + for (name, path, transl) in files: |
298 | + fpath = os.path.join(source_dir, path) |
299 | + raw = None |
300 | + try: |
301 | + raw = util.load_file(fpath, decode=False) |
302 | + except IOError as e: |
303 | + LOG.debug("Failed reading path '%s': %s", fpath, e) |
304 | + |
305 | + if raw is None or transl is None: |
306 | + data = raw |
307 | + else: |
308 | + try: |
309 | + data = transl(raw) |
310 | + except Exception as e: |
311 | + raise BrokenMetadata("Failed decoding %s: %s" % (path, e)) |
312 | + |
313 | + results[name] = data |
314 | + |
315 | + if results.get('metadata_raw') is None: |
316 | + raise BrokenMetadata( |
317 | + "%s missing required file 'meta_data.json'" % source_dir) |
318 | + |
319 | + results['metadata'] = {} |
320 | + |
321 | + md_raw = results['metadata_raw'] |
322 | + md = results['metadata'] |
323 | + if 'random_seed' in md_raw: |
324 | + try: |
325 | + md['random_seed'] = base64.b64decode(md_raw['random_seed']) |
326 | + except (ValueError, TypeError) as e: |
327 | + raise BrokenMetadata( |
328 | + "Badly formatted metadata random_seed entry: %s" % e) |
329 | + |
330 | + renames = ( |
331 | + ('public_keys', 'public-keys'), ('hostname', 'local-hostname'), |
332 | + ('uuid', 'instance-id')) |
333 | + for mdname, newname in renames: |
334 | + if mdname in md_raw: |
335 | + md[newname] = md_raw[mdname] |
336 | + |
337 | + return results |
338 | + |
339 | + |
340 | +# Used to match classes to dependencies |
341 | +datasources = [ |
342 | + (DataSourceIBMCloud, (sources.DEP_FILESYSTEM,)), |
343 | +] |
344 | + |
345 | + |
346 | +# Return a list of data sources that match this set of dependencies |
347 | +def get_datasource_list(depends): |
348 | + return sources.list_from_depends(depends, datasources) |
349 | + |
350 | + |
351 | +if __name__ == "__main__": |
352 | + import argparse |
353 | + |
354 | + parser = argparse.ArgumentParser(description='Query IBM Cloud Metadata') |
355 | + args = parser.parse_args() |
356 | + data = read_md() |
357 | + print(util.json_dumps(data)) |
358 | + |
359 | +# vi: ts=4 expandtab |
360 | diff --git a/cloudinit/tests/test_util.py b/cloudinit/tests/test_util.py |
361 | index d30643d..3f37dbb 100644 |
362 | --- a/cloudinit/tests/test_util.py |
363 | +++ b/cloudinit/tests/test_util.py |
364 | @@ -3,6 +3,7 @@ |
365 | """Tests for cloudinit.util""" |
366 | |
367 | import logging |
368 | +from textwrap import dedent |
369 | |
370 | import cloudinit.util as util |
371 | |
372 | @@ -140,4 +141,75 @@ class TestGetHostnameFqdn(CiTestCase): |
373 | [{'fqdn': True, 'metadata_only': True}, |
374 | {'metadata_only': True}], mycloud.calls) |
375 | |
376 | + |
377 | +class TestBlkid(CiTestCase): |
378 | + ids = { |
379 | + "id01": "1111-1111", |
380 | + "id02": "22222222-2222", |
381 | + "id03": "33333333-3333", |
382 | + "id04": "44444444-4444", |
383 | + "id05": "55555555-5555-5555-5555-555555555555", |
384 | + "id06": "66666666-6666-6666-6666-666666666666", |
385 | + "id07": "52894610484658920398", |
386 | + "id08": "86753098675309867530", |
387 | + "id09": "99999999-9999-9999-9999-999999999999", |
388 | + } |
389 | + |
390 | + blkid_out = dedent("""\ |
391 | + /dev/loop0: TYPE="squashfs" |
392 | + /dev/loop1: TYPE="squashfs" |
393 | + /dev/loop2: TYPE="squashfs" |
394 | + /dev/loop3: TYPE="squashfs" |
395 | + /dev/sda1: UUID="{id01}" TYPE="vfat" PARTUUID="{id02}" |
396 | + /dev/sda2: UUID="{id03}" TYPE="ext4" PARTUUID="{id04}" |
397 | + /dev/sda3: UUID="{id05}" TYPE="ext4" PARTUUID="{id06}" |
398 | + /dev/sda4: LABEL="default" UUID="{id07}" UUID_SUB="{id08}" """ |
399 | + """TYPE="zfs_member" PARTUUID="{id09}" |
400 | + /dev/loop4: TYPE="squashfs" |
401 | + """) |
402 | + |
403 | + maxDiff = None |
404 | + |
405 | + def _get_expected(self): |
406 | + return ({ |
407 | + "/dev/loop0": {"DEVNAME": "/dev/loop0", "TYPE": "squashfs"}, |
408 | + "/dev/loop1": {"DEVNAME": "/dev/loop1", "TYPE": "squashfs"}, |
409 | + "/dev/loop2": {"DEVNAME": "/dev/loop2", "TYPE": "squashfs"}, |
410 | + "/dev/loop3": {"DEVNAME": "/dev/loop3", "TYPE": "squashfs"}, |
411 | + "/dev/loop4": {"DEVNAME": "/dev/loop4", "TYPE": "squashfs"}, |
412 | + "/dev/sda1": {"DEVNAME": "/dev/sda1", "TYPE": "vfat", |
413 | + "UUID": self.ids["id01"], |
414 | + "PARTUUID": self.ids["id02"]}, |
415 | + "/dev/sda2": {"DEVNAME": "/dev/sda2", "TYPE": "ext4", |
416 | + "UUID": self.ids["id03"], |
417 | + "PARTUUID": self.ids["id04"]}, |
418 | + "/dev/sda3": {"DEVNAME": "/dev/sda3", "TYPE": "ext4", |
419 | + "UUID": self.ids["id05"], |
420 | + "PARTUUID": self.ids["id06"]}, |
421 | + "/dev/sda4": {"DEVNAME": "/dev/sda4", "TYPE": "zfs_member", |
422 | + "LABEL": "default", |
423 | + "UUID": self.ids["id07"], |
424 | + "UUID_SUB": self.ids["id08"], |
425 | + "PARTUUID": self.ids["id09"]}, |
426 | + }) |
427 | + |
428 | + @mock.patch("cloudinit.util.subp") |
429 | + def test_functional_blkid(self, m_subp): |
430 | + m_subp.return_value = ( |
431 | + self.blkid_out.format(**self.ids), "") |
432 | + self.assertEqual(self._get_expected(), util.blkid()) |
433 | + m_subp.assert_called_with(["blkid", "-o", "full"], capture=True, |
434 | + decode="replace") |
435 | + |
436 | + @mock.patch("cloudinit.util.subp") |
437 | + def test_blkid_no_cache_uses_no_cache(self, m_subp): |
438 | + """blkid should turn off cache if disable_cache is true.""" |
439 | + m_subp.return_value = ( |
440 | + self.blkid_out.format(**self.ids), "") |
441 | + self.assertEqual(self._get_expected(), |
442 | + util.blkid(disable_cache=True)) |
443 | + m_subp.assert_called_with(["blkid", "-o", "full", "-c", "/dev/null"], |
444 | + capture=True, decode="replace") |
445 | + |
446 | + |
447 | # vi: ts=4 expandtab |
448 | diff --git a/cloudinit/util.py b/cloudinit/util.py |
449 | index cae8b19..fb4ee5f 100644 |
450 | --- a/cloudinit/util.py |
451 | +++ b/cloudinit/util.py |
452 | @@ -1237,6 +1237,37 @@ def find_devs_with(criteria=None, oformat='device', |
453 | return entries |
454 | |
455 | |
456 | +def blkid(devs=None, disable_cache=False): |
457 | + """Get all device tags details from blkid. |
458 | + |
459 | + @param devs: Optional list of device paths you wish to query. |
460 | + @param disable_cache: Bool, set True to start with clean cache. |
461 | + |
462 | + @return: Dict of key value pairs of info for the device. |
463 | + """ |
464 | + if devs is None: |
465 | + devs = [] |
466 | + else: |
467 | + devs = list(devs) |
468 | + |
469 | + cmd = ['blkid', '-o', 'full'] |
470 | + if disable_cache: |
471 | + cmd.extend(['-c', '/dev/null']) |
472 | + cmd.extend(devs) |
473 | + |
474 | + # we have to decode with 'replace' as shelx.split (called by |
475 | + # load_shell_content) can't take bytes. So this is potentially |
476 | + # lossy of non-utf-8 chars in blkid output. |
477 | + out, _ = subp(cmd, capture=True, decode="replace") |
478 | + ret = {} |
479 | + for line in out.splitlines(): |
480 | + dev, _, data = line.partition(":") |
481 | + ret[dev] = load_shell_content(data) |
482 | + ret[dev]["DEVNAME"] = dev |
483 | + |
484 | + return ret |
485 | + |
486 | + |
487 | def peek_file(fname, max_bytes): |
488 | LOG.debug("Peeking at %s (max_bytes=%s)", fname, max_bytes) |
489 | with open(fname, 'rb') as ifh: |
490 | diff --git a/tests/unittests/test_datasource/test_ibmcloud.py b/tests/unittests/test_datasource/test_ibmcloud.py |
491 | new file mode 100644 |
492 | index 0000000..621cfe4 |
493 | --- /dev/null |
494 | +++ b/tests/unittests/test_datasource/test_ibmcloud.py |
495 | @@ -0,0 +1,262 @@ |
496 | +# This file is part of cloud-init. See LICENSE file for license information. |
497 | + |
498 | +from cloudinit.sources import DataSourceIBMCloud as ibm |
499 | +from cloudinit.tests import helpers as test_helpers |
500 | + |
501 | +import base64 |
502 | +import copy |
503 | +import json |
504 | +import mock |
505 | +from textwrap import dedent |
506 | + |
507 | +D_PATH = "cloudinit.sources.DataSourceIBMCloud." |
508 | + |
509 | + |
510 | +class TestIBMCloud(test_helpers.CiTestCase): |
511 | + """Test the datasource.""" |
512 | + def setUp(self): |
513 | + super(TestIBMCloud, self).setUp() |
514 | + pass |
515 | + |
516 | + |
517 | +@mock.patch(D_PATH + "_is_xen", return_value=True) |
518 | +@mock.patch(D_PATH + "_is_ibm_provisioning") |
519 | +@mock.patch(D_PATH + "util.blkid") |
520 | +class TestGetIBMPlatform(test_helpers.CiTestCase): |
521 | + """Test the get_ibm_platform helper.""" |
522 | + |
523 | + blkid_base = { |
524 | + "/dev/xvda1": { |
525 | + "DEVNAME": "/dev/xvda1", "LABEL": "cloudimg-bootfs", |
526 | + "TYPE": "ext3"}, |
527 | + "/dev/xvda2": { |
528 | + "DEVNAME": "/dev/xvda2", "LABEL": "cloudimg-rootfs", |
529 | + "TYPE": "ext4"}, |
530 | + } |
531 | + |
532 | + blkid_metadata_disk = { |
533 | + "/dev/xvdh1": { |
534 | + "DEVNAME": "/dev/xvdh1", "LABEL": "METADATA", "TYPE": "vfat", |
535 | + "SEC_TYPE": "msdos", "UUID": "681B-8C5D", |
536 | + "PARTUUID": "3d631e09-01"}, |
537 | + } |
538 | + |
539 | + blkid_oscode_disk = { |
540 | + "/dev/xvdh": { |
541 | + "DEVNAME": "/dev/xvdh", "LABEL": "config-2", "TYPE": "vfat", |
542 | + "SEC_TYPE": "msdos", "UUID": ibm.IBM_CONFIG_UUID} |
543 | + } |
544 | + |
545 | + def setUp(self): |
546 | + self.blkid_metadata = copy.deepcopy(self.blkid_base) |
547 | + self.blkid_metadata.update(copy.deepcopy(self.blkid_metadata_disk)) |
548 | + |
549 | + self.blkid_oscode = copy.deepcopy(self.blkid_base) |
550 | + self.blkid_oscode.update(copy.deepcopy(self.blkid_oscode_disk)) |
551 | + |
552 | + def test_id_template_live_metadata(self, m_blkid, m_is_prov, _m_xen): |
553 | + """identify TEMPLATE_LIVE_METADATA.""" |
554 | + m_blkid.return_value = self.blkid_metadata |
555 | + m_is_prov.return_value = False |
556 | + self.assertEqual( |
557 | + (ibm.Platforms.TEMPLATE_LIVE_METADATA, "/dev/xvdh1"), |
558 | + ibm.get_ibm_platform()) |
559 | + |
560 | + def test_id_template_prov_metadata(self, m_blkid, m_is_prov, _m_xen): |
561 | + """identify TEMPLATE_PROVISIONING_METADATA.""" |
562 | + m_blkid.return_value = self.blkid_metadata |
563 | + m_is_prov.return_value = True |
564 | + self.assertEqual( |
565 | + (ibm.Platforms.TEMPLATE_PROVISIONING_METADATA, "/dev/xvdh1"), |
566 | + ibm.get_ibm_platform()) |
567 | + |
568 | + def test_id_template_prov_nodata(self, m_blkid, m_is_prov, _m_xen): |
569 | + """identify TEMPLATE_PROVISIONING_NODATA.""" |
570 | + m_blkid.return_value = self.blkid_base |
571 | + m_is_prov.return_value = True |
572 | + self.assertEqual( |
573 | + (ibm.Platforms.TEMPLATE_PROVISIONING_NODATA, None), |
574 | + ibm.get_ibm_platform()) |
575 | + |
576 | + def test_id_os_code(self, m_blkid, m_is_prov, _m_xen): |
577 | + """Identify OS_CODE.""" |
578 | + m_blkid.return_value = self.blkid_oscode |
579 | + m_is_prov.return_value = False |
580 | + self.assertEqual((ibm.Platforms.OS_CODE, "/dev/xvdh"), |
581 | + ibm.get_ibm_platform()) |
582 | + |
583 | + def test_id_os_code_must_match_uuid(self, m_blkid, m_is_prov, _m_xen): |
584 | + """Test against false positive on openstack with non-ibm UUID.""" |
585 | + blkid = self.blkid_oscode |
586 | + blkid["/dev/xvdh"]["UUID"] = "9999-9999" |
587 | + m_blkid.return_value = blkid |
588 | + m_is_prov.return_value = False |
589 | + self.assertEqual((None, None), ibm.get_ibm_platform()) |
590 | + |
591 | + |
592 | +@mock.patch(D_PATH + "_read_system_uuid", return_value=None) |
593 | +@mock.patch(D_PATH + "get_ibm_platform") |
594 | +class TestReadMD(test_helpers.CiTestCase): |
595 | + """Test the read_datasource helper.""" |
596 | + |
597 | + template_md = { |
598 | + "files": [], |
599 | + "network_config": {"content_path": "/content/interfaces"}, |
600 | + "hostname": "ci-fond-ram", |
601 | + "name": "ci-fond-ram", |
602 | + "domain": "testing.ci.cloud-init.org", |
603 | + "meta": {"dsmode": "net"}, |
604 | + "uuid": "8e636730-9f5d-c4a5-327c-d7123c46e82f", |
605 | + "public_keys": {"1091307": "ssh-rsa AAAAB3NzaC1...Hw== ci-pubkey"}, |
606 | + } |
607 | + |
608 | + oscode_md = { |
609 | + "hostname": "ci-grand-gannet.testing.ci.cloud-init.org", |
610 | + "name": "ci-grand-gannet", |
611 | + "uuid": "2f266908-8e6c-4818-9b5c-42e9cc66a785", |
612 | + "random_seed": "bm90LXJhbmRvbQo=", |
613 | + "crypt_key": "ssh-rsa AAAAB3NzaC1yc2..n6z/", |
614 | + "configuration_token": "eyJhbGciOi..M3ZA", |
615 | + "public_keys": {"1091307": "ssh-rsa AAAAB3N..Hw== ci-pubkey"}, |
616 | + } |
617 | + |
618 | + content_interfaces = dedent("""\ |
619 | + auto lo |
620 | + iface lo inet loopback |
621 | + |
622 | + auto eth0 |
623 | + allow-hotplug eth0 |
624 | + iface eth0 inet static |
625 | + address 10.82.43.5 |
626 | + netmask 255.255.255.192 |
627 | + """) |
628 | + |
629 | + userdata = b"#!/bin/sh\necho hi mom\n" |
630 | + # meta.js file gets json encoded userdata as a list. |
631 | + meta_js = '["#!/bin/sh\necho hi mom\n"]' |
632 | + vendor_data = { |
633 | + "cloud-init": "#!/bin/bash\necho 'root:$6$5ab01p1m1' | chpasswd -e"} |
634 | + |
635 | + network_data = { |
636 | + "links": [ |
637 | + {"id": "interface_29402281", "name": "eth0", "mtu": None, |
638 | + "type": "phy", "ethernet_mac_address": "06:00:f1:bd:da:25"}, |
639 | + {"id": "interface_29402279", "name": "eth1", "mtu": None, |
640 | + "type": "phy", "ethernet_mac_address": "06:98:5e:d0:7f:86"} |
641 | + ], |
642 | + "networks": [ |
643 | + {"id": "network_109887563", "link": "interface_29402281", |
644 | + "type": "ipv4", "ip_address": "10.82.43.2", |
645 | + "netmask": "255.255.255.192", |
646 | + "routes": [ |
647 | + {"network": "10.0.0.0", "netmask": "255.0.0.0", |
648 | + "gateway": "10.82.43.1"}, |
649 | + {"network": "161.26.0.0", "netmask": "255.255.0.0", |
650 | + "gateway": "10.82.43.1"}]}, |
651 | + {"id": "network_109887551", "link": "interface_29402279", |
652 | + "type": "ipv4", "ip_address": "108.168.194.252", |
653 | + "netmask": "255.255.255.248", |
654 | + "routes": [ |
655 | + {"network": "0.0.0.0", "netmask": "0.0.0.0", |
656 | + "gateway": "108.168.194.249"}]} |
657 | + ], |
658 | + "services": [ |
659 | + {"type": "dns", "address": "10.0.80.11"}, |
660 | + {"type": "dns", "address": "10.0.80.12"} |
661 | + ], |
662 | + } |
663 | + |
664 | + sysuuid = '7f79ebf5-d791-43c3-a723-854e8389d59f' |
665 | + |
666 | + def _get_expected_metadata(self, os_md): |
667 | + """return expected 'metadata' for data loaded from meta_data.json.""" |
668 | + os_md = copy.deepcopy(os_md) |
669 | + renames = ( |
670 | + ('hostname', 'local-hostname'), |
671 | + ('uuid', 'instance-id'), |
672 | + ('public_keys', 'public-keys')) |
673 | + ret = {} |
674 | + for osname, mdname in renames: |
675 | + if osname in os_md: |
676 | + ret[mdname] = os_md[osname] |
677 | + if 'random_seed' in os_md: |
678 | + ret['random_seed'] = base64.b64decode(os_md['random_seed']) |
679 | + |
680 | + return ret |
681 | + |
682 | + def test_provisioning_md(self, m_platform, m_sysuuid): |
683 | + """Provisioning env with a metadata disk should return None.""" |
684 | + m_platform.return_value = ( |
685 | + ibm.Platforms.TEMPLATE_PROVISIONING_METADATA, "/dev/xvdh") |
686 | + self.assertIsNone(ibm.read_md()) |
687 | + |
688 | + def test_provisioning_no_metadata(self, m_platform, m_sysuuid): |
689 | + """Provisioning env with no metadata disk should return None.""" |
690 | + m_platform.return_value = ( |
691 | + ibm.Platforms.TEMPLATE_PROVISIONING_NODATA, None) |
692 | + self.assertIsNone(ibm.read_md()) |
693 | + |
694 | + def test_provisioning_not_ibm(self, m_platform, m_sysuuid): |
695 | + """Provisioning env but not identified as IBM should return None.""" |
696 | + m_platform.return_value = (None, None) |
697 | + self.assertIsNone(ibm.read_md()) |
698 | + |
699 | + def test_template_live(self, m_platform, m_sysuuid): |
700 | + """Template live environment should be identified.""" |
701 | + tmpdir = self.tmp_dir() |
702 | + m_platform.return_value = ( |
703 | + ibm.Platforms.TEMPLATE_LIVE_METADATA, tmpdir) |
704 | + m_sysuuid.return_value = self.sysuuid |
705 | + |
706 | + test_helpers.populate_dir(tmpdir, { |
707 | + 'openstack/latest/meta_data.json': json.dumps(self.template_md), |
708 | + 'openstack/latest/user_data': self.userdata, |
709 | + 'openstack/content/interfaces': self.content_interfaces, |
710 | + 'meta.js': self.meta_js}) |
711 | + |
712 | + ret = ibm.read_md() |
713 | + self.assertEqual(ibm.Platforms.TEMPLATE_LIVE_METADATA, |
714 | + ret['platform']) |
715 | + self.assertEqual(tmpdir, ret['source']) |
716 | + self.assertEqual(self.userdata, ret['userdata']) |
717 | + self.assertEqual(self._get_expected_metadata(self.template_md), |
718 | + ret['metadata']) |
719 | + self.assertEqual(self.sysuuid, ret['system-uuid']) |
720 | + |
721 | + def test_os_code_live(self, m_platform, m_sysuuid): |
722 | + """Verify an os_code metadata path.""" |
723 | + tmpdir = self.tmp_dir() |
724 | + m_platform.return_value = (ibm.Platforms.OS_CODE, tmpdir) |
725 | + netdata = json.dumps(self.network_data) |
726 | + test_helpers.populate_dir(tmpdir, { |
727 | + 'openstack/latest/meta_data.json': json.dumps(self.oscode_md), |
728 | + 'openstack/latest/user_data': self.userdata, |
729 | + 'openstack/latest/vendor_data.json': json.dumps(self.vendor_data), |
730 | + 'openstack/latest/network_data.json': netdata, |
731 | + }) |
732 | + |
733 | + ret = ibm.read_md() |
734 | + self.assertEqual(ibm.Platforms.OS_CODE, ret['platform']) |
735 | + self.assertEqual(tmpdir, ret['source']) |
736 | + self.assertEqual(self.userdata, ret['userdata']) |
737 | + self.assertEqual(self._get_expected_metadata(self.oscode_md), |
738 | + ret['metadata']) |
739 | + |
740 | + def test_os_code_live_no_userdata(self, m_platform, m_sysuuid): |
741 | + """Verify os_code without user-data.""" |
742 | + tmpdir = self.tmp_dir() |
743 | + m_platform.return_value = (ibm.Platforms.OS_CODE, tmpdir) |
744 | + test_helpers.populate_dir(tmpdir, { |
745 | + 'openstack/latest/meta_data.json': json.dumps(self.oscode_md), |
746 | + 'openstack/latest/vendor_data.json': json.dumps(self.vendor_data), |
747 | + }) |
748 | + |
749 | + ret = ibm.read_md() |
750 | + self.assertEqual(ibm.Platforms.OS_CODE, ret['platform']) |
751 | + self.assertEqual(tmpdir, ret['source']) |
752 | + self.assertIsNone(ret['userdata']) |
753 | + self.assertEqual(self._get_expected_metadata(self.oscode_md), |
754 | + ret['metadata']) |
755 | + |
756 | + |
757 | +# vi: ts=4 expandtab |
758 | diff --git a/tests/unittests/test_ds_identify.py b/tests/unittests/test_ds_identify.py |
759 | index 85999b7..5364398 100644 |
760 | --- a/tests/unittests/test_ds_identify.py |
761 | +++ b/tests/unittests/test_ds_identify.py |
762 | @@ -9,6 +9,8 @@ from cloudinit import util |
763 | from cloudinit.tests.helpers import ( |
764 | CiTestCase, dir2dict, populate_dir) |
765 | |
766 | +from cloudinit.sources import DataSourceIBMCloud as dsibm |
767 | + |
768 | UNAME_MYSYS = ("Linux bart 4.4.0-62-generic #83-Ubuntu " |
769 | "SMP Wed Jan 18 14:10:15 UTC 2017 x86_64 GNU/Linux") |
770 | UNAME_PPC64EL = ("Linux diamond 4.4.0-83-generic #106-Ubuntu SMP " |
771 | @@ -37,8 +39,8 @@ BLKID_UEFI_UBUNTU = [ |
772 | |
773 | POLICY_FOUND_ONLY = "search,found=all,maybe=none,notfound=disabled" |
774 | POLICY_FOUND_OR_MAYBE = "search,found=all,maybe=all,notfound=disabled" |
775 | -DI_DEFAULT_POLICY = "search,found=all,maybe=all,notfound=enabled" |
776 | -DI_DEFAULT_POLICY_NO_DMI = "search,found=all,maybe=all,notfound=disabled" |
777 | +DI_DEFAULT_POLICY = "search,found=all,maybe=all,notfound=disabled" |
778 | +DI_DEFAULT_POLICY_NO_DMI = "search,found=all,maybe=all,notfound=enabled" |
779 | DI_EC2_STRICT_ID_DEFAULT = "true" |
780 | OVF_MATCH_STRING = 'http://schemas.dmtf.org/ovf/environment/1' |
781 | |
782 | @@ -64,6 +66,9 @@ P_SYS_VENDOR = "sys/class/dmi/id/sys_vendor" |
783 | P_SEED_DIR = "var/lib/cloud/seed" |
784 | P_DSID_CFG = "etc/cloud/ds-identify.cfg" |
785 | |
786 | +IBM_PROVISIONING_CHECK_PATH = "/root/provisioningConfiguration.cfg" |
787 | +IBM_CONFIG_UUID = "9796-932E" |
788 | + |
789 | MOCK_VIRT_IS_KVM = {'name': 'detect_virt', 'RET': 'kvm', 'ret': 0} |
790 | MOCK_VIRT_IS_VMWARE = {'name': 'detect_virt', 'RET': 'vmware', 'ret': 0} |
791 | MOCK_VIRT_IS_XEN = {'name': 'detect_virt', 'RET': 'xen', 'ret': 0} |
792 | @@ -239,6 +244,57 @@ class TestDsIdentify(CiTestCase): |
793 | self._test_ds_found('ConfigDriveUpper') |
794 | return |
795 | |
796 | + def test_ibmcloud_template_userdata_in_provisioning(self): |
797 | + """Template provisioned with user-data during provisioning stage. |
798 | + |
799 | + Template provisioning with user-data has METADATA disk, |
800 | + datasource should return not found.""" |
801 | + data = copy.deepcopy(VALID_CFG['IBMCloud-metadata']) |
802 | + data['files'] = {IBM_PROVISIONING_CHECK_PATH: 'xxx'} |
803 | + return self._check_via_dict(data, RC_NOT_FOUND) |
804 | + |
805 | + def test_ibmcloud_template_userdata(self): |
806 | + """Template provisioned with user-data first boot. |
807 | + |
808 | + Template provisioning with user-data has METADATA disk. |
809 | + datasource should return found.""" |
810 | + self._test_ds_found('IBMCloud-metadata') |
811 | + |
812 | + def test_ibmcloud_template_no_userdata_in_provisioning(self): |
813 | + """Template provisioned with no user-data during provisioning. |
814 | + |
815 | + no disks attached. Datasource should return not found.""" |
816 | + data = copy.deepcopy(VALID_CFG['IBMCloud-nodisks']) |
817 | + data['files'] = {IBM_PROVISIONING_CHECK_PATH: 'xxx'} |
818 | + return self._check_via_dict(data, RC_NOT_FOUND) |
819 | + |
820 | + def test_ibmcloud_template_no_userdata(self): |
821 | + """Template provisioned with no user-data first boot. |
822 | + |
823 | + no disks attached. Datasource should return found.""" |
824 | + self._check_via_dict(VALID_CFG['IBMCloud-nodisks'], RC_NOT_FOUND) |
825 | + |
826 | + def test_ibmcloud_os_code(self): |
827 | + """Launched by os code always has config-2 disk.""" |
828 | + self._test_ds_found('IBMCloud-config-2') |
829 | + |
830 | + def test_ibmcloud_os_code_different_uuid(self): |
831 | + """IBM cloud config-2 disks must be explicit match on UUID. |
832 | + |
833 | + If the UUID is not 9796-932E then we actually expect ConfigDrive.""" |
834 | + data = copy.deepcopy(VALID_CFG['IBMCloud-config-2']) |
835 | + offset = None |
836 | + for m, d in enumerate(data['mocks']): |
837 | + if d.get('name') == "blkid": |
838 | + offset = m |
839 | + break |
840 | + if not offset: |
841 | + raise ValueError("Expected to find 'blkid' mock, but did not.") |
842 | + data['mocks'][offset]['out'] = d['out'].replace(dsibm.IBM_CONFIG_UUID, |
843 | + "DEAD-BEEF") |
844 | + self._check_via_dict( |
845 | + data, rc=RC_FOUND, dslist=['ConfigDrive', DS_NONE]) |
846 | + |
847 | def test_policy_disabled(self): |
848 | """A Builtin policy of 'disabled' should return not found. |
849 | |
850 | @@ -452,7 +508,7 @@ VALID_CFG = { |
851 | }, |
852 | 'Ec2-xen': { |
853 | 'ds': 'Ec2', |
854 | - 'mocks': [{'name': 'detect_virt', 'RET': 'xen', 'ret': 0}], |
855 | + 'mocks': [MOCK_VIRT_IS_XEN], |
856 | 'files': { |
857 | 'sys/hypervisor/uuid': 'ec2c6e2f-5fac-4fc7-9c82-74127ec14bbb\n' |
858 | }, |
859 | @@ -579,6 +635,48 @@ VALID_CFG = { |
860 | 'ds': 'Hetzner', |
861 | 'files': {P_SYS_VENDOR: 'Hetzner\n'}, |
862 | }, |
863 | + 'IBMCloud-metadata': { |
864 | + 'ds': 'IBMCloud', |
865 | + 'mocks': [ |
866 | + MOCK_VIRT_IS_XEN, |
867 | + {'name': 'blkid', 'ret': 0, |
868 | + 'out': blkid_out( |
869 | + [{'DEVNAME': 'xvda1', 'TYPE': 'vfat', 'PARTUUID': uuid4()}, |
870 | + {'DEVNAME': 'xvda2', 'TYPE': 'ext4', |
871 | + 'LABEL': 'cloudimg-rootfs', 'PARTUUID': uuid4()}, |
872 | + {'DEVNAME': 'xvdb', 'TYPE': 'vfat', 'LABEL': 'METADATA'}]), |
873 | + }, |
874 | + ], |
875 | + }, |
876 | + 'IBMCloud-config-2': { |
877 | + 'ds': 'IBMCloud', |
878 | + 'mocks': [ |
879 | + MOCK_VIRT_IS_XEN, |
880 | + {'name': 'blkid', 'ret': 0, |
881 | + 'out': blkid_out( |
882 | + [{'DEVNAME': 'xvda1', 'TYPE': 'ext3', 'PARTUUID': uuid4(), |
883 | + 'UUID': uuid4(), 'LABEL': 'cloudimg-bootfs'}, |
884 | + {'DEVNAME': 'xvdb', 'TYPE': 'vfat', 'LABEL': 'config-2', |
885 | + 'UUID': dsibm.IBM_CONFIG_UUID}, |
886 | + {'DEVNAME': 'xvda2', 'TYPE': 'ext4', |
887 | + 'LABEL': 'cloudimg-rootfs', 'PARTUUID': uuid4(), |
888 | + 'UUID': uuid4()}, |
889 | + ]), |
890 | + }, |
891 | + ], |
892 | + }, |
893 | + 'IBMCloud-nodisks': { |
894 | + 'ds': 'IBMCloud', |
895 | + 'mocks': [ |
896 | + MOCK_VIRT_IS_XEN, |
897 | + {'name': 'blkid', 'ret': 0, |
898 | + 'out': blkid_out( |
899 | + [{'DEVNAME': 'xvda1', 'TYPE': 'vfat', 'PARTUUID': uuid4()}, |
900 | + {'DEVNAME': 'xvda2', 'TYPE': 'ext4', |
901 | + 'LABEL': 'cloudimg-rootfs', 'PARTUUID': uuid4()}]), |
902 | + }, |
903 | + ], |
904 | + }, |
905 | } |
906 | |
907 | # vi: ts=4 expandtab |
908 | diff --git a/tools/ds-identify b/tools/ds-identify |
909 | index e2552c8..9a2db5c 100755 |
910 | --- a/tools/ds-identify |
911 | +++ b/tools/ds-identify |
912 | @@ -92,6 +92,7 @@ DI_DMI_SYS_VENDOR="" |
913 | DI_DMI_PRODUCT_SERIAL="" |
914 | DI_DMI_PRODUCT_UUID="" |
915 | DI_FS_LABELS="" |
916 | +DI_FS_UUIDS="" |
917 | DI_ISO9660_DEVS="" |
918 | DI_KERNEL_CMDLINE="" |
919 | DI_VIRT="" |
920 | @@ -114,7 +115,7 @@ DI_DSNAME="" |
921 | # be searched if there is no setting found in config. |
922 | DI_DSLIST_DEFAULT="MAAS ConfigDrive NoCloud AltCloud Azure Bigstep \ |
923 | CloudSigma CloudStack DigitalOcean AliYun Ec2 GCE OpenNebula OpenStack \ |
924 | -OVF SmartOS Scaleway Hetzner" |
925 | +OVF SmartOS Scaleway Hetzner IBMCloud" |
926 | DI_DSLIST="" |
927 | DI_MODE="" |
928 | DI_ON_FOUND="" |
929 | @@ -123,6 +124,8 @@ DI_ON_NOTFOUND="" |
930 | |
931 | DI_EC2_STRICT_ID_DEFAULT="true" |
932 | |
933 | +_IS_IBM_CLOUD="" |
934 | + |
935 | error() { |
936 | set -- "ERROR:" "$@"; |
937 | debug 0 "$@" |
938 | @@ -196,7 +199,7 @@ read_fs_info() { |
939 | return |
940 | fi |
941 | local oifs="$IFS" line="" delim="," |
942 | - local ret=0 out="" labels="" dev="" label="" ftype="" isodevs="" |
943 | + local ret=0 out="" labels="" dev="" label="" ftype="" isodevs="" uuids="" |
944 | out=$(blkid -c /dev/null -o export) || { |
945 | ret=$? |
946 | error "failed running [$ret]: blkid -c /dev/null -o export" |
947 | @@ -219,12 +222,14 @@ read_fs_info() { |
948 | LABEL=*) label="${line#LABEL=}"; |
949 | labels="${labels}${line#LABEL=}${delim}";; |
950 | TYPE=*) ftype=${line#TYPE=};; |
951 | + UUID=*) uuids="${uuids}${line#UUID=}$delim";; |
952 | esac |
953 | done |
954 | [ -n "$dev" -a "$ftype" = "iso9660" ] && |
955 | isodevs="${isodevs} ${dev}=$label" |
956 | |
957 | DI_FS_LABELS="${labels%${delim}}" |
958 | + DI_FS_UUIDS="${uuids%${delim}}" |
959 | DI_ISO9660_DEVS="${isodevs# }" |
960 | } |
961 | |
962 | @@ -437,14 +442,25 @@ dmi_sys_vendor_is() { |
963 | [ "${DI_DMI_SYS_VENDOR}" = "$1" ] |
964 | } |
965 | |
966 | -has_fs_with_label() { |
967 | - local label="$1" |
968 | - case ",${DI_FS_LABELS}," in |
969 | - *,$label,*) return 0;; |
970 | +has_fs_with_uuid() { |
971 | + case ",${DI_FS_UUIDS}," in |
972 | + *,$1,*) return 0;; |
973 | esac |
974 | return 1 |
975 | } |
976 | |
977 | +has_fs_with_label() { |
978 | + # has_fs_with_label(label1[ ,label2 ..]) |
979 | + # return 0 if a there is a filesystem that matches any of the labels. |
980 | + local label="" |
981 | + for label in "$@"; do |
982 | + case ",${DI_FS_LABELS}," in |
983 | + *,$label,*) return 0;; |
984 | + esac |
985 | + done |
986 | + return 1 |
987 | +} |
988 | + |
989 | nocase_equal() { |
990 | # nocase_equal(a, b) |
991 | # return 0 if case insenstive comparision a.lower() == b.lower() |
992 | @@ -583,6 +599,8 @@ dscheck_NoCloud() { |
993 | case " ${DI_DMI_PRODUCT_SERIAL} " in |
994 | *\ ds=nocloud*) return ${DS_FOUND};; |
995 | esac |
996 | + |
997 | + is_ibm_cloud && return ${DS_NOT_FOUND} |
998 | for d in nocloud nocloud-net; do |
999 | check_seed_dir "$d" meta-data user-data && return ${DS_FOUND} |
1000 | check_writable_seed_dir "$d" meta-data user-data && return ${DS_FOUND} |
1001 | @@ -594,9 +612,8 @@ dscheck_NoCloud() { |
1002 | } |
1003 | |
1004 | check_configdrive_v2() { |
1005 | - if has_fs_with_label "config-2"; then |
1006 | - return ${DS_FOUND} |
1007 | - elif has_fs_with_label "CONFIG-2"; then |
1008 | + is_ibm_cloud && return ${DS_NOT_FOUND} |
1009 | + if has_fs_with_label CONFIG-2 config-2; then |
1010 | return ${DS_FOUND} |
1011 | fi |
1012 | # look in /config-drive <vlc>/seed/config_drive for a directory |
1013 | @@ -988,6 +1005,36 @@ dscheck_Hetzner() { |
1014 | return ${DS_NOT_FOUND} |
1015 | } |
1016 | |
1017 | +is_ibm_provisioning() { |
1018 | + [ -f "${PATH_ROOT}/root/provisioningConfiguration.cfg" ] |
1019 | +} |
1020 | + |
1021 | +is_ibm_cloud() { |
1022 | + cached "${_IS_IBM_CLOUD}" && return ${_IS_IBM_CLOUD} |
1023 | + local ret=1 |
1024 | + if [ "$DI_VIRT" = "xen" ]; then |
1025 | + if is_ibm_provisioning; then |
1026 | + ret=0 |
1027 | + elif has_fs_with_label METADATA metadata; then |
1028 | + ret=0 |
1029 | + elif has_fs_with_uuid 9796-932E && |
1030 | + has_fs_with_label CONFIG-2 config-2; then |
1031 | + ret=0 |
1032 | + fi |
1033 | + fi |
1034 | + _IS_IBM_CLOUD=$ret |
1035 | + return $ret |
1036 | +} |
1037 | + |
1038 | +dscheck_IBMCloud() { |
1039 | + if is_ibm_provisioning; then |
1040 | + debug 1 "cloud-init disabled during provisioning on IBMCloud" |
1041 | + return ${DS_NOT_FOUND} |
1042 | + fi |
1043 | + is_ibm_cloud && return ${DS_FOUND} |
1044 | + return ${DS_NOT_FOUND} |
1045 | +} |
1046 | + |
1047 | collect_info() { |
1048 | read_virt |
1049 | read_pid1_product_name |
Addressed minor review comments to land this branch.