diff options
Diffstat (limited to 'cloudinit')
-rw-r--r-- | cloudinit/DataSource.py | 41 | ||||
-rw-r--r-- | cloudinit/DataSourceEc2.py | 51 | ||||
-rw-r--r-- | cloudinit/DataSourceNoCloud.py | 152 | ||||
-rw-r--r-- | cloudinit/__init__.py | 28 | ||||
-rw-r--r-- | cloudinit/util.py | 33 |
5 files changed, 259 insertions, 46 deletions
diff --git a/cloudinit/DataSource.py b/cloudinit/DataSource.py index 858608cc..d1458ffd 100644 --- a/cloudinit/DataSource.py +++ b/cloudinit/DataSource.py @@ -36,7 +36,20 @@ class DataSource: return(self.userdata_raw) def get_public_ssh_keys(self): - return([]) + keys = [] + if not self.metadata.has_key('public-keys'): return([]) + for keyname, klist in self.metadata['public-keys'].items(): + # lp:506332 uec metadata service responds with + # data that makes boto populate a string for 'klist' rather + # than a list. + if isinstance(klist,str): + klist = [ klist ] + for pkey in klist: + # there is an empty string at the end of the keylist, trim it + if pkey: + keys.append(pkey) + + return(keys) def device_name_to_device(self, name): # translate a 'name' to a device @@ -45,3 +58,29 @@ class DataSource: # ephemeral0: sdb # and return 'sdb' for input 'ephemeral0' return(None) + + def get_locale(self): + return('en_US.UTF-8') + + def get_local_mirror(self): + return('http://archive.ubuntu.com/ubuntu/') + + def get_instance_id(self): + if 'instance-id' not in self.metadata: + return "ubuntuhost" + return(self.metadata['instance-id']) + + def get_hostname(self): + if not 'local-hostname' in self.metadata: + return None + + toks = self.metadata['local-hostname'].split('.') + # if there is an ipv4 address in 'local-hostname', then + # make up a hostname (LP: #475354) + if len(toks) == 4: + try: + r = filter(lambda x: int(x) < 256 and x > 0, toks) + if len(r) == 4: + return("ip-%s" % '-'.join(r)) + except: pass + return toks[0] diff --git a/cloudinit/DataSourceEc2.py b/cloudinit/DataSourceEc2.py index cf50a3d5..8a79eb5f 100644 --- a/cloudinit/DataSourceEc2.py +++ b/cloudinit/DataSourceEc2.py @@ -19,12 +19,14 @@ import DataSource import cloudinit +import cloudinit.util as util import socket import urllib2 import time import sys import boto_utils import os.path +import errno class DataSourceEc2(DataSource.DataSource): api_ver = '2009-04-04' @@ -39,21 +41,20 @@ class DataSourceEc2(DataSource.DataSource): def __init__(self): pass + def __str__(self): + return("DataSourceEc2") + def get_data(self): try: - udf = open(self.cachedir + "/user-data.raw") - self.userdata_raw = udf.read() - udf.close() - - mdf = open(self.cachedir + "/meta-data.raw") - data = mdf.read() - self.metadata = eval(data) - mdf.close() - + (md,ud) = util.read_seeded(self.cachedir + "/") + self.userdata_raw = ud + self.metadata = md cloudinit.log.debug("using seeded ec2 cache data in %s" % self.cachedir) return True - except: - pass + except OSError, e: + if e.errno != errno.ENOENT: + cloudinit.log.warn("unexpected error from preseeded data") + raise try: if not self.wait_for_metadata_service(): @@ -81,18 +82,6 @@ class DataSourceEc2(DataSource.DataSource): else: return(self.location_locale_map["default"]) - def get_hostname(self): - toks = self.metadata['local-hostname'].split('.') - # if there is an ipv4 address in 'local-hostname', then - # make up a hostname (LP: #475354) - if len(toks) == 4: - try: - r = filter(lambda x: int(x) < 256 and x > 0, toks) - if len(r) == 4: - return("ip-%s" % '-'.join(r)) - except: pass - return toks[0] - def get_mirror_from_availability_zone(self, availability_zone = None): # availability is like 'us-west-1b' or 'eu-west-1a' if availability_zone == None: @@ -138,22 +127,6 @@ class DataSourceEc2(DataSource.DataSource): int(time.time()-starttime)) return False - def get_public_ssh_keys(self): - keys = [] - if not self.metadata.has_key('public-keys'): return([]) - for keyname, klist in self.metadata['public-keys'].items(): - # lp:506332 uec metadata service responds with - # data that makes boto populate a string for 'klist' rather - # than a list. - if isinstance(klist,str): - klist = [ klist ] - for pkey in klist: - # there is an empty string at the end of the keylist, trim it - if pkey: - keys.append(pkey) - - return(keys) - def device_name_to_device(self, name): # consult metadata service, that has # ephemeral0: sdb diff --git a/cloudinit/DataSourceNoCloud.py b/cloudinit/DataSourceNoCloud.py new file mode 100644 index 00000000..573f3f43 --- /dev/null +++ b/cloudinit/DataSourceNoCloud.py @@ -0,0 +1,152 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# +# Author: Scott Moser <scott.moser@canonical.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/>. + +import DataSource + +import cloudinit +import cloudinit.util as util +import sys +import os.path +import os +import errno + +class DataSourceNoCloud(DataSource.DataSource): + metadata = None + userdata = None + userdata_raw = None + supported_seed_starts = ( "/" , "file://" ) + seed = None + cmdline_id = "ds=nocloud" + seeddir = cloudinit.cachedir + '/nocloud' + + def __init__(self): + pass + + def __str__(self): + mstr="DataSourceNoCloud" + mstr = mstr + " [seed=%s]" % self.seed + return(mstr) + + def get_data(self): + defaults = { + "local-hostname" : "ubuntuhost", + "instance-id" : "nocloud" + } + + md = { } + ud = "" + + try: + # parse the kernel command line, getting data passed in + md = parse_cmdline_data(self.cmdline_id) + except: + util.logexc(cloudinit.log,util.WARN) + return False + + # check to see if the seeddir has data. + try: + (seeddir_md,ud) = util.read_seeded(self.seeddir + "/") + self.metadata = md + md = util.mergedict(md,seeddir_md) + cloudinit.log.debug("using seeded cache data in %s" % self.seeddir) + except OSError, e: + if e.errno != errno.ENOENT: + util.logexc(cloudinit.log,util.WARN) + raise + + # there was no indication on kernel cmdline or data + # in the seeddir suggesting this handler should be used. + if md is None: + return False + + # the special argument "seedfrom" indicates we should + # attempt to seed the userdata / metadata from its value + if "seedfrom" in md: + seedfrom = md["seedfrom"] + found = False + for proto in self.supported_seed_starts: + if seedfrom.startswith(proto): + found=proto + break + if not found: + cloudinit.log.debug("seed from %s not supported by %s" % + (seedfrom, self.__class__)) + return False + + # this could throw errors, but the user told us to do it + # so if errors are raised, let them raise + (md_seed,ud) = util.read_seeded(seedfrom) + cloudinit.log.debug("using seeded cache data from %s" % seedfrom) + + # values in the command line override those from the seed + md = util.mergedict(md,md_seed) + self.seed = seedfrom + + md = util.mergedict(md,defaults) + self.metadata = md; + self.userdata_raw = ud + return True + +# returns a dictionary of key/val or None if cmdline did not have data +# example cmdline: +# root=LABEL=uec-rootfs ro ds=nocloud +def parse_cmdline_data(ds_id,cmdline=None): + if cmdline is None: + if 'DEBUG_PROC_CMDLINE' in os.environ: + cmdline = os.environ["DEBUG_PROC_CMDLINE"] + else: + cmdfp = open("/proc/cmdline") + cmdline = cmdfp.read().strip() + cmdfp.close() + cmdline = " %s " % cmdline.lower() + + if not ( " %s " % ds_id in cmdline or " %s;" % ds_id in cmdline ): + return None + + argline="" + # cmdline can contain: + # ds=nocloud[;key=val;key=val] + for tok in cmdline.split(): + if tok.startswith(ds_id): argline=tok.split("=",1) + + # argline array is now 'nocloud' followed optionally by + # a ';' and then key=value pairs also terminated with ';' + tmp=argline[1].split(";") + if len(tmp) > 1: + kvpairs=tmp[1:] + else: + kvpairs=() + + # short2long mapping to save cmdline typing + s2l = { "h" : "local-hostname", "i" : "instance-id", "s" : "seedfrom" } + cfg = { } + for item in kvpairs: + try: + (k,v) = item.split("=",1) + except: + k=item + v=None + if k in s2l: k=s2l[k] + cfg[k]=v + + return(cfg) + +class DataSourceNoCloudNet(DataSourceNoCloud): + cmdline_id = "ds=nocloud-net" + supported_seed_starts = ( "http://", "https://", "ftp://" ) + seeddir = cloudinit.cachedir + '/nocloud-net' diff --git a/cloudinit/__init__.py b/cloudinit/__init__.py index a5df3436..830bc17b 100644 --- a/cloudinit/__init__.py +++ b/cloudinit/__init__.py @@ -159,20 +159,27 @@ def logging_set_from_cfg(cfg, logfile=None): logging.config.fileConfig(StringIO.StringIO(failsafe)) import DataSourceEc2 +import DataSourceNoCloud import UserDataHandler class CloudInit: datasource_map = { "ec2" : DataSourceEc2.DataSourceEc2, + "nocloud" : DataSourceNoCloud.DataSourceNoCloud, + "nocloud-net" : DataSourceNoCloud.DataSourceNoCloudNet } datasource = None - auto_order = [ 'ec2' ] + auto_orders = { + "all": ( "nocloud-net", "ec2" ), + "local" : ( "nocloud", ), + } cfg = None part_handlers = { } old_conffile = '/etc/ec2-init/ec2-config.cfg' + source_type = "all" - def __init__(self, sysconfig=system_config): + def __init__(self, source_type = "all", sysconfig=system_config): self.part_handlers = { 'text/x-shellscript' : self.handle_user_script, 'text/cloud-config' : self.handle_cloud_config, @@ -182,6 +189,7 @@ class CloudInit: } self.sysconfig=sysconfig self.cfg=self.read_cfg() + self.source_type = source_type def read_cfg(self): if self.cfg: @@ -232,18 +240,21 @@ class CloudInit: if self.datasource is not None: return True if self.restore_from_cache(): + log.debug("restored from cache type %s" % self.datasource) return True dslist=[ ] cfglist=self.cfg['cloud_type'] if cfglist == "auto": - dslist = self.auto_order + dslist = self.auto_orders[self.source_type] elif cfglist: for ds in cfglist.split(','): dslist.append(strip(ds).tolower()) for ds in dslist: - if ds not in self.datasource_map: continue + if ds not in self.datasource_map: + log.warn("data source %s not found in map" % ds) + continue try: s = self.datasource_map[ds]() if s.get_data(): @@ -252,9 +263,11 @@ class CloudInit: log.debug("found data source %s" % ds) return True except Exception as e: + log.warn("get_data of %s raised %s" % (ds,e)) + util.logexc(log) pass - log.critical("Could not find data source") - raise Exception("Could not find data source") + log.debug("did not find data source from %s" % dslist) + raise DataSourceNotFoundException("Could not find data source") def get_userdata(self): return(self.datasource.get_userdata()) @@ -475,3 +488,6 @@ def purge_cache(): except: return(False) return(True) + +class DataSourceNotFoundException(Exception): + pass diff --git a/cloudinit/util.py b/cloudinit/util.py index 7f5c1db4..137921ed 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -21,6 +21,13 @@ import errno import subprocess from Cheetah.Template import Template import cloudinit +import urllib2 +import logging +import traceback + +WARN = logging.WARN +DEBUG = logging.DEBUG +INFO = logging.INFO def read_conf(fname): try: @@ -114,3 +121,29 @@ def render_to_file(template, outfile, searchList): f.write(t.respond()) f.close() +# raise OSError with enoent if not found +def read_seeded(base="", ext=".raw", timeout=2): + if base.startswith("/"): + base="file://%s" % base + + ud_url = "%s%s%s" % (base, "user-data", ext) + md_url = "%s%s%s" % (base, "meta-data", ext) + + try: + md_resp = urllib2.urlopen(urllib2.Request(md_url), timeout=timeout) + ud_resp = urllib2.urlopen(urllib2.Request(ud_url), timeout=timeout) + + md_str = md_resp.read() + ud = ud_resp.read() + md = yaml.load(md_str) + + return(md,ud) + except urllib2.HTTPError: + raise + except urllib2.URLError, e: + if isinstance(e.reason,OSError) and e.reason.errno == errno.ENOENT: + raise e.reason + raise e + +def logexc(log,lvl=logging.DEBUG): + log.log(lvl,traceback.format_exc()) |