diff options
author | Scott Moser <smoser@ubuntu.com> | 2011-01-26 18:43:50 +0000 |
---|---|---|
committer | Scott Moser <smoser@ubuntu.com> | 2011-01-26 18:43:50 +0000 |
commit | dc5e7116e663d3b5cad165fcac8b3141f8ffbc05 (patch) | |
tree | b180b671e54b4fb8975b6ba2032c53d4228b39b7 | |
parent | 95d6fa02204799b4ea120faf3c75e216194fdd38 (diff) | |
download | vyos-cloud-init-dc5e7116e663d3b5cad165fcac8b3141f8ffbc05.tar.gz vyos-cloud-init-dc5e7116e663d3b5cad165fcac8b3141f8ffbc05.zip |
rework of DataSource loading.
The DataSources that are loaded are now controlled entirely via
configuration file of 'datasource_list', like:
datasource_list: [ "NoCloud", "OVF", "Ec2" ]
Each item in that list is a "DataSourceCollection". for each item
in the list, cloudinit will attempt to load:
cloudinit.DataSource<item>
and, failing that,
DataSource<item>
The module is required to have a method named 'get_datasource_list'
in it that takes a single list of "dependencies" and returns
a list of python classes inside the collection that can run needing
only those dependencies.
The dependencies are defines in DataSource.py. Currently:
DEP_FILESYSTEM = "FILESYSTEM"
DEP_NETWORK = "NETWORK"
When 'get_datasource_list' is called for the DataSourceOVF module with
[DEP_FILESYSTEM], then DataSourceOVF returns a single item list with a
reference to the 'DataSourceOVF' class.
When 'get_datasource_list' is called for the DataSourceOVF module with
[DEP_FILESYSTEM, DEP_NETWORK], it will return a single item list
with a reference to 'DataSourceOVFNet'.
cloudinit will then instanciate the class and call its 'get_data' method.
if the get_data method returns 'True', then it selects this class as the
selected Datasource.
-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 |