diff options
-rw-r--r-- | cloudinit/cloud.py | 199 |
1 files changed, 144 insertions, 55 deletions
diff --git a/cloudinit/cloud.py b/cloudinit/cloud.py index f9c0d531..80d4f1ce 100644 --- a/cloudinit/cloud.py +++ b/cloudinit/cloud.py @@ -1,34 +1,57 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2012 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# Copyright (C) 2012 Yahoo! Inc. +# +# Author: Scott Moser <scott.moser@canonical.com> +# Author: Juerg Haefliger <juerg.haefliger@hp.com> +# Author: Joshua Harlow <harlowja@yahoo-inc.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + from time import time import cPickle as pickle + import contextlib import copy import os import sys import weakref -from cloudinit.settings import (PER_INSTANCE, PER_ALWAYS, - OLD_CLOUD_CONFIG, CLOUD_CONFIG, - CUR_INSTANCE_LINK) +from cloudinit.settings import (PER_INSTANCE, PER_ALWAYS) +from cloudinit.settings import (OLD_CLOUD_CONFIG, CLOUD_CONFIG) + from cloudinit import (get_builtin_cfg, get_base_cfg) from cloudinit import log as logging from cloudinit import sources from cloudinit import util -from cloudinit import user_data from cloudinit import handlers -from cloudinit import parts -from cloudinit.parts import boot_hook as bh_part -from cloudinit.parts import cloud_config as cc_part -from cloudinit.parts import upstart_job as up_part -from cloudinit.parts import shell_script as ss_part +from cloudinit import user_data as ud +from cloudinit.user_data import boot_hook as bh_part +from cloudinit.user_data import cloud_config as cc_part +from cloudinit.user_data import processor as ud_proc +from cloudinit.user_data import shell_script as ss_part +from cloudinit.user_data import upstart_job as up_part LOG = logging.getLogger(__name__) class CloudSemaphores(object): - def __init__(self, paths): - self.paths = paths + def __init__(self, sem_path): + self.sem_path = sem_path # acquire lock on 'name' for given 'freq' and run function 'func' # if 'clear_on_fail' is True and 'func' throws an exception @@ -71,7 +94,7 @@ class CloudSemaphores(object): # here, but this should be ok due to the nature of when # and where cloud-init runs... (file writing is not a lock..) sem_file = self._getpath(name, freq) - contents = "%s\n" % str(time()) + contents = "%s: %s\n" % (os.getpid(), time()) try: util.write_file(sem_file, contents) except (IOError, OSError): @@ -87,28 +110,37 @@ class CloudSemaphores(object): return False def _get_path(self, name, freq): - sem_path = self.init.get_ipath("sem") + sem_path = self.sem_path if freq == PER_INSTANCE: return os.path.join(sem_path, name) return os.path.join(sem_path, "%s.%s" % (name, freq)) class CloudPaths(object): - def __init__(self, init): - self.config = CLOUD_CONFIG - self.old_config = OLD_CLOUD_CONFIG - self.var_dir = VAR_LIB_DIR - self.instance_link = CUR_INSTANCE_LINK - self.init = weakref.proxy(init) - self.upstart_conf_d = "/etc/init" - - def _get_path_key(self, name): - return PATH_MAP.get(name) + def __init__(self, sys_info): + self.cloud_dir = sys_info['cloud_dir'] + self.instance_link = os.path.join(self.cloud_dir, 'instance') + self.boot_finished = os.path.join(self.instance_link, "boot-finished") + self.upstart_conf_d = sys_info.get('upstart_dir') + self.template_dir = sys_info['templates_dir'] + self.seed_dir = os.path.join(self.cloud_dir, 'seed') + self.datasource = None + self.lookups = { + "handlers": "handlers", + "scripts": "scripts", + "sem": "sem", + "boothooks": "boothooks", + "userdata_raw": "user-data.txt", + "userdata": "user-data.txt.i", + "obj_pkl": "obj.pkl", + "cloud_config": "cloud-config.txt", + "data": "data", + } # get_ipath_cur: get the current instance path for an item def get_ipath_cur(self, name=None): - add_on = self._get_path_key(name) - ipath = os.path.join(self.var_dir, 'instance') + ipath = os.path.join(self.cloud_dir, 'instance') + add_on = self.lookups.get(name) if add_on: ipath = os.path.join(ipath, add_on) return ipath @@ -117,7 +149,7 @@ class CloudPaths(object): # for a name in dirmap def get_cpath(self, name=None): cpath = self.var_dir - add_on = self._get_path_key(name) + add_on = self.lookups.get(name) if add_on: cpath = os.path.join(cpath, add_on) return cpath @@ -125,18 +157,21 @@ class CloudPaths(object): # get_ipath : get the instance path for a name in pathmap # (/var/lib/cloud/instances/<instance>/<name>) def get_ipath(self, name=None): - iid = self.init.datasource.get_instance_id() - ipath = os.path.join(self.var_dir, 'instances', iid) - add_on = self._get_path_key(name) + if not self.datasource: + raise RuntimeError("Unable to get instance path, datasource not available/set.") + iid = self.datasource.get_instance_id() + ipath = os.path.join(self.cloud_dir, 'instances', iid) + add_on = self.lookups.get(name) if add_on: ipath = os.path.join(ipath, add_on) return ipath class CloudSimple(object): - def __init__(self, init): + def __init__(self, ci): self.datasource = init.datasource self.paths = init.paths + self.cfg = copy.deepcopy(ci.cfg) def get_userdata(self): return self.datasource.get_userdata() @@ -173,8 +208,8 @@ class CloudInit(object): self.ds_deps = ds_deps else: self.ds_deps = [sources.DEP_FILESYSTEM, sources.DEP_NETWORK] - self.paths = CloudPaths(self) self.cfg = self._read_cfg() + self.paths = CloudPaths(self.cfg['system_info']) def _read_cfg_old(self): # support reading the old ConfigObj format file and merging @@ -185,56 +220,110 @@ class CloudInit(object): ConfigObj = None if not ConfigObj: return {} - old_cfg = ConfigObj(self.paths.old_config_fn) + old_cfg = ConfigObj(OLD_CLOUD_CONFIG) return dict(old_cfg) - def read_cfg(self): - if not self.cfg: - self.cfg = self._read_cfg() - return self.cfg + def _initial_subdirs(self): + c_dir = self.paths.cloud_dir + initial_dirs = [ + os.path.join(c_dir, 'scripts'), + os.path.join(c_dir, 'scripts', 'per-instance'), + os.path.join(c_dir, 'scripts', 'per-once'), + os.path.join(c_dir, 'scripts', 'per-boot'), + os.path.join(c_dir, 'seed'), + os.path.join(c_dir, 'instances'), + os.path.join(c_dir, 'handlers'), + os.path.join(c_dir, 'sem'), + os.path.join(c_dir, 'data'), + ] + return initial_dirs + + def purge_cache(self, rmcur=True): + rmlist = [] + rmlist.append(self.paths.boot_finished) + if rmcur: + rmlist.append(self.paths.instance_link) + for f in rmlist: + util.unlink(f) + return len(rmlist) + + def init_fs(self): + util.ensure_dirs(self._initial_subdirs()) + log_file = util.get_cfg_option_str(self.cfg, 'def_log_file', None) + perms = util.get_cfg_option_str(self.cfg, 'syslog_fix_perms', None) + if log_file: + util.ensure_file(log_file) + if perms: + (u, g) = perms.split(':', 1) + if u == "-1" or u == "None": + u = None + if g == "-1" or g == "None": + g = None + util.chownbyname(log_file, u, g) def _read_cfg(self): starting_config = get_builtin_cfg() try: - conf = get_base_cfg(self.paths.config, starting_config) + conf = get_base_cfg(CLOUD_CONFIG, starting_config) except Exception: conf = starting_config old_conf = self._read_cfg_old() conf = util.mergedict(conf, old_conf) return conf - def restore_from_cache(self): + def _restore_from_cache(self): pickled_fn = self.paths.get_ipath_cur('obj_pkl') try: # we try to restore from a current link and static path # by using the instance link, if purge_cache was called # the file wont exist - self.datasource = pickle.loads(util.load_file(pickled_fn)) - return True + return pickle.loads(util.load_file(pickled_fn)) except Exception as e: LOG.debug("Failed loading pickled datasource from %s due to %s", pickled_fn, e) return False - + def write_to_cache(self): pickled_fn = self.paths.get_ipath_cur("obj_pkl") try: contents = pickle.dumps(self.datasource) util.write_file(pickled_fn, contents, mode=0400) except Exception as e: - LOG.debug("Failed pickling datasource to %s due to %s", pickled_fn, e) + LOG.debug("Failed pickling datasource to %s due to: %s", pickled_fn, e) return False - + + def _get_processor(self): + return ud_proc.UserDataProcessor(self.paths) + + def _get_datasources(self): + # Any config provided??? + pkg_list = self.cfg.get('datasource_pkg_list') or [] + # Add the defaults at the end + for n in [util.obj_name(sources), '']: + if n not in pkg_list: + pkg_list.append(n) + cfg_list = self.cfg.get('datasource_list') or [] + return (cfg_list, pkg_list) + def get_data_source(self): if self.datasource: return True - if self.restore_from_cache(): - LOG.debug("Restored from cache datasource: %s" % self.datasource) - return True - (ds, dsname) = sources.find_source(self.cfg, self.ds_deps) - LOG.debug("Loaded datasource %s:%s", dsname, ds) + ds = self._restore_from_cache() + if ds: + LOG.debug("Restored from cache datasource: %s" % ds) + else: + (cfg_list, pkg_list) = self._get_datasources() + ud_proc = self._get_processor() + (ds, dsname) = sources.find_source(self.cfg, + self.ds_deps, + cfg_list=cfg_list, + pkg_list=pkg_list, + ud_proc=ud_proc) + LOG.debug("Loaded datasource %s - %s", dsname, ds) self.datasource = ds + # This allows the paths obj to have an ipath function that works + self.paths.datasource = ds return True - + def set_cur_instance(self): # Ensure we are hooked into the right symlink for the current instance idir = self.paths.get_ipath() @@ -299,7 +388,7 @@ class CloudInit(object): handlers = CloudHandlers(self) # Add handlers in cdir - potential_handlers = parts.find_module_files(cdir) + potential_handlers = utils.find_modules(cdir) for (fname, modname) in potential_handlers.iteritems(): try: mod = parts.fixup_module(importer.import_module(modname)) @@ -329,7 +418,7 @@ class CloudInit(object): 'frequency': frequency, 'handlercount': 0, } - user_data.walk(data.get_userdata(), parts.walker_callback, data=part_data) + ud.walk(data.get_userdata(), parts.walker_callback, data=part_data) # Give callbacks opportunity to finalize called = [] @@ -394,11 +483,11 @@ class CloudHandlers(object): class CloudConfig(object): - def __init__(self, cfgfile, cloud): - self.cloud = CloudSimple(cloud) + self.cloud = cloud self.cfg = self._get_config(cfgfile) - self.sems = CloudSemaphores(self.cloud.paths) + self.paths = cloud.paths + self.sems = CloudSemaphores(self.paths.get_ipath("sem")) def _get_config(self, cfgfile): cfg = None @@ -433,5 +522,5 @@ class CloudConfig(object): if not freq: freq = def_freq c_name = "config-%s" % (name) - real_args = [name, copy.deepcopy(self.cfg), self.cloud, LOG, copy.deepcopy(args)] + real_args = [name, copy.deepcopy(self.cfg), CloudSimple(self.cloud), LOG, copy.deepcopy(args)] return self.sems.run_functor(c_name, freq, mod.handle, real_args) |