summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Moser <smoser@ubuntu.com>2011-01-26 18:43:50 +0000
committerScott Moser <smoser@ubuntu.com>2011-01-26 18:43:50 +0000
commitdc5e7116e663d3b5cad165fcac8b3141f8ffbc05 (patch)
treeb180b671e54b4fb8975b6ba2032c53d4228b39b7
parent95d6fa02204799b4ea120faf3c75e216194fdd38 (diff)
downloadvyos-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--ChangeLog4
-rwxr-xr-xcloud-init.py25
-rw-r--r--cloudinit/DataSource.py56
-rw-r--r--cloudinit/DataSourceEc2.py27
-rw-r--r--cloudinit/DataSourceNoCloud.py27
-rw-r--r--cloudinit/DataSourceOVF.py23
-rw-r--r--cloudinit/__init__.py70
-rw-r--r--config/cloud.cfg2
8 files changed, 156 insertions, 78 deletions
diff --git a/ChangeLog b/ChangeLog
index aa17a1a6..18934465 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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