cloud-init-dev team mailing list archive
-
cloud-init-dev team
-
Mailing list archive
-
Message #04444
Re: [Merge] ~smoser/cloud-init:feature/datasource-ibmcloud into cloud-init:master
addressed feedbacks.
Diff comments:
> diff --git a/cloudinit/sources/DataSourceIBMCloud.py b/cloudinit/sources/DataSourceIBMCloud.py
> new file mode 100644
> index 0000000..fa6cef0
> --- /dev/null
> +++ b/cloudinit/sources/DataSourceIBMCloud.py
> @@ -0,0 +1,311 @@
> +# This file is part of cloud-init. See LICENSE file for license information.
> +"""Datasource for IBMCloud.
> +
> +IBMCloud is also know as SoftLayer or BlueMix.
> +IBMCloud hypervisor is xen (2018-03-10).
> +
> +There are 2 different api exposed launch methods.
> + * template: This is the legacy method of launching instances.
> + When booting from an image template, the system boots first into
> + a "provisioning" mode. There, host <-> guest mechanisms are utilized
> + to execute code in the guest and provision it.
> +
> + Cloud-init will disables itself when it detects that it is in the
ack
> + provisioning mode. It detects this by the presence of
> + a file '/root/provisioningConfiguration.cfg'.
> +
> + When provided with user-data, the "first boot" will contain a
> + ConfigDrive-like disk labeled with 'METADATA'. If there is no user-data
> + provided, then there is no data-source.
> +
> + Cloud-init never does any network configuration in this mode.
> +
> + * os_code: Essentially "launch by OS Code" (Operating System Code).
> + This is a more modern approach. There is no specific "provisioning" boot.
> + Instead, cloud-init does all the customization. With or without
> + user-data provided, an OpenStack ConfigDrive like disk is attached.
> +
> + Only disks with label 'config-2' and UUID '9796-932E' are considered.
> + This is to avoid this datasource claiming ConfigDrive. This does
> + mean that 1 in 8^16 (~4 billion) Xen ConfigDrive systems will be
> + incorrectly identified as IBMCloud.
> +
> +TODO:
> + * is uuid (/sys/hypervisor/uuid) stable for life of an instance?
> + it seems it is not the same as data's uuid in the os_code case
> + but is in the template case.
> +
> +"""
> +import base64
> +import json
> +import os
> +
> +from cloudinit import log as logging
> +from cloudinit import sources
> +from cloudinit.sources.helpers import openstack
> +from cloudinit import util
> +
> +LOG = logging.getLogger(__name__)
> +
> +IBM_CONFIG_UUID = "9796-932E"
> +
> +
> +class Platforms(object):
> + TEMPLATE_LIVE_METADATA = "Template/Live/Metadata"
> + TEMPLATE_LIVE_NODATA = "UNABLE TO BE IDENTIFIED."
> + TEMPLATE_PROVISIONING_METADATA = "Template/Provisioning/Metadata"
> + TEMPLATE_PROVISIONING_NODATA = "Template/Provisioning/No-Metadata"
> + OS_CODE = "OS-Code/Live"
> +
> +
> +PROVISIONING = (
> + Platforms.TEMPLATE_PROVISIONING_METADATA,
> + Platforms.TEMPLATE_PROVISIONING_NODATA)
> +
> +
> +class DataSourceIBMCloud(sources.DataSource):
> +
> + dsname = 'IBMCloud'
> + system_uuid = None
> +
> + def __init__(self, sys_cfg, distro, paths):
> + super(DataSourceIBMCloud, self).__init__(sys_cfg, distro, paths)
> + self.source = None
> + self._network_config = None
> + self.network_json = None
> + self.platform = None
> +
> + def __str__(self):
> + root = sources.DataSource.__str__(self)
ack
> + mstr = "%s [%s %s]" % (root, self.platform, self.source)
> + return mstr
> +
> + def _get_data(self):
> + results = read_md()
> + if results is None:
> + return False
> +
> + self.source = results['source']
> + self.platform = results['platform']
> + self.metadata = results['metadata']
> + self.userdata_raw = results.get('userdata')
> + self.network_json = results.get('networkdata')
> + vd = results.get('vendordata')
> + self.vendordata_pure = vd
same behavior in the openstack case. yeah, we do not do anything with hit.
i'm open to thoughts on that.
> + self.system_uuid = results['system-uuid']
> + try:
> + self.vendordata_raw = sources.convert_vendordata(vd)
> + except ValueError as e:
> + LOG.warning("Invalid content in vendor-data: %s", e)
> + self.vendordata_raw = None
> +
> + return True
> +
> + def check_instance_id(self, sys_cfg):
> + """quickly (local check only) if self.instance_id is still valid
> +
> + in Template mode, the system uuid (/sys/hypervisor/uuid) is the
> + same as found in the METADATA disk. But that is not true in OS_CODE
> + mode. So we read the system_uuid and keep that for later compare."""
> + if self.system_uuid is None:
> + return False
> + return self.system_uuid == _read_system_uuid()
> +
> + @property
> + def network_config(self):
> + if self.platform != Platforms.OS_CODE:
> + # If deployed from template, an agent in the provisioning
> + # environment handles networking configuration. Not cloud-init.
> + return {'config': 'disabled', 'version': 1}
> + if self._network_config is None:
> + if self.network_json is not None:
> + LOG.debug("network config provided via network_json")
> + self._network_config = openstack.convert_net_json(
> + self.network_json, known_macs=None)
> + else:
> + LOG.debug("no network configuration available.")
> + return self._network_config
> +
> +
> +def _read_system_uuid():
> + uuid_path = "/sys/hypervisor/uuid"
> + if not os.path.isfile(uuid_path):
> + return None
> + return util.load_file(uuid_path).strip().lower()
> +
> +
> +def _is_xen():
> + return os.path.exists("/sys/hypervisor")
thanks, yeah, this is probably busted. i thought that only showed up in domu guests.
i wanted it to work without systemd-detect-virt.
> +
> +
> +def _is_ibm_provisioning():
> + return os.path.exists("/root/provisioningConfiguration.cfg")
> +
> +
> +def get_ibm_platform():
> + """Return a tuple (Platform, path)
> +
> + If this is Not IBM cloud, then the return value is (None, None).
> + An instance in provisioning mode is considered running on IBM cloud."""
> + label_mdata = "METADATA"
> + label_cfg2 = "CONFIG-2"
> + not_found = (None, None)
> +
> + if not _is_xen():
> + return not_found
> +
> + # fslabels contains only the first entry with a given label.
> + fslabels = {}
> + try:
> + devs = util.blkid()
> + except util.ProcessExecutionError as e:
> + LOG.warning("Failed to run blkid: %s", e)
> + return (None, None)
> +
> + for dev in sorted(devs.keys()):
> + data = devs[dev]
> + label = data.get("LABEL", "").upper()
> + uuid = data.get("UUID", "").upper()
> + if label not in (label_mdata, label_cfg2):
> + continue
> + if label in fslabels:
> + LOG.warning("Duplicate fslabel '%s'. existing=%s current=%s",
> + label, fslabels[label], data)
> + continue
> + if label == label_cfg2 and uuid != IBM_CONFIG_UUID:
> + LOG.debug("Skipping %s with LABEL=%s due to uuid != %s: %s",
> + dev, label, uuid, data)
> + continue
> + fslabels[label] = data
> +
> + metadata_path = fslabels.get(label_mdata, {}).get('DEVNAME')
> + cfg2_path = fslabels.get(label_cfg2, {}).get('DEVNAME')
> +
> + if cfg2_path:
> + return (Platforms.OS_CODE, cfg2_path)
> + elif metadata_path:
> + if _is_ibm_provisioning():
> + return (Platforms.TEMPLATE_PROVISIONING_METADATA, metadata_path)
> + else:
> + return (Platforms.TEMPLATE_LIVE_METADATA, metadata_path)
> + elif _is_ibm_provisioning():
> + return (Platforms.TEMPLATE_PROVISIONING_NODATA, None)
> + return not_found
> +
> +
> +def read_md():
> + """Read data from IBM Cloud.
> +
> + @return: None if not running on IBM Cloud.
> + dictionary with guaranteed fields: metadata, version
> + and optional fields: userdata, vendordata, networkdata.
> + Also includes the system uuid from /sys/hypervisor/uuid."""
> + platform, path = get_ibm_platform()
> + if platform is None:
> + LOG.debug("This is not an IBMCloud platform.")
> + return None
> + elif platform in PROVISIONING:
> + LOG.debug("Cloud-init is disabled during provisioning: %s.",
> + platform)
> + return None
> +
> + ret = {'platform': platform, 'source': path,
> + 'system-uuid': _read_system_uuid()}
> +
> + try:
> + if os.path.isdir(path):
> + results = metadata_from_dir(path)
> + else:
> + results = util.mount_cb(path, metadata_from_dir)
> + except BrokenMetadata as e:
> + raise RuntimeError(
> + "Failed reading IBM config disk (platform=%s path=%s): %s" %
> + (platform, path, e))
> +
> + ret.update(results)
> + return ret
> +
> +
> +class BrokenMetadata(IOError):
> + pass
> +
> +
> +def metadata_from_dir(source_dir):
> + def opath(fname):
> + return os.path.join("openstack", "latest", fname)
> +
> + def load_json_bytes(blob):
> + return json.loads(blob.decode('utf-8'))
> +
> + files = [
> + # tuples of (results_name, path, translator)
> + ('metadata_raw', opath('meta_data.json'), load_json_bytes),
> + ('userdata', opath('user_data'), None),
> + ('vendordata', opath('vendor_data.json'), load_json_bytes),
> + ('networkdata', opath('network_data.json'), load_json_bytes),
> + ]
> +
> + results = {}
> + for (name, path, transl) in files:
> + fpath = os.path.join(source_dir, path)
> + raw = None
> + try:
> + raw = util.load_file(fpath, decode=False)
> + except IOError as e:
> + LOG.debug("Failed reading path '%s': %s", fpath, e)
> +
> + if raw is None or transl is None:
> + data = raw
> + else:
> + try:
> + data = transl(raw)
> + except Exception as e:
> + raise BrokenMetadata("Failed decoding %s: %s" % (path, e))
> +
> + results[name] = data
> +
> + if results.get('metadata_raw') is None:
> + raise BrokenMetadata(
> + "%s missing required file 'meta_data.json'", source_dir)
> +
> + results['metadata'] = {}
> +
> + md_raw = results['metadata_raw']
> + md = results['metadata']
> + if 'random_seed' in md_raw:
> + try:
> + md['random_seed'] = base64.b64decode(md_raw['random_seed'])
> + except (ValueError, TypeError) as e:
> + raise BrokenMetadata(
> + "Badly formatted metadata random_seed entry: %s" % e)
> +
> + renames = (
> + ('public_keys', 'public-keys'), ('hostname', 'local-hostname'),
> + ('uuid', 'instance-id'))
> + for mdname, newname in renames:
> + if mdname in md_raw:
> + md[newname] = md_raw[mdname]
> +
> + return results
> +
> +
> +# Used to match classes to dependencies
> +datasources = [
> + (DataSourceIBMCloud, (sources.DEP_FILESYSTEM,)),
> +]
> +
> +
> +# Return a list of data sources that match this set of dependencies
> +def get_datasource_list(depends):
> + return sources.list_from_depends(depends, datasources)
> +
> +
> +if __name__ == "__main__":
> + import argparse
> +
> + parser = argparse.ArgumentParser(description='Query IBM Cloud Metadata')
> + args = parser.parse_args()
> + data = read_md()
> + print(util.json_dumps(data))
> +
> +# vi: ts=4 expandtab
--
https://code.launchpad.net/~smoser/cloud-init/+git/cloud-init/+merge/341774
Your team cloud-init commiters is requested to review the proposed merge of ~smoser/cloud-init:feature/datasource-ibmcloud into cloud-init:master.
References