Blame cloudinit/sources/DataSourceConfigDrive.py

Packit Service a04d08
# Copyright (C) 2012 Canonical Ltd.
Packit Service a04d08
# Copyright (C) 2012 Yahoo! Inc.
Packit Service a04d08
#
Packit Service a04d08
# Author: Scott Moser <scott.moser@canonical.com>
Packit Service a04d08
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
Packit Service a04d08
#
Packit Service a04d08
# This file is part of cloud-init. See LICENSE file for license information.
Packit Service a04d08
Packit Service a04d08
import os
Packit Service a04d08
Packit Service a04d08
from cloudinit import log as logging
Packit Service a04d08
from cloudinit import sources
Packit Service 9bfd13
from cloudinit import subp
Packit Service a04d08
from cloudinit import util
Packit Service a04d08
Packit Service a04d08
from cloudinit.net import eni
Packit Service a04d08
Packit Service a04d08
from cloudinit.sources.DataSourceIBMCloud import get_ibm_platform
Packit Service a04d08
from cloudinit.sources.helpers import openstack
Packit Service a04d08
Packit Service a04d08
LOG = logging.getLogger(__name__)
Packit Service a04d08
Packit Service a04d08
# Various defaults/constants...
Packit Service a04d08
DEFAULT_IID = "iid-dsconfigdrive"
Packit Service a04d08
DEFAULT_MODE = 'pass'
Packit Service a04d08
DEFAULT_METADATA = {
Packit Service a04d08
    "instance-id": DEFAULT_IID,
Packit Service a04d08
}
Packit Service a04d08
FS_TYPES = ('vfat', 'iso9660')
Packit Service a04d08
LABEL_TYPES = ('config-2', 'CONFIG-2')
Packit Service a04d08
POSSIBLE_MOUNTS = ('sr', 'cd')
Packit Service a04d08
OPTICAL_DEVICES = tuple(('/dev/%s%s' % (z, i) for z in POSSIBLE_MOUNTS
Packit Service a04d08
                        for i in range(0, 2)))
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class DataSourceConfigDrive(openstack.SourceMixin, sources.DataSource):
Packit Service a04d08
Packit Service a04d08
    dsname = 'ConfigDrive'
Packit Service a04d08
Packit Service a04d08
    def __init__(self, sys_cfg, distro, paths):
Packit Service a04d08
        super(DataSourceConfigDrive, self).__init__(sys_cfg, distro, paths)
Packit Service a04d08
        self.source = None
Packit Service a04d08
        self.seed_dir = os.path.join(paths.seed_dir, 'config_drive')
Packit Service a04d08
        self.version = None
Packit Service a04d08
        self.ec2_metadata = None
Packit Service a04d08
        self._network_config = None
Packit Service a04d08
        self.network_json = sources.UNSET
Packit Service a04d08
        self.network_eni = None
Packit Service a04d08
        self.known_macs = None
Packit Service a04d08
        self.files = {}
Packit Service a04d08
Packit Service a04d08
    def __str__(self):
Packit Service a04d08
        root = sources.DataSource.__str__(self)
Packit Service a04d08
        mstr = "%s [%s,ver=%s]" % (root, self.dsmode, self.version)
Packit Service a04d08
        mstr += "[source=%s]" % (self.source)
Packit Service a04d08
        return mstr
Packit Service a04d08
Packit Service a04d08
    def _get_data(self):
Packit Service a04d08
        found = None
Packit Service a04d08
        md = {}
Packit Service a04d08
        results = {}
Packit Service a04d08
        for sdir in (self.seed_dir, "/config-drive"):
Packit Service a04d08
            if not os.path.isdir(sdir):
Packit Service a04d08
                continue
Packit Service a04d08
            try:
Packit Service a04d08
                results = read_config_drive(sdir)
Packit Service a04d08
                found = sdir
Packit Service a04d08
                break
Packit Service a04d08
            except openstack.NonReadable:
Packit Service a04d08
                util.logexc(LOG, "Failed reading config drive from %s", sdir)
Packit Service a04d08
Packit Service a04d08
        if not found:
Packit Service a04d08
            dslist = self.sys_cfg.get('datasource_list')
Packit Service a04d08
            for dev in find_candidate_devs(dslist=dslist):
Packit Service 9bfd13
                mtype = None
Packit Service 9bfd13
                if util.is_BSD():
Packit Service 9bfd13
                    if dev.startswith("/dev/cd"):
Packit Service b1601c
                        mtype = "cd9660"
Packit Service 9bfd13
                try:
Packit Service a04d08
                    results = util.mount_cb(dev, read_config_drive,
Packit Service a04d08
                                            mtype=mtype)
Packit Service a04d08
                    found = dev
Packit Service a04d08
                except openstack.NonReadable:
Packit Service a04d08
                    pass
Packit Service a04d08
                except util.MountFailedError:
Packit Service a04d08
                    pass
Packit Service a04d08
                except openstack.BrokenMetadata:
Packit Service a04d08
                    util.logexc(LOG, "Broken config drive: %s", dev)
Packit Service a04d08
                if found:
Packit Service a04d08
                    break
Packit Service a04d08
        if not found:
Packit Service a04d08
            return False
Packit Service a04d08
Packit Service a04d08
        md = results.get('metadata', {})
Packit Service a04d08
        md = util.mergemanydict([md, DEFAULT_METADATA])
Packit Service a04d08
Packit Service a04d08
        self.dsmode = self._determine_dsmode(
Packit Service a04d08
            [results.get('dsmode'), self.ds_cfg.get('dsmode'),
Packit Service a04d08
             sources.DSMODE_PASS if results['version'] == 1 else None])
Packit Service a04d08
Packit Service a04d08
        if self.dsmode == sources.DSMODE_DISABLED:
Packit Service a04d08
            return False
Packit Service a04d08
Packit Service a04d08
        prev_iid = get_previous_iid(self.paths)
Packit Service a04d08
        cur_iid = md['instance-id']
Packit Service a04d08
        if prev_iid != cur_iid:
Packit Service a04d08
            # better would be to handle this centrally, allowing
Packit Service a04d08
            # the datasource to do something on new instance id
Packit Service a04d08
            # note, networking is only rendered here if dsmode is DSMODE_PASS
Packit Service a04d08
            # which means "DISABLED, but render files and networking"
Packit Service a04d08
            on_first_boot(results, distro=self.distro,
Packit Service a04d08
                          network=self.dsmode == sources.DSMODE_PASS)
Packit Service a04d08
Packit Service a04d08
        # This is legacy and sneaky.  If dsmode is 'pass' then do not claim
Packit Service a04d08
        # the datasource was used, even though we did run on_first_boot above.
Packit Service a04d08
        if self.dsmode == sources.DSMODE_PASS:
Packit Service a04d08
            LOG.debug("%s: not claiming datasource, dsmode=%s", self,
Packit Service a04d08
                      self.dsmode)
Packit Service a04d08
            return False
Packit Service a04d08
Packit Service a04d08
        self.source = found
Packit Service a04d08
        self.metadata = md
Packit Service a04d08
        self.ec2_metadata = results.get('ec2-metadata')
Packit Service a04d08
        self.userdata_raw = results.get('userdata')
Packit Service a04d08
        self.version = results['version']
Packit Service a04d08
        self.files.update(results.get('files', {}))
Packit Service a04d08
Packit Service a04d08
        vd = results.get('vendordata')
Packit Service a04d08
        self.vendordata_pure = vd
Packit Service a04d08
        try:
Packit Service a04d08
            self.vendordata_raw = sources.convert_vendordata(vd)
Packit Service a04d08
        except ValueError as e:
Packit Service a04d08
            LOG.warning("Invalid content in vendor-data: %s", e)
Packit Service a04d08
            self.vendordata_raw = None
Packit Service a04d08
Packit Service a04d08
        # network_config is an /etc/network/interfaces formated file and is
Packit Service a04d08
        # obsolete compared to networkdata (from network_data.json) but both
Packit Service a04d08
        # might be present.
Packit Service a04d08
        self.network_eni = results.get("network_config")
Packit Service a04d08
        self.network_json = results.get('networkdata')
Packit Service a04d08
        return True
Packit Service a04d08
Packit Service a04d08
    def check_instance_id(self, sys_cfg):
Packit Service a04d08
        # quickly (local check only) if self.instance_id is still valid
Packit Service a04d08
        return sources.instance_id_matches_system_uuid(self.get_instance_id())
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def network_config(self):
Packit Service a04d08
        if self._network_config is None:
Packit Service a04d08
            if self.network_json not in (None, sources.UNSET):
Packit Service a04d08
                LOG.debug("network config provided via network_json")
Packit Service a04d08
                self._network_config = openstack.convert_net_json(
Packit Service a04d08
                    self.network_json, known_macs=self.known_macs)
Packit Service a04d08
            elif self.network_eni is not None:
Packit Service a04d08
                self._network_config = eni.convert_eni_data(self.network_eni)
Packit Service a04d08
                LOG.debug("network config provided via converted eni data")
Packit Service a04d08
            else:
Packit Service a04d08
                LOG.debug("no network configuration available")
Packit Service a04d08
        return self._network_config
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def platform(self):
Packit Service a04d08
        return 'openstack'
Packit Service a04d08
Packit Service a04d08
    def _get_subplatform(self):
Packit Service a04d08
        """Return the subplatform metadata source details."""
Packit Service a04d08
        if self.source.startswith('/dev'):
Packit Service a04d08
            subplatform_type = 'config-disk'
Packit Service a04d08
        else:
Packit Service a04d08
            subplatform_type = 'seed-dir'
Packit Service a04d08
        return '%s (%s)' % (subplatform_type, self.source)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def read_config_drive(source_dir):
Packit Service a04d08
    reader = openstack.ConfigDriveReader(source_dir)
Packit Service a04d08
    finders = [
Packit Service a04d08
        (reader.read_v2, [], {}),
Packit Service a04d08
        (reader.read_v1, [], {}),
Packit Service a04d08
    ]
Packit Service a04d08
    excps = []
Packit Service a04d08
    for (functor, args, kwargs) in finders:
Packit Service a04d08
        try:
Packit Service a04d08
            return functor(*args, **kwargs)
Packit Service a04d08
        except openstack.NonReadable as e:
Packit Service a04d08
            excps.append(e)
Packit Service a04d08
    raise excps[-1]
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def get_previous_iid(paths):
Packit Service a04d08
    # interestingly, for this purpose the "previous" instance-id is the current
Packit Service a04d08
    # instance-id.  cloud-init hasn't moved them over yet as this datasource
Packit Service a04d08
    # hasn't declared itself found.
Packit Service a04d08
    fname = os.path.join(paths.get_cpath('data'), 'instance-id')
Packit Service a04d08
    try:
Packit Service a04d08
        return util.load_file(fname).rstrip("\n")
Packit Service a04d08
    except IOError:
Packit Service a04d08
        return None
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def on_first_boot(data, distro=None, network=True):
Packit Service a04d08
    """Performs any first-boot actions using data read from a config-drive."""
Packit Service a04d08
    if not isinstance(data, dict):
Packit Service a04d08
        raise TypeError("Config-drive data expected to be a dict; not %s"
Packit Service a04d08
                        % (type(data)))
Packit Service a04d08
    if network:
Packit Service a04d08
        net_conf = data.get("network_config", '')
Packit Service a04d08
        if net_conf and distro:
Packit Service a04d08
            LOG.warning("Updating network interfaces from config drive")
Packit Service a04d08
            distro.apply_network_config(eni.convert_eni_data(net_conf))
Packit Service a04d08
    write_injected_files(data.get('files'))
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def write_injected_files(files):
Packit Service a04d08
    if files:
Packit Service a04d08
        LOG.debug("Writing %s injected files", len(files))
Packit Service a04d08
        for (filename, content) in files.items():
Packit Service a04d08
            if not filename.startswith(os.sep):
Packit Service a04d08
                filename = os.sep + filename
Packit Service a04d08
            try:
Packit Service a04d08
                util.write_file(filename, content, mode=0o660)
Packit Service a04d08
            except IOError:
Packit Service a04d08
                util.logexc(LOG, "Failed writing file: %s", filename)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def find_candidate_devs(probe_optical=True, dslist=None):
Packit Service a04d08
    """Return a list of devices that may contain the config drive.
Packit Service a04d08
Packit Service a04d08
    The returned list is sorted by search order where the first item has
Packit Service a04d08
    should be searched first (highest priority)
Packit Service a04d08
Packit Service a04d08
    config drive v1:
Packit Service a04d08
       Per documentation, this is "associated as the last available disk on the
Packit Service a04d08
       instance", and should be VFAT.
Packit Service a04d08
       Currently, we do not restrict search list to "last available disk"
Packit Service a04d08
Packit Service a04d08
    config drive v2:
Packit Service a04d08
       Disk should be:
Packit Service a04d08
        * either vfat or iso9660 formatted
Packit Service a04d08
        * labeled with 'config-2' or 'CONFIG-2'
Packit Service a04d08
    """
Packit Service a04d08
    if dslist is None:
Packit Service a04d08
        dslist = []
Packit Service a04d08
Packit Service a04d08
    # query optical drive to get it in blkid cache for 2.6 kernels
Packit Service a04d08
    if probe_optical:
Packit Service a04d08
        for device in OPTICAL_DEVICES:
Packit Service a04d08
            try:
Packit Service a04d08
                util.find_devs_with(path=device)
Packit Service 9bfd13
            except subp.ProcessExecutionError:
Packit Service a04d08
                pass
Packit Service a04d08
Packit Service a04d08
    by_fstype = []
Packit Service a04d08
    for fs_type in FS_TYPES:
Packit Service a04d08
        by_fstype.extend(util.find_devs_with("TYPE=%s" % (fs_type)))
Packit Service a04d08
Packit Service a04d08
    by_label = []
Packit Service a04d08
    for label in LABEL_TYPES:
Packit Service a04d08
        by_label.extend(util.find_devs_with("LABEL=%s" % (label)))
Packit Service a04d08
Packit Service a04d08
    # give preference to "last available disk" (vdb over vda)
Packit Service a04d08
    # note, this is not a perfect rendition of that.
Packit Service a04d08
    by_fstype.sort(reverse=True)
Packit Service a04d08
    by_label.sort(reverse=True)
Packit Service a04d08
Packit Service a04d08
    # combine list of items by putting by-label items first
Packit Service a04d08
    # followed by fstype items, but with dupes removed
Packit Service a04d08
    candidates = (by_label + [d for d in by_fstype if d not in by_label])
Packit Service a04d08
Packit Service a04d08
    # We are looking for a block device or partition with necessary label or
Packit Service a04d08
    # an unpartitioned block device (ex sda, not sda1)
Packit Service a04d08
    devices = [d for d in candidates
Packit Service a04d08
               if d in by_label or not util.is_partition(d)]
Packit Service a04d08
Packit Service a04d08
    LOG.debug("devices=%s dslist=%s", devices, dslist)
Packit Service a04d08
    if devices and "IBMCloud" in dslist:
Packit Service a04d08
        # IBMCloud uses config-2 label, but limited to a single UUID.
Packit Service a04d08
        ibm_platform, ibm_path = get_ibm_platform()
Packit Service a04d08
        if ibm_path in devices:
Packit Service a04d08
            devices.remove(ibm_path)
Packit Service a04d08
            LOG.debug("IBMCloud device '%s' (%s) removed from candidate list",
Packit Service a04d08
                      ibm_path, ibm_platform)
Packit Service a04d08
Packit Service a04d08
    return devices
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# Legacy: Must be present in case we load an old pkl object
Packit Service a04d08
DataSourceConfigDriveNet = DataSourceConfigDrive
Packit Service a04d08
Packit Service a04d08
# Used to match classes to dependencies
Packit Service a04d08
datasources = [
Packit Service a04d08
    (DataSourceConfigDrive, (sources.DEP_FILESYSTEM,)),
Packit Service a04d08
]
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# Return a list of data sources that match this set of dependencies
Packit Service a04d08
def get_datasource_list(depends):
Packit Service a04d08
    return sources.list_from_depends(depends, datasources)
Packit Service a04d08
Packit Service a04d08
# vi: ts=4 expandtab