diff options
| -rw-r--r-- | ChangeLog | 4 | ||||
| -rwxr-xr-x | cloud-init.py | 25 | ||||
| -rw-r--r-- | cloudinit/DataSource.py | 56 | ||||
| -rw-r--r-- | cloudinit/DataSourceEc2.py | 27 | ||||
| -rw-r--r-- | cloudinit/DataSourceNoCloud.py | 27 | ||||
| -rw-r--r-- | cloudinit/DataSourceOVF.py | 23 | ||||
| -rw-r--r-- | cloudinit/__init__.py | 70 | ||||
| -rw-r--r-- | config/cloud.cfg | 2 | 
8 files changed, 156 insertions, 78 deletions
@@ -31,3 +31,7 @@     via the config file, or user data config file   - add support for posting data about the instance to a url (phone_home)   - add minimal OVF transport (iso) support + - make DataSources that are attempted dynamic and configurable from +   system config. changen "cloud_type: auto" as configuration for this +   to 'datasource_list: [ "Ec2" ]'.  Each of the items in that list +   must be modules that can be loaded by "DataSource<item>" diff --git a/cloud-init.py b/cloud-init.py index 92d8a091..0902d966 100755 --- a/cloud-init.py +++ b/cloud-init.py @@ -23,6 +23,7 @@ import sys  import cloudinit  import cloudinit.util as util  import cloudinit.CloudConfig as CC +import cloudinit.DataSource as ds  import time  import logging  import errno @@ -32,10 +33,19 @@ def warn(wstr):  def main():      cmds = ( "start", "start-local" ) +    deps = { "start" : ( ds.DEP_FILESYSTEM, ds.DEP_NETWORK ), +             "start-local" : ( ds.DEP_FILESYSTEM, ) } +      cmd = ""      if len(sys.argv) > 1:          cmd = sys.argv[1] +    cfg_path = None +    if len(sys.argv) > 2: +        # this is really for debugging only +        # but you can invoke on development system with ./config/cloud.cfg +        cfg_path = sys.argv[2] +      if not cmd in cmds:          sys.stderr.write("bad command %s. use one of %s\n" % (cmd, cmds))          sys.exit(1) @@ -49,12 +59,17 @@ def main():         warn("unable to open /proc/uptime\n")         uptime = "na" -    source_type = "all" -    if cmd == "start-local": -        source_type = "local" +    try: +        cfg = cloudinit.get_base_cfg(cfg_path) +    except Exception as e: +        warn("Failed to get base config. falling back to builtin: %s\n" % e) +        try: +            cfg = cloudinit.get_builtin_cfg() +        except Exception as e: +            warn("Unable to load builtin config\n") +            raise      try: -        cfg = cloudinit.get_base_cfg()          (outfmt, errfmt) = CC.get_output_cfg(cfg,"init")          CC.redirect_output(outfmt, errfmt)      except Exception as e: @@ -80,7 +95,7 @@ def main():      if cmd == "start-local":          cloudinit.purge_cache() -    cloud = cloudinit.CloudInit(source_type=source_type) +    cloud = cloudinit.CloudInit(ds_deps=deps[cmd])      try:          cloud.get_data_source() diff --git a/cloudinit/DataSource.py b/cloudinit/DataSource.py index 316eb5ae..21404ecc 100644 --- a/cloudinit/DataSource.py +++ b/cloudinit/DataSource.py @@ -16,16 +16,23 @@  #    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 cloudinit + +DEP_FILESYSTEM = "FILESYSTEM" +DEP_NETWORK = "NETWORK" +  import UserDataHandler as ud  class DataSource:      userdata = None      metadata = None      userdata_raw = None +    log = None -    def __init__(self): -       pass +    def __init__(self, log=None): +        if not log: +            import logging +            log = logging.log +        self.log = log      def get_userdata(self):          if self.userdata == None: @@ -91,3 +98,46 @@ class DataSource:                      return("ip-%s" % '-'.join(r))              except: pass          return toks[0] + +# return a list of classes that have the same depends as 'depends' +# iterate through cfg_list, loading "DataSourceCollections" modules +# and calling their "get_datasource_list". +# return an ordered list of classes that match +# +# - modules must be named "DataSource<item>", where 'item' is an entry +#   in cfg_list +# - if pkglist is given, it will iterate try loading from that package +#   ie, pkglist=[ "foo", "" ] +#     will first try to load foo.DataSource<item> +#     then DataSource<item> +def list_sources(cfg_list, depends, pkglist=[]): +    retlist = [] +    for ds_coll in cfg_list: +        for pkg in pkglist: +            if pkg: pkg="%s." % pkg +            try: +                mod = __import__("%sDataSource%s" % (pkg, ds_coll)) +                if pkg: +                    mod = getattr(mod, "DataSource%s" % ds_coll) +                lister = getattr(mod, "get_datasource_list") +                retlist.extend(lister(depends)) +                break +            except: +                raise +    return(retlist) + +# depends is a list of dependencies (DEP_FILESYSTEM) +# dslist is a list of 2 item lists +# dslist = [  +#   ( class, ( depends-that-this-class-needs ) ) +# } +# it returns a list of 'class' that matched these deps exactly +# it is a helper function for DataSourceCollections +def list_from_depends(depends, dslist): +    retlist = [ ] +    depset = set(depends) +    for elem in dslist: +        (cls, deps) = elem +        if depset == set(deps): +            retlist.append(cls) +    return(retlist) diff --git a/cloudinit/DataSourceEc2.py b/cloudinit/DataSourceEc2.py index 8ecb28ad..183c57b8 100644 --- a/cloudinit/DataSourceEc2.py +++ b/cloudinit/DataSourceEc2.py @@ -18,7 +18,7 @@  import DataSource -import cloudinit +from cloudinit import seeddir  import cloudinit.util as util  import socket  import urllib2 @@ -30,10 +30,7 @@ import errno  class DataSourceEc2(DataSource.DataSource):      api_ver  = '2009-04-04' -    seeddir = cloudinit.seeddir + '/ec2' - -    def __init__(self): -        pass +    seeddir = seeddir + '/ec2'      def __str__(self):          return("DataSourceEc2") @@ -43,7 +40,7 @@ class DataSourceEc2(DataSource.DataSource):          if util.read_optional_seed(seedret,base=self.seeddir+ "/"):              self.userdata_raw = seedret['user-data']              self.metadata = seedret['meta-data'] -            cloudinit.log.debug("using seeded ec2 data in %s" % self.seeddir) +            self.log.debug("using seeded ec2 data in %s" % self.seeddir)              return True          try: @@ -105,13 +102,13 @@ class DataSourceEc2(DataSource.DataSource):                  reason = "url error [%s]" % e.reason              if x == 0: -                cloudinit.log.warning("waiting for metadata service at %s\n" % url) +                self.log.warning("waiting for metadata service at %s\n" % url) -            cloudinit.log.warning("  %s [%02s/%s]: %s\n" % +            self.log.warning("  %s [%02s/%s]: %s\n" %                  (time.strftime("%H:%M:%S",time.gmtime()), x+1, sleeps, reason))              time.sleep(sleeptime) -        cloudinit.log.critical("giving up on md after %i seconds\n" % +        self.log.critical("giving up on md after %i seconds\n" %                    int(time.time()-starttime))          return False @@ -131,7 +128,7 @@ class DataSourceEc2(DataSource.DataSource):              if entname == "ephemeral" and name == "ephemeral0":                  found = device          if found == None: -            cloudinit.log.warn("unable to convert %s to a device" % name) +            self.log.warn("unable to convert %s to a device" % name)              return None          # LP: #611137 @@ -154,7 +151,7 @@ class DataSourceEc2(DataSource.DataSource):              for nto in tlist:                  cand = "/dev/%s%s" % (nto, short[len(nfrom):])                  if os.path.exists(cand): -                    cloudinit.log.debug("remapped device name %s => %s" % (found,cand)) +                    self.log.debug("remapped device name %s => %s" % (found,cand))                      return(cand)          return ofound @@ -165,3 +162,11 @@ class DataSourceEc2(DataSource.DataSource):              (p4 not in self.metadata or self.metadata[p4] == "")):              return True          return False + +datasources = [  +  ( DataSourceEc2, ( DataSource.DEP_FILESYSTEM , DataSource.DEP_NETWORK ) ), +] + +# return a list of data sources that match this set of dependencies +def get_datasource_list(depends): +    return(DataSource.list_from_depends(depends, datasources)) diff --git a/cloudinit/DataSourceNoCloud.py b/cloudinit/DataSourceNoCloud.py index 2f6033a9..6403d879 100644 --- a/cloudinit/DataSourceNoCloud.py +++ b/cloudinit/DataSourceNoCloud.py @@ -18,7 +18,7 @@  import DataSource -import cloudinit +from cloudinit import seeddir  import cloudinit.util as util  import sys  import os.path @@ -32,10 +32,7 @@ class DataSourceNoCloud(DataSource.DataSource):      supported_seed_starts = ( "/" , "file://" )      seed = None      cmdline_id = "ds=nocloud" -    seeddir = cloudinit.seeddir + '/nocloud' - -    def __init__(self): -        pass +    seeddir = seeddir + '/nocloud'      def __str__(self):          mstr="DataSourceNoCloud" @@ -57,7 +54,7 @@ class DataSourceNoCloud(DataSource.DataSource):              if parse_cmdline_data(self.cmdline_id, md):                  found.append("cmdline")          except: -            util.logexc(cloudinit.log,util.WARN) +            util.logexc(self.log,util.WARN)              return False          # check to see if the seeddir has data. @@ -66,7 +63,7 @@ class DataSourceNoCloud(DataSource.DataSource):              md = util.mergedict(md,seedret['meta-data'])              ud = seedret['user-data']              found.append(self.seeddir) -            cloudinit.log.debug("using seeded cache data in %s" % self.seeddir) +            self.log.debug("using seeded cache data in %s" % self.seeddir)          # there was no indication on kernel cmdline or data          # in the seeddir suggesting this handler should be used. @@ -83,14 +80,14 @@ class DataSourceNoCloud(DataSource.DataSource):                      seedfound=proto                      break              if not seedfound: -                cloudinit.log.debug("seed from %s not supported by %s" % +                self.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) +            self.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) @@ -143,4 +140,14 @@ def parse_cmdline_data(ds_id,fill,cmdline=None):  class DataSourceNoCloudNet(DataSourceNoCloud):      cmdline_id = "ds=nocloud-net"      supported_seed_starts = ( "http://", "https://", "ftp://" ) -    seeddir = cloudinit.seeddir + '/nocloud-net' +    seeddir = seeddir + '/nocloud-net' + +datasources = ( +  ( DataSourceNoCloud, ( DataSource.DEP_FILESYSTEM, ) ), +  ( DataSourceNoCloudNet,  +    ( DataSource.DEP_FILESYSTEM, DataSource.DEP_NETWORK ) ), +) + +# return a list of data sources that match this set of dependencies +def get_datasource_list(depends): +    return(DataSource.list_from_depends(depends, datasources)) diff --git a/cloudinit/DataSourceOVF.py b/cloudinit/DataSourceOVF.py index 3fba878b..31d0c407 100644 --- a/cloudinit/DataSourceOVF.py +++ b/cloudinit/DataSourceOVF.py @@ -18,7 +18,7 @@  import DataSource -import cloudinit +from cloudinit import seeddir  import cloudinit.util as util  import sys  import os.path @@ -33,16 +33,13 @@ import subprocess  class DataSourceOVF(DataSource.DataSource):      seed = None -    seeddir = cloudinit.seeddir + '/ovf' +    seeddir = seeddir + '/ovf'      environment = None      cfg = { }      userdata_raw = None      metadata = None      supported_seed_starts = ( "/" , "file://" ) -    def __init__(self): -        pass -      def __str__(self):          mstr="DataSourceOVF"          mstr = mstr + " [seed=%s]" % self.seed @@ -90,12 +87,12 @@ class DataSourceOVF(DataSource.DataSource):                      seedfound = proto                      break              if not seedfound: -                cloudinit.log.debug("seed from %s not supported by %s" % +                self.log.debug("seed from %s not supported by %s" %                      (seedfrom, self.__class__))                  return False              (md_seed,ud) = util.read_seeded(seedfrom) -            cloudinit.log.debug("using seeded cache data from %s" % seedfrom) +            self.log.debug("using seeded cache data from %s" % seedfrom)              md = util.mergedict(md,md_seed)              found.append(seedfrom) @@ -122,7 +119,7 @@ class DataSourceOVF(DataSource.DataSource):          return(self.cfg)  class DataSourceOVFNet(DataSourceOVF): -    seeddir = cloudinit.seeddir + '/ovf-net' +    seeddir = seeddir + '/ovf-net'      supported_seed_starts = ( "http://", "https://", "ftp://" )  # this will return a dict with some content @@ -283,6 +280,16 @@ def getProperties(environString):      return(props) +datasources = ( +  ( DataSourceOVF, ( DataSource.DEP_FILESYSTEM, ) ), +  ( DataSourceOVFNet,  +    ( DataSource.DEP_FILESYSTEM, DataSource.DEP_NETWORK ) ), +) + +# return a list of data sources that match this set of dependencies +def get_datasource_list(depends): +    return(DataSource.list_from_depends(depends, datasources)) +  if __name__ == "__main__":      import sys      envStr = open(sys.argv[1]).read() diff --git a/cloudinit/__init__.py b/cloudinit/__init__.py index 7e60ee13..034e4bd6 100644 --- a/cloudinit/__init__.py +++ b/cloudinit/__init__.py @@ -27,7 +27,7 @@ cfg_env_name = "CLOUD_CFG"  cfg_builtin = """  log_cfgs: [ ] -cloud_type: auto +datasource_list: [ "NoCloud", "OVF", "Ec2" ]  def_log_file: /var/log/cloud-init.log  syslog_fix_perms: syslog:adm  """ @@ -101,29 +101,17 @@ def logging_set_from_cfg(cfg):      raise Exception("no valid logging found\n") -import DataSourceEc2 -import DataSourceNoCloud -import DataSourceOVF +import DataSource  import UserDataHandler  class CloudInit: -    datasource_map = { -        "ec2" : DataSourceEc2.DataSourceEc2, -        "nocloud" : DataSourceNoCloud.DataSourceNoCloud, -        "nocloud-net" : DataSourceNoCloud.DataSourceNoCloudNet, -        "ovf" : DataSourceOVF.DataSourceOVF, -    } -    datasource = None -    auto_orders = { -        "all": ( "nocloud-net", "ec2" ), -        "local" : ( "nocloud", "ovf" ), -    }      cfg = None      part_handlers = { }      old_conffile = '/etc/ec2-init/ec2-config.cfg' -    source_type = "all" +    ds_deps = [ DataSource.DEP_FILESYSTEM, DataSource.DEP_NETWORK ] +    datasource = None -    def __init__(self, source_type = "all", sysconfig=system_config): +    def __init__(self, ds_deps = None, sysconfig=system_config):          self.part_handlers = {              'text/x-shellscript' : self.handle_user_script,              'text/cloud-config' : self.handle_cloud_config, @@ -131,15 +119,19 @@ class CloudInit:              'text/part-handler' : self.handle_handler,              'text/cloud-boothook' : self.handle_cloud_boothook          } +        if ds_deps != None: +            self.ds_deps = ds_deps          self.sysconfig=sysconfig          self.cfg=self.read_cfg() -        self.source_type = source_type      def read_cfg(self):          if self.cfg:              return(self.cfg) -        conf = util.get_base_cfg(self.sysconfig,cfg_builtin, parsed_cfgs) +        try: +            conf = util.get_base_cfg(self.sysconfig,cfg_builtin, parsed_cfgs) +        except Exception as e: +            conf = get_builtin_cfg()          # support reading the old ConfigObj format file and merging          # it into the yaml dictionary @@ -182,9 +174,6 @@ class CloudInit:          except:              return False -    def get_cloud_type(self): -        pass -      def get_data_source(self):          if self.datasource is not None: return True @@ -192,21 +181,14 @@ class CloudInit:              log.debug("restored from cache type %s" % self.datasource)              return True -        dslist=[ ] -        cfglist=self.cfg['cloud_type'] -        if cfglist == "auto": -            dslist = self.auto_orders[self.source_type] -        elif cfglist: -            for ds in cfglist.split(','): -                dslist.append(strip(ds).tolower()) -             -        log.debug("searching for data source in [%s]" % str(dslist)) -        for ds in dslist: -            if ds not in self.datasource_map: -                log.warn("data source %s not found in map" % ds) -                continue +        cfglist=self.cfg['datasource_list'] +        dslist = list_sources(cfglist, self.ds_deps) +        dsnames = map(lambda f: f.__name__, dslist) +        log.debug("searching for data source in %s" % dsnames) +        for cls in dslist: +            ds = cls.__name__              try: -                s = self.datasource_map[ds]() +                s = cls(log)                  if s.get_data():                      self.datasource = s                      self.datasource_name = ds @@ -216,8 +198,9 @@ class CloudInit:                  log.warn("get_data of %s raised %s" % (ds,e))                  util.logexc(log)                  pass -        log.debug("did not find data source from %s" % dslist) -        raise DataSourceNotFoundException("Could not find data source") +        msg = "Did not find data source. searched classes: %s" % dsnames +        log.debug(msg) +        raise DataSourceNotFoundException(msg)      def set_cur_instance(self):          try: @@ -532,8 +515,15 @@ def get_ipath_cur(name=None):  def get_cpath(name=None):      return("%s%s" % (varlibdir, pathmap[name])) -def get_base_cfg(): -    return(util.get_base_cfg(system_config,cfg_builtin,parsed_cfgs)) +def get_base_cfg(cfg_path=None): +    if cfg_path is None: cfg_path = system_config +    return(util.get_base_cfg(cfg_path,cfg_builtin,parsed_cfgs)) + +def get_builtin_cfg(): +    return(yaml.load(cfg_builtin))  class DataSourceNotFoundException(Exception):      pass + +def list_sources(cfg_list, depends): +    return(DataSource.list_sources(cfg_list,depends, ["cloudinit", "" ])) diff --git a/config/cloud.cfg b/config/cloud.cfg index 2aa574c7..5b5893a9 100644 --- a/config/cloud.cfg +++ b/config/cloud.cfg @@ -1,7 +1,7 @@ -cloud: auto  user: ubuntu  disable_root: 1  preserve_hostname: False +# datasource_list: [ "NoCloud", "OVF", "Ec2" ]  cloud_init_modules:   - resizefs  | 
