Blame cloudinit/helpers.py

Packit Service a04d08
# Copyright (C) 2012 Canonical Ltd.
Packit Service a04d08
# Copyright (C) 2012, 2013 Hewlett-Packard Development Company, L.P.
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: Juerg Haefliger <juerg.haefliger@hp.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
from time import time
Packit Service a04d08
Packit Service a04d08
import contextlib
Packit Service a04d08
import os
Packit Service a04d08
from configparser import NoSectionError, NoOptionError, RawConfigParser
Packit Service a04d08
from io import StringIO
Packit Service a04d08
Packit Service a04d08
from cloudinit.settings import (PER_INSTANCE, PER_ALWAYS, PER_ONCE,
Packit Service a04d08
                                CFG_ENV_NAME)
Packit Service a04d08
Packit Service a04d08
from cloudinit import log as logging
Packit Service a04d08
from cloudinit import type_utils
Packit Service a04d08
from cloudinit import util
Packit Service a04d08
Packit Service a04d08
LOG = logging.getLogger(__name__)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class LockFailure(Exception):
Packit Service a04d08
    pass
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class DummyLock(object):
Packit Service a04d08
    pass
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class DummySemaphores(object):
Packit Service a04d08
    def __init__(self):
Packit Service a04d08
        pass
Packit Service a04d08
Packit Service a04d08
    @contextlib.contextmanager
Packit Service a04d08
    def lock(self, _name, _freq, _clear_on_fail=False):
Packit Service a04d08
        yield DummyLock()
Packit Service a04d08
Packit Service a04d08
    def has_run(self, _name, _freq):
Packit Service a04d08
        return False
Packit Service a04d08
Packit Service a04d08
    def clear(self, _name, _freq):
Packit Service a04d08
        return True
Packit Service a04d08
Packit Service a04d08
    def clear_all(self):
Packit Service a04d08
        pass
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class FileLock(object):
Packit Service a04d08
    def __init__(self, fn):
Packit Service a04d08
        self.fn = fn
Packit Service a04d08
Packit Service a04d08
    def __str__(self):
Packit Service a04d08
        return "<%s using file %r>" % (type_utils.obj_name(self), self.fn)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def canon_sem_name(name):
Packit Service a04d08
    return name.replace("-", "_")
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class FileSemaphores(object):
Packit Service a04d08
    def __init__(self, sem_path):
Packit Service a04d08
        self.sem_path = sem_path
Packit Service a04d08
Packit Service a04d08
    @contextlib.contextmanager
Packit Service a04d08
    def lock(self, name, freq, clear_on_fail=False):
Packit Service a04d08
        name = canon_sem_name(name)
Packit Service a04d08
        try:
Packit Service a04d08
            yield self._acquire(name, freq)
Packit Service a04d08
        except Exception:
Packit Service a04d08
            if clear_on_fail:
Packit Service a04d08
                self.clear(name, freq)
Packit Service a04d08
            raise
Packit Service a04d08
Packit Service a04d08
    def clear(self, name, freq):
Packit Service a04d08
        name = canon_sem_name(name)
Packit Service a04d08
        sem_file = self._get_path(name, freq)
Packit Service a04d08
        try:
Packit Service a04d08
            util.del_file(sem_file)
Packit Service a04d08
        except (IOError, OSError):
Packit Service a04d08
            util.logexc(LOG, "Failed deleting semaphore %s", sem_file)
Packit Service a04d08
            return False
Packit Service a04d08
        return True
Packit Service a04d08
Packit Service a04d08
    def clear_all(self):
Packit Service a04d08
        try:
Packit Service a04d08
            util.del_dir(self.sem_path)
Packit Service a04d08
        except (IOError, OSError):
Packit Service a04d08
            util.logexc(LOG, "Failed deleting semaphore directory %s",
Packit Service a04d08
                        self.sem_path)
Packit Service a04d08
Packit Service a04d08
    def _acquire(self, name, freq):
Packit Service a04d08
        # Check again if its been already gotten
Packit Service a04d08
        if self.has_run(name, freq):
Packit Service a04d08
            return None
Packit Service a04d08
        # This is a race condition since nothing atomic is happening
Packit Service a04d08
        # here, but this should be ok due to the nature of when
Packit Service a04d08
        # and where cloud-init runs... (file writing is not a lock...)
Packit Service a04d08
        sem_file = self._get_path(name, freq)
Packit Service a04d08
        contents = "%s: %s\n" % (os.getpid(), time())
Packit Service a04d08
        try:
Packit Service a04d08
            util.write_file(sem_file, contents)
Packit Service a04d08
        except (IOError, OSError):
Packit Service a04d08
            util.logexc(LOG, "Failed writing semaphore file %s", sem_file)
Packit Service a04d08
            return None
Packit Service a04d08
        return FileLock(sem_file)
Packit Service a04d08
Packit Service a04d08
    def has_run(self, name, freq):
Packit Service a04d08
        if not freq or freq == PER_ALWAYS:
Packit Service a04d08
            return False
Packit Service a04d08
Packit Service a04d08
        cname = canon_sem_name(name)
Packit Service a04d08
        sem_file = self._get_path(cname, freq)
Packit Service a04d08
        # This isn't really a good atomic check
Packit Service a04d08
        # but it suffices for where and when cloudinit runs
Packit Service a04d08
        if os.path.exists(sem_file):
Packit Service a04d08
            return True
Packit Service a04d08
Packit Service a04d08
        # this case could happen if the migrator module hadn't run yet
Packit Service a04d08
        # but the item had run before we did canon_sem_name.
Packit Service a04d08
        if cname != name and os.path.exists(self._get_path(name, freq)):
Packit Service a04d08
            LOG.warning("%s has run without canonicalized name [%s].\n"
Packit Service a04d08
                        "likely the migrator has not yet run. "
Packit Service a04d08
                        "It will run next boot.\n"
Packit Service a04d08
                        "run manually with: cloud-init single --name=migrator",
Packit Service a04d08
                        name, cname)
Packit Service a04d08
            return True
Packit Service a04d08
Packit Service a04d08
        return False
Packit Service a04d08
Packit Service a04d08
    def _get_path(self, name, freq):
Packit Service a04d08
        sem_path = self.sem_path
Packit Service a04d08
        if not freq or freq == PER_INSTANCE:
Packit Service a04d08
            return os.path.join(sem_path, name)
Packit Service a04d08
        else:
Packit Service a04d08
            return os.path.join(sem_path, "%s.%s" % (name, freq))
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class Runners(object):
Packit Service a04d08
    def __init__(self, paths):
Packit Service a04d08
        self.paths = paths
Packit Service a04d08
        self.sems = {}
Packit Service a04d08
Packit Service a04d08
    def _get_sem(self, freq):
Packit Service a04d08
        if freq == PER_ALWAYS or not freq:
Packit Service a04d08
            return None
Packit Service a04d08
        sem_path = None
Packit Service a04d08
        if freq == PER_INSTANCE:
Packit Service a04d08
            # This may not exist,
Packit Service a04d08
            # so thats why we still check for none
Packit Service a04d08
            # below if say the paths object
Packit Service a04d08
            # doesn't have a datasource that can
Packit Service a04d08
            # provide this instance path...
Packit Service a04d08
            sem_path = self.paths.get_ipath("sem")
Packit Service a04d08
        elif freq == PER_ONCE:
Packit Service a04d08
            sem_path = self.paths.get_cpath("sem")
Packit Service a04d08
        if not sem_path:
Packit Service a04d08
            return None
Packit Service a04d08
        if sem_path not in self.sems:
Packit Service a04d08
            self.sems[sem_path] = FileSemaphores(sem_path)
Packit Service a04d08
        return self.sems[sem_path]
Packit Service a04d08
Packit Service a04d08
    def run(self, name, functor, args, freq=None, clear_on_fail=False):
Packit Service a04d08
        sem = self._get_sem(freq)
Packit Service a04d08
        if not sem:
Packit Service a04d08
            sem = DummySemaphores()
Packit Service a04d08
        if not args:
Packit Service a04d08
            args = []
Packit Service a04d08
        if sem.has_run(name, freq):
Packit Service a04d08
            LOG.debug("%s already ran (freq=%s)", name, freq)
Packit Service a04d08
            return (False, None)
Packit Service a04d08
        with sem.lock(name, freq, clear_on_fail) as lk:
Packit Service a04d08
            if not lk:
Packit Service a04d08
                raise LockFailure("Failed to acquire lock for %s" % name)
Packit Service a04d08
            else:
Packit Service a04d08
                LOG.debug("Running %s using lock (%s)", name, lk)
Packit Service a04d08
                if isinstance(args, (dict)):
Packit Service a04d08
                    results = functor(**args)
Packit Service a04d08
                else:
Packit Service a04d08
                    results = functor(*args)
Packit Service a04d08
                return (True, results)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class ConfigMerger(object):
Packit Service a04d08
    def __init__(self, paths=None, datasource=None,
Packit Service a04d08
                 additional_fns=None, base_cfg=None,
Packit Service a04d08
                 include_vendor=True):
Packit Service a04d08
        self._paths = paths
Packit Service a04d08
        self._ds = datasource
Packit Service a04d08
        self._fns = additional_fns
Packit Service a04d08
        self._base_cfg = base_cfg
Packit Service a04d08
        self._include_vendor = include_vendor
Packit Service a04d08
        # Created on first use
Packit Service a04d08
        self._cfg = None
Packit Service a04d08
Packit Service a04d08
    def _get_datasource_configs(self):
Packit Service a04d08
        d_cfgs = []
Packit Service a04d08
        if self._ds:
Packit Service a04d08
            try:
Packit Service a04d08
                ds_cfg = self._ds.get_config_obj()
Packit Service a04d08
                if ds_cfg and isinstance(ds_cfg, (dict)):
Packit Service a04d08
                    d_cfgs.append(ds_cfg)
Packit Service a04d08
            except Exception:
Packit Service a04d08
                util.logexc(LOG, "Failed loading of datasource config object "
Packit Service a04d08
                            "from %s", self._ds)
Packit Service a04d08
        return d_cfgs
Packit Service a04d08
Packit Service a04d08
    def _get_env_configs(self):
Packit Service a04d08
        e_cfgs = []
Packit Service a04d08
        if CFG_ENV_NAME in os.environ:
Packit Service a04d08
            e_fn = os.environ[CFG_ENV_NAME]
Packit Service a04d08
            try:
Packit Service a04d08
                e_cfgs.append(util.read_conf(e_fn))
Packit Service a04d08
            except Exception:
Packit Service a04d08
                util.logexc(LOG, 'Failed loading of env. config from %s',
Packit Service a04d08
                            e_fn)
Packit Service a04d08
        return e_cfgs
Packit Service a04d08
Packit Service a04d08
    def _get_instance_configs(self):
Packit Service a04d08
        i_cfgs = []
Packit Service a04d08
        # If cloud-config was written, pick it up as
Packit Service a04d08
        # a configuration file to use when running...
Packit Service a04d08
        if not self._paths:
Packit Service a04d08
            return i_cfgs
Packit Service a04d08
Packit Service a04d08
        cc_paths = ['cloud_config']
Packit Service a04d08
        if self._include_vendor:
Packit Service a04d08
            cc_paths.append('vendor_cloud_config')
Packit Service a04d08
Packit Service a04d08
        for cc_p in cc_paths:
Packit Service a04d08
            cc_fn = self._paths.get_ipath_cur(cc_p)
Packit Service a04d08
            if cc_fn and os.path.isfile(cc_fn):
Packit Service a04d08
                try:
Packit Service a04d08
                    i_cfgs.append(util.read_conf(cc_fn))
Packit Service a04d08
                except PermissionError:
Packit Service a04d08
                    LOG.debug(
Packit Service a04d08
                        'Skipped loading cloud-config from %s due to'
Packit Service a04d08
                        ' non-root.', cc_fn)
Packit Service a04d08
                except Exception:
Packit Service a04d08
                    util.logexc(LOG, 'Failed loading of cloud-config from %s',
Packit Service a04d08
                                cc_fn)
Packit Service a04d08
        return i_cfgs
Packit Service a04d08
Packit Service a04d08
    def _read_cfg(self):
Packit Service a04d08
        # Input config files override
Packit Service a04d08
        # env config files which
Packit Service a04d08
        # override instance configs
Packit Service a04d08
        # which override datasource
Packit Service a04d08
        # configs which override
Packit Service a04d08
        # base configuration
Packit Service a04d08
        cfgs = []
Packit Service a04d08
        if self._fns:
Packit Service a04d08
            for c_fn in self._fns:
Packit Service a04d08
                try:
Packit Service a04d08
                    cfgs.append(util.read_conf(c_fn))
Packit Service a04d08
                except Exception:
Packit Service a04d08
                    util.logexc(LOG, "Failed loading of configuration from %s",
Packit Service a04d08
                                c_fn)
Packit Service a04d08
Packit Service a04d08
        cfgs.extend(self._get_env_configs())
Packit Service a04d08
        cfgs.extend(self._get_instance_configs())
Packit Service a04d08
        cfgs.extend(self._get_datasource_configs())
Packit Service a04d08
        if self._base_cfg:
Packit Service a04d08
            cfgs.append(self._base_cfg)
Packit Service a04d08
        return util.mergemanydict(cfgs)
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def cfg(self):
Packit Service a04d08
        # None check to avoid empty case causing re-reading
Packit Service a04d08
        if self._cfg is None:
Packit Service a04d08
            self._cfg = self._read_cfg()
Packit Service a04d08
        return self._cfg
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class ContentHandlers(object):
Packit Service a04d08
Packit Service a04d08
    def __init__(self):
Packit Service a04d08
        self.registered = {}
Packit Service a04d08
        self.initialized = []
Packit Service a04d08
Packit Service a04d08
    def __contains__(self, item):
Packit Service a04d08
        return self.is_registered(item)
Packit Service a04d08
Packit Service a04d08
    def __getitem__(self, key):
Packit Service a04d08
        return self._get_handler(key)
Packit Service a04d08
Packit Service a04d08
    def is_registered(self, content_type):
Packit Service a04d08
        return content_type in self.registered
Packit Service a04d08
Packit Service a04d08
    def register(self, mod, initialized=False, overwrite=True):
Packit Service a04d08
        types = set()
Packit Service a04d08
        for t in mod.list_types():
Packit Service a04d08
            if overwrite:
Packit Service a04d08
                types.add(t)
Packit Service a04d08
            else:
Packit Service a04d08
                if not self.is_registered(t):
Packit Service a04d08
                    types.add(t)
Packit Service a04d08
        for t in types:
Packit Service a04d08
            self.registered[t] = mod
Packit Service a04d08
        if initialized and mod not in self.initialized:
Packit Service a04d08
            self.initialized.append(mod)
Packit Service a04d08
        return types
Packit Service a04d08
Packit Service a04d08
    def _get_handler(self, content_type):
Packit Service a04d08
        return self.registered[content_type]
Packit Service a04d08
Packit Service a04d08
    def items(self):
Packit Service a04d08
        return list(self.registered.items())
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class Paths(object):
Packit Service a04d08
    def __init__(self, path_cfgs, ds=None):
Packit Service a04d08
        self.cfgs = path_cfgs
Packit Service a04d08
        # Populate all the initial paths
Packit Service a04d08
        self.cloud_dir = path_cfgs.get('cloud_dir', '/var/lib/cloud')
Packit Service a04d08
        self.run_dir = path_cfgs.get('run_dir', '/run/cloud-init')
Packit Service a04d08
        self.instance_link = os.path.join(self.cloud_dir, 'instance')
Packit Service a04d08
        self.boot_finished = os.path.join(self.instance_link, "boot-finished")
Packit Service a04d08
        self.upstart_conf_d = path_cfgs.get('upstart_dir')
Packit Service a04d08
        self.seed_dir = os.path.join(self.cloud_dir, 'seed')
Packit Service a04d08
        # This one isn't joined, since it should just be read-only
Packit Service a04d08
        template_dir = path_cfgs.get('templates_dir', '/etc/cloud/templates/')
Packit Service a04d08
        self.template_tpl = os.path.join(template_dir, '%s.tmpl')
Packit Service a04d08
        self.lookups = {
Packit Service a04d08
            "handlers": "handlers",
Packit Service a04d08
            "scripts": "scripts",
Packit Service a04d08
            "vendor_scripts": "scripts/vendor",
Packit Service a04d08
            "sem": "sem",
Packit Service a04d08
            "boothooks": "boothooks",
Packit Service a04d08
            "userdata_raw": "user-data.txt",
Packit Service a04d08
            "userdata": "user-data.txt.i",
Packit Service a04d08
            "obj_pkl": "obj.pkl",
Packit Service a04d08
            "cloud_config": "cloud-config.txt",
Packit Service a04d08
            "vendor_cloud_config": "vendor-cloud-config.txt",
Packit Service a04d08
            "data": "data",
Packit Service a04d08
            "vendordata_raw": "vendor-data.txt",
Packit Service a04d08
            "vendordata": "vendor-data.txt.i",
Packit Service a04d08
            "instance_id": ".instance-id",
Packit Service a04d08
            "manual_clean_marker": "manual-clean",
Packit Service a04d08
            "warnings": "warnings",
Packit Service a04d08
        }
Packit Service a04d08
        # Set when a datasource becomes active
Packit Service a04d08
        self.datasource = ds
Packit Service a04d08
Packit Service a04d08
    # get_ipath_cur: get the current instance path for an item
Packit Service a04d08
    def get_ipath_cur(self, name=None):
Packit Service a04d08
        return self._get_path(self.instance_link, name)
Packit Service a04d08
Packit Service a04d08
    # get_cpath : get the "clouddir" (/var/lib/cloud/<name>)
Packit Service a04d08
    # for a name in dirmap
Packit Service a04d08
    def get_cpath(self, name=None):
Packit Service a04d08
        return self._get_path(self.cloud_dir, name)
Packit Service a04d08
Packit Service a04d08
    # _get_ipath : get the instance path for a name in pathmap
Packit Service a04d08
    # (/var/lib/cloud/instances/<instance>/<name>)
Packit Service a04d08
    def _get_ipath(self, name=None):
Packit Service a04d08
        if not self.datasource:
Packit Service a04d08
            return None
Packit Service a04d08
        iid = self.datasource.get_instance_id()
Packit Service a04d08
        if iid is None:
Packit Service a04d08
            return None
Packit Service a04d08
        path_safe_iid = str(iid).replace(os.sep, '_')
Packit Service a04d08
        ipath = os.path.join(self.cloud_dir, 'instances', path_safe_iid)
Packit Service a04d08
        add_on = self.lookups.get(name)
Packit Service a04d08
        if add_on:
Packit Service a04d08
            ipath = os.path.join(ipath, add_on)
Packit Service a04d08
        return ipath
Packit Service a04d08
Packit Service a04d08
    # get_ipath : get the instance path for a name in pathmap
Packit Service a04d08
    # (/var/lib/cloud/instances/<instance>/<name>)
Packit Service a04d08
    # returns None + warns if no active datasource....
Packit Service a04d08
    def get_ipath(self, name=None):
Packit Service a04d08
        ipath = self._get_ipath(name)
Packit Service a04d08
        if not ipath:
Packit Service a04d08
            LOG.warning(("No per instance data available, "
Packit Service a04d08
                         "is there an datasource/iid set?"))
Packit Service a04d08
            return None
Packit Service a04d08
        else:
Packit Service a04d08
            return ipath
Packit Service a04d08
Packit Service a04d08
    def _get_path(self, base, name=None):
Packit Service a04d08
        if name is None:
Packit Service a04d08
            return base
Packit Service a04d08
        return os.path.join(base, self.lookups[name])
Packit Service a04d08
Packit Service a04d08
    def get_runpath(self, name=None):
Packit Service a04d08
        return self._get_path(self.run_dir, name)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# This config parser will not throw when sections don't exist
Packit Service a04d08
# and you are setting values on those sections which is useful
Packit Service a04d08
# when writing to new options that may not have corresponding
Packit Service a04d08
# sections. Also it can default other values when doing gets
Packit Service a04d08
# so that if those sections/options do not exist you will
Packit Service a04d08
# get a default instead of an error. Another useful case where
Packit Service a04d08
# you can avoid catching exceptions that you typically don't
Packit Service a04d08
# care about...
Packit Service a04d08
Packit Service a04d08
class DefaultingConfigParser(RawConfigParser):
Packit Service a04d08
    DEF_INT = 0
Packit Service a04d08
    DEF_FLOAT = 0.0
Packit Service a04d08
    DEF_BOOLEAN = False
Packit Service a04d08
    DEF_BASE = None
Packit Service a04d08
Packit Service a04d08
    def get(self, section, option):
Packit Service a04d08
        value = self.DEF_BASE
Packit Service a04d08
        try:
Packit Service a04d08
            value = RawConfigParser.get(self, section, option)
Packit Service a04d08
        except NoSectionError:
Packit Service a04d08
            pass
Packit Service a04d08
        except NoOptionError:
Packit Service a04d08
            pass
Packit Service a04d08
        return value
Packit Service a04d08
Packit Service a04d08
    def set(self, section, option, value=None):
Packit Service a04d08
        if not self.has_section(section) and section.lower() != 'default':
Packit Service a04d08
            self.add_section(section)
Packit Service a04d08
        RawConfigParser.set(self, section, option, value)
Packit Service a04d08
Packit Service a04d08
    def remove_option(self, section, option):
Packit Service a04d08
        if self.has_option(section, option):
Packit Service a04d08
            RawConfigParser.remove_option(self, section, option)
Packit Service a04d08
Packit Service a04d08
    def getboolean(self, section, option):
Packit Service a04d08
        if not self.has_option(section, option):
Packit Service a04d08
            return self.DEF_BOOLEAN
Packit Service a04d08
        return RawConfigParser.getboolean(self, section, option)
Packit Service a04d08
Packit Service a04d08
    def getfloat(self, section, option):
Packit Service a04d08
        if not self.has_option(section, option):
Packit Service a04d08
            return self.DEF_FLOAT
Packit Service a04d08
        return RawConfigParser.getfloat(self, section, option)
Packit Service a04d08
Packit Service a04d08
    def getint(self, section, option):
Packit Service a04d08
        if not self.has_option(section, option):
Packit Service a04d08
            return self.DEF_INT
Packit Service a04d08
        return RawConfigParser.getint(self, section, option)
Packit Service a04d08
Packit Service a04d08
    def stringify(self, header=None):
Packit Service a04d08
        contents = ''
Packit Service a04d08
        outputstream = StringIO()
Packit Service a04d08
        self.write(outputstream)
Packit Service a04d08
        outputstream.flush()
Packit Service a04d08
        contents = outputstream.getvalue()
Packit Service a04d08
        if header:
Packit Service a04d08
            contents = '\n'.join([header, contents, ''])
Packit Service a04d08
        return contents
Packit Service a04d08
Packit Service a04d08
# vi: ts=4 expandtab