summaryrefslogtreecommitdiff
path: root/cloudinit/cloud.py
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit/cloud.py')
-rw-r--r--cloudinit/cloud.py199
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)