|
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
|