summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog6
-rwxr-xr-xbin/cloud-init135
-rw-r--r--cloudinit/config/cc_seed_random.py47
-rw-r--r--cloudinit/distros/freebsd.py2
-rw-r--r--cloudinit/netinfo.py17
-rw-r--r--cloudinit/sources/DataSourceAltCloud.py6
-rw-r--r--cloudinit/sources/DataSourceCloudSigma.py5
-rw-r--r--cloudinit/sources/DataSourceConfigDrive.py2
-rw-r--r--cloudinit/sources/DataSourceNoCloud.py7
-rw-r--r--cloudinit/sources/DataSourceOpenStack.py12
-rw-r--r--cloudinit/sources/DataSourceSmartOS.py11
-rw-r--r--cloudinit/sources/helpers/__init__.py1
-rw-r--r--cloudinit/sources/helpers/openstack.py21
-rw-r--r--cloudinit/util.py4
-rw-r--r--doc/status.txt53
-rw-r--r--tests/unittests/test_datasource/test_altcloud.py24
-rw-r--r--tests/unittests/test_datasource/test_azure.py4
-rw-r--r--tests/unittests/test_datasource/test_configdrive.py2
-rw-r--r--tests/unittests/test_datasource/test_nocloud.py1
-rw-r--r--tests/unittests/test_datasource/test_smartos.py33
-rw-r--r--tests/unittests/test_handler/test_handler_growpart.py3
-rw-r--r--tests/unittests/test_handler/test_handler_seed_random.py67
-rwxr-xr-xtools/make-tarball8
23 files changed, 383 insertions, 88 deletions
diff --git a/ChangeLog b/ChangeLog
index dcf891d7..c7d42db4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -31,6 +31,12 @@
- Add GCE datasource [Vaidas Jablonskis]
- Add native Openstack datasource which reads openstack metadata
rather than relying on EC2 data in openstack metadata service.
+ - SmartOS, AltCloud: disable running on arm systems due to bug
+ (LP: #1243287, #1285686) [Oleg Strikov]
+ - Allow running a command to seed random, default is 'pollinate -q'
+ (LP: #1286316) [Dustin Kirkland]
+ - Write status to /run/cloud-init/status.json for consumption by
+ other programs (LP: #1284439)
0.7.4:
- fix issue mounting 'ephemeral0' if ephemeral0 was an alias for a
partitioned block device with target filesystem on ephemeral0.1.
diff --git a/bin/cloud-init b/bin/cloud-init
index 80a1df05..6ede60af 100755
--- a/bin/cloud-init
+++ b/bin/cloud-init
@@ -22,8 +22,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import argparse
+import json
import os
import sys
+import time
+import tempfile
import traceback
# This is more just for running from the bin folder so that
@@ -126,11 +129,11 @@ def run_module_section(mods, action_name, section):
" under section '%s'") % (action_name, full_section_name)
sys.stderr.write("%s\n" % (msg))
LOG.debug(msg)
- return 0
+ return []
else:
LOG.debug("Ran %s modules with %s failures",
len(which_ran), len(failures))
- return len(failures)
+ return failures
def main_init(name, args):
@@ -220,7 +223,7 @@ def main_init(name, args):
if existing_files:
LOG.debug("Exiting early due to the existence of %s files",
existing_files)
- return 0
+ return (None, [])
else:
# The cache is not instance specific, so it has to be purged
# but we want 'start' to benefit from a cache if
@@ -249,9 +252,9 @@ def main_init(name, args):
" Likely bad things to come!"))
if not args.force:
if args.local:
- return 0
+ return (None, [])
else:
- return 1
+ return (None, ["No instance datasource found."])
# Stage 6
iid = init.instancify()
LOG.debug("%s will now be targeting instance id: %s", name, iid)
@@ -274,7 +277,7 @@ def main_init(name, args):
init.consume_data(PER_ALWAYS)
except Exception:
util.logexc(LOG, "Consuming user data failed!")
- return 1
+ return (init.datasource, ["Consuming user data failed!"])
# Stage 8 - re-read and apply relevant cloud-config to include user-data
mods = stages.Modules(init, extract_fns(args))
@@ -291,7 +294,7 @@ def main_init(name, args):
logging.setupLogging(mods.cfg)
# Stage 10
- return run_module_section(mods, name, name)
+ return (init.datasource, run_module_section(mods, name, name))
def main_modules(action_name, args):
@@ -315,14 +318,12 @@ def main_modules(action_name, args):
init.fetch()
except sources.DataSourceNotFoundException:
# There was no datasource found, theres nothing to do
- util.logexc(LOG, ('Can not apply stage %s, '
- 'no datasource found!'
- " Likely bad things to come!"), name)
- print_exc(('Can not apply stage %s, '
- 'no datasource found!'
- " Likely bad things to come!") % (name))
+ msg = ('Can not apply stage %s, no datasource found! Likely bad '
+ 'things to come!' % name)
+ util.logexc(LOG, msg)
+ print_exc(msg)
if not args.force:
- return 1
+ return [(msg)]
# Stage 3
mods = stages.Modules(init, extract_fns(args))
# Stage 4
@@ -419,6 +420,110 @@ def main_single(name, args):
return 0
+def atomic_write_json(path, data):
+ tf = None
+ try:
+ tf = tempfile.NamedTemporaryFile(dir=os.path.dirname(path),
+ delete=False)
+ tf.write(json.dumps(data, indent=1) + "\n")
+ tf.close()
+ os.rename(tf.name, path)
+ except Exception as e:
+ if tf is not None:
+ util.del_file(tf.name)
+ raise e
+
+
+def status_wrapper(name, args, data_d=None, link_d=None):
+ if data_d is None:
+ data_d = os.path.normpath("/var/lib/cloud/data")
+ if link_d is None:
+ link_d = os.path.normpath("/run/cloud-init")
+
+ status_path = os.path.join(data_d, "status.json")
+ status_link = os.path.join(link_d, "status.json")
+ result_path = os.path.join(data_d, "result.json")
+ result_link = os.path.join(link_d, "result.json")
+
+ util.ensure_dirs((data_d, link_d,))
+
+ (_name, functor) = args.action
+
+ if name == "init":
+ if args.local:
+ mode = "init-local"
+ else:
+ mode = "init"
+ elif name == "modules":
+ mode = "modules-%s" % args.mode
+ else:
+ raise ValueError("unknown name: %s" % name)
+
+ modes = ('init', 'init-local', 'modules-config', 'modules-final')
+
+ status = None
+ if mode == 'init-local':
+ for f in (status_link, result_link, status_path, result_path):
+ util.del_file(f)
+ else:
+ try:
+ status = json.loads(util.load_file(status_path))
+ except:
+ pass
+
+ if status is None:
+ nullstatus = {
+ 'errors': [],
+ 'start': None,
+ 'end': None,
+ }
+ status = {'v1': {}}
+ for m in modes:
+ status['v1'][m] = nullstatus.copy()
+ status['v1']['datasource'] = None
+
+ v1 = status['v1']
+ v1['stage'] = mode
+ v1[mode]['start'] = time.time()
+
+ atomic_write_json(status_path, status)
+ util.sym_link(os.path.relpath(status_path, link_d), status_link,
+ force=True)
+
+ try:
+ ret = functor(name, args)
+ if mode in ('init', 'init-local'):
+ (datasource, errors) = ret
+ if datasource is not None:
+ v1['datasource'] = str(datasource)
+ else:
+ errors = ret
+
+ v1[mode]['errors'] = [str(e) for e in errors]
+
+ except Exception as e:
+ v1[mode]['errors'] = [str(e)]
+
+ v1[mode]['finished'] = time.time()
+ v1['stage'] = None
+
+ atomic_write_json(status_path, status)
+
+ if mode == "modules-final":
+ # write the 'finished' file
+ errors = []
+ for m in modes:
+ if v1[m]['errors']:
+ errors.extend(v1[m].get('errors', []))
+
+ atomic_write_json(result_path,
+ {'v1': {'datasource': v1['datasource'], 'errors': errors}})
+ util.sym_link(os.path.relpath(result_path, link_d), result_link,
+ force=True)
+
+ return len(v1[mode]['errors'])
+
+
def main():
parser = argparse.ArgumentParser()
@@ -502,6 +607,8 @@ def main():
signal_handler.attach_handlers()
(name, functor) = args.action
+ if name in ("modules", "init"):
+ functor = status_wrapper
return util.log_time(logfunc=LOG.debug, msg="cloud-init mode '%s'" % name,
get_uptime=True, func=functor, args=(name, args))
diff --git a/cloudinit/config/cc_seed_random.py b/cloudinit/config/cc_seed_random.py
index 22a31f29..599280f6 100644
--- a/cloudinit/config/cc_seed_random.py
+++ b/cloudinit/config/cc_seed_random.py
@@ -1,8 +1,11 @@
# vi: ts=4 expandtab
#
# Copyright (C) 2013 Yahoo! Inc.
+# Copyright (C) 2014 Canonical, Ltd
#
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
+# Author: Dustin Kirkland <kirkland@ubuntu.com>
+# 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
@@ -20,9 +23,11 @@ import base64
from StringIO import StringIO
from cloudinit.settings import PER_INSTANCE
+from cloudinit import log as logging
from cloudinit import util
frequency = PER_INSTANCE
+LOG = logging.getLogger(__name__)
def _decode(data, encoding=None):
@@ -38,24 +43,48 @@ def _decode(data, encoding=None):
raise IOError("Unknown random_seed encoding: %s" % (encoding))
-def handle(name, cfg, cloud, log, _args):
- if not cfg or "random_seed" not in cfg:
- log.debug(("Skipping module named %s, "
- "no 'random_seed' configuration found"), name)
+def handle_random_seed_command(command, required):
+ if not command and required:
+ raise ValueError("no command found but required=true")
+ elif not command:
+ LOG.debug("no command provided")
return
- my_cfg = cfg['random_seed']
- seed_path = my_cfg.get('file', '/dev/urandom')
+ cmd = command[0]
+ if not util.which(cmd):
+ if required:
+ raise ValueError("command '%s' not found but required=true", cmd)
+ else:
+ LOG.debug("command '%s' not found for seed_command", cmd)
+ return
+ util.subp(command)
+
+
+def handle(name, cfg, cloud, log, _args):
+ mycfg = cfg.get('random_seed', {})
+ seed_path = mycfg.get('file', '/dev/urandom')
+ seed_data = mycfg.get('data', '')
+
seed_buf = StringIO()
- seed_buf.write(_decode(my_cfg.get('data', ''),
- encoding=my_cfg.get('encoding')))
+ if seed_data:
+ seed_buf.write(_decode(seed_data, encoding=mycfg.get('encoding')))
+ # 'random_seed' is set up by Azure datasource, and comes already in
+ # openstack meta_data.json
metadata = cloud.datasource.metadata
if metadata and 'random_seed' in metadata:
seed_buf.write(metadata['random_seed'])
seed_data = seed_buf.getvalue()
if len(seed_data):
- log.debug("%s: adding %s bytes of random seed entrophy to %s", name,
+ log.debug("%s: adding %s bytes of random seed entropy to %s", name,
len(seed_data), seed_path)
util.append_file(seed_path, seed_data)
+
+ command = mycfg.get('command', ['pollinate', '-q'])
+ req = mycfg.get('command_required', False)
+ try:
+ handle_random_seed_command(command=command, required=req)
+ except ValueError as e:
+ log.warn("handling random command [%s] failed: %s", command, e)
+ raise e
diff --git a/cloudinit/distros/freebsd.py b/cloudinit/distros/freebsd.py
index afb502c9..d98f9578 100644
--- a/cloudinit/distros/freebsd.py
+++ b/cloudinit/distros/freebsd.py
@@ -79,7 +79,7 @@ class Distro(distros.Distro):
return val
def _read_system_hostname(self):
- sys_hostname = self._read_hostname()
+ sys_hostname = self._read_hostname(filename=None)
return ('rc.conf', sys_hostname)
def _read_hostname(self, filename, default=None):
diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py
index ac3c011f..30b6f3b3 100644
--- a/cloudinit/netinfo.py
+++ b/cloudinit/netinfo.py
@@ -52,18 +52,23 @@ def netdev_info(empty=""):
fieldpost = "6"
for i in range(len(toks)):
- if toks[i] == "hwaddr" or toks[i] == "ether":
- try:
- devs[curdev]["hwaddr"] = toks[i + 1]
- except IndexError:
- pass
+ # older net-tools (ubuntu) show 'inet addr:xx.yy',
+ # newer (freebsd and fedora) show 'inet xx.yy'
+ # just skip this 'inet' entry. (LP: #1285185)
+ try:
+ if (toks[i] in ("inet", "inet6") and
+ toks[i + 1].startswith("addr:")):
+ continue
+ except IndexError:
+ pass
# Couple the different items we're interested in with the correct
# field since FreeBSD/CentOS/Fedora differ in the output.
ifconfigfields = {
"addr:": "addr", "inet": "addr",
"bcast:": "bcast", "broadcast": "bcast",
- "mask:": "mask", "netmask": "mask"
+ "mask:": "mask", "netmask": "mask",
+ "hwaddr": "hwaddr", "ether": "hwaddr",
}
for origfield, field in ifconfigfields.items():
target = "%s%s" % (field, fieldpost)
diff --git a/cloudinit/sources/DataSourceAltCloud.py b/cloudinit/sources/DataSourceAltCloud.py
index a834f8eb..1e913a6e 100644
--- a/cloudinit/sources/DataSourceAltCloud.py
+++ b/cloudinit/sources/DataSourceAltCloud.py
@@ -115,6 +115,12 @@ class DataSourceAltCloud(sources.DataSource):
'''
+ uname_arch = os.uname()[4]
+ if uname_arch.startswith("arm") or uname_arch == "aarch64":
+ # Disabling because dmidecode in CMD_DMI_SYSTEM crashes kvm process
+ LOG.debug("Disabling AltCloud datasource on arm (LP: #1243287)")
+ return 'UNKNOWN'
+
cmd = CMD_DMI_SYSTEM
try:
(cmd_out, _err) = util.subp(cmd)
diff --git a/cloudinit/sources/DataSourceCloudSigma.py b/cloudinit/sources/DataSourceCloudSigma.py
index 79ced3f4..e1c7e566 100644
--- a/cloudinit/sources/DataSourceCloudSigma.py
+++ b/cloudinit/sources/DataSourceCloudSigma.py
@@ -20,7 +20,6 @@ import re
from cloudinit import log as logging
from cloudinit import sources
-from cloudinit import util
from cloudinit.cs_utils import Cepko
LOG = logging.getLogger(__name__)
@@ -51,7 +50,9 @@ class DataSourceCloudSigma(sources.DataSource):
server_context = self.cepko.all().result
server_meta = server_context['meta']
except:
- util.logexc(LOG, "Failed reading from the serial port")
+ # TODO: check for explicit "config on", and then warn
+ # but since no explicit config is available now, just debug.
+ LOG.debug("CloudSigma: Unable to read from serial port")
return False
dsmode = server_meta.get('cloudinit-dsmode', self.dsmode)
diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py
index 142e0eb8..0c35f83a 100644
--- a/cloudinit/sources/DataSourceConfigDrive.py
+++ b/cloudinit/sources/DataSourceConfigDrive.py
@@ -181,7 +181,7 @@ def get_previous_iid(paths):
# hasn't declared itself found.
fname = os.path.join(paths.get_cpath('data'), 'instance-id')
try:
- return util.load_file(fname)
+ return util.load_file(fname).rstrip("\n")
except IOError:
return None
diff --git a/cloudinit/sources/DataSourceNoCloud.py b/cloudinit/sources/DataSourceNoCloud.py
index cbaac29f..8dc96ab6 100644
--- a/cloudinit/sources/DataSourceNoCloud.py
+++ b/cloudinit/sources/DataSourceNoCloud.py
@@ -90,7 +90,7 @@ class DataSourceNoCloud(sources.DataSource):
found.append("ds_config")
def _pp2d_callback(mp, data):
- util.pathprefix2dict(mp, **data)
+ return util.pathprefix2dict(mp, **data)
label = self.ds_cfg.get('fs_label', "cidata")
if label is not None:
@@ -110,7 +110,8 @@ class DataSourceNoCloud(sources.DataSource):
LOG.debug("Attempting to use data from %s", dev)
try:
- seeded = util.mount_cb(dev, _pp2d_callback)
+ seeded = util.mount_cb(dev, _pp2d_callback,
+ pp2d_kwargs)
except ValueError as e:
if dev in label_list:
LOG.warn("device %s with label=%s not a"
@@ -123,7 +124,7 @@ class DataSourceNoCloud(sources.DataSource):
# that is more likely to be what is desired. If they want
# dsmode of local, then they must specify that.
if 'dsmode' not in mydata['meta-data']:
- mydata['meta-data'] = "net"
+ mydata['dsmode'] = "net"
LOG.debug("Using data from %s", dev)
found.append(dev)
diff --git a/cloudinit/sources/DataSourceOpenStack.py b/cloudinit/sources/DataSourceOpenStack.py
index 72ec8075..0970d07b 100644
--- a/cloudinit/sources/DataSourceOpenStack.py
+++ b/cloudinit/sources/DataSourceOpenStack.py
@@ -88,11 +88,11 @@ class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource):
md_urls = []
url2base = {}
for url in urls:
- md_url = url_helper.combine_url(url, 'openstack',
- openstack.OS_LATEST,
- 'meta_data.json')
- md_urls.append(md_url)
- url2base[md_url] = url
+ for version in openstack.OS_VERSIONS + (openstack.OS_LATEST,):
+ md_url = url_helper.combine_url(url, 'openstack',
+ version, 'meta_data.json')
+ md_urls.append(md_url)
+ url2base[md_url] = url
(max_wait, timeout) = self._get_url_settings()
start_time = time.time()
@@ -120,7 +120,7 @@ class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource):
read_metadata_service,
args=[self.metadata_address],
kwargs={'ssl_details': self.ssl_details,
- 'version': openstack.OS_LATEST})
+ 'version': openstack.OS_HAVANA})
except openstack.NonReadable:
return False
except (openstack.BrokenMetadata, IOError):
diff --git a/cloudinit/sources/DataSourceSmartOS.py b/cloudinit/sources/DataSourceSmartOS.py
index 8d4a16e6..7c1eb09a 100644
--- a/cloudinit/sources/DataSourceSmartOS.py
+++ b/cloudinit/sources/DataSourceSmartOS.py
@@ -174,6 +174,12 @@ class DataSourceSmartOS(sources.DataSource):
LOG.debug("Host does not appear to be on SmartOS")
return False
+ uname_arch = os.uname()[4]
+ if uname_arch.startswith("arm") or uname_arch == "aarch64":
+ # Disabling because dmidcode in dmi_data() crashes kvm process
+ LOG.debug("Disabling SmartOS datasource on arm (LP: #1243287)")
+ return False
+
dmi_info = dmi_data()
if dmi_info is False:
LOG.debug("No dmidata utility found")
@@ -203,7 +209,7 @@ class DataSourceSmartOS(sources.DataSource):
# executed. It may be of any format that would be considered
# executable in the guest instance.
#
- # We write 'user-script' and 'operator-script' into the
+ # We write 'user-script' and 'operator-script' into the
# instance/data directory. The default vendor-data then handles
# executing them later.
data_d = os.path.join(self.paths.get_cpath(), 'instances',
@@ -238,7 +244,8 @@ class DataSourceSmartOS(sources.DataSource):
md['vendor-data'] = BUILTIN_VENDOR_DATA % {
'user_script': user_script,
'operator_script': operator_script,
- 'per_boot_d': os.path.join(self.paths.get_cpath("scripts"), 'per-boot'),
+ 'per_boot_d': os.path.join(self.paths.get_cpath("scripts"),
+ 'per-boot'),
}
self.metadata = util.mergemanydict([md, self.metadata])
diff --git a/cloudinit/sources/helpers/__init__.py b/cloudinit/sources/helpers/__init__.py
index 2cf99ec8..386225d5 100644
--- a/cloudinit/sources/helpers/__init__.py
+++ b/cloudinit/sources/helpers/__init__.py
@@ -11,4 +11,3 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py
index a17148d3..0fac0335 100644
--- a/cloudinit/sources/helpers/openstack.py
+++ b/cloudinit/sources/helpers/openstack.py
@@ -44,12 +44,15 @@ KEY_COPIES = (
('local-hostname', 'hostname', False),
('instance-id', 'uuid', True),
)
+OS_LATEST = 'latest'
+OS_FOLSOM = '2012-08-10'
+OS_GRIZZLY = '2013-04-04'
+OS_HAVANA = '2013-10-17'
OS_VERSIONS = (
- '2012-08-10', # folsom
- '2013-04-04', # grizzly
- '2013-10-17', # havana
+ OS_FOLSOM,
+ OS_GRIZZLY,
+ OS_HAVANA,
)
-OS_LATEST = 'latest'
class NonReadable(IOError):
@@ -176,12 +179,12 @@ class BaseReader(object):
potential_version)
if self._path_exists(path):
if potential_version != version:
- LOG.warn("Version '%s' not available, attempting to use"
- " version '%s' instead", version,
- potential_version)
+ LOG.debug("Version '%s' not available, attempting to use"
+ " version '%s' instead", version,
+ potential_version)
return potential_version
- LOG.warn("Version '%s' not available, attempting to use '%s'"
- " instead", version, OS_LATEST)
+ LOG.debug("Version '%s' not available, attempting to use '%s'"
+ " instead", version, OS_LATEST)
return OS_LATEST
def read_v2(self, version=None):
diff --git a/cloudinit/util.py b/cloudinit/util.py
index 87b0c853..06039ee2 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -1395,8 +1395,10 @@ def get_builtin_cfg():
return obj_copy.deepcopy(CFG_BUILTIN)
-def sym_link(source, link):
+def sym_link(source, link, force=False):
LOG.debug("Creating symbolic link from %r => %r", link, source)
+ if force and os.path.exists(link):
+ del_file(link)
os.symlink(source, link)
diff --git a/doc/status.txt b/doc/status.txt
new file mode 100644
index 00000000..60993216
--- /dev/null
+++ b/doc/status.txt
@@ -0,0 +1,53 @@
+cloud-init will keep a 'status' file up to date for other applications
+wishing to use it to determine cloud-init status.
+
+It will manage 2 files:
+ status.json
+ result.json
+
+The files will be written to /var/lib/cloud/data/ .
+A symlink will be created in /run/cloud-init. The link from /run is to ensure
+that if the file exists, it is not stale for this boot.
+
+status.json's format is:
+ {
+ 'v1': {
+ 'init': {
+ errors: [] # list of strings for each error that occurred
+ start: float # time.time() that this stage started or None
+ end: float # time.time() that this stage finished or None
+ },
+ 'init-local': {
+ 'errors': [], 'start': <float>, 'end' <float> # (same as 'init' above)
+ },
+ 'modules-config': {
+ 'errors': [], 'start': <float>, 'end' <float> # (same as 'init' above)
+ },
+ 'modules-final': {
+ 'errors': [], 'start': <float>, 'end' <float> # (same as 'init' above)
+ },
+ 'datasource': string describing datasource found or None
+ 'stage': string representing stage that is currently running
+ ('init', 'init-local', 'modules-final', 'modules-config', None)
+ if None, then no stage is running. Reader must read the start/end
+ of each of the above stages to determine the state.
+ }
+
+result.json's format is:
+ {
+ 'v1': {
+ 'datasource': string describing the datasource found
+ 'errors': [] # list of errors reported
+ }
+ }
+
+Thus, to determine if cloud-init is finished:
+ fin = "/run/cloud-init/result.json"
+ if os.path.exists(fin):
+ ret = json.load(open(fin, "r"))
+ if len(ret['v1']['errors']):
+ print "Finished with errors:" + "\n".join(ret['v1']['errors'])
+ else:
+ print "Finished no errors"
+ else:
+ print "Not Finished"
diff --git a/tests/unittests/test_datasource/test_altcloud.py b/tests/unittests/test_datasource/test_altcloud.py
index bda61c7e..eaaa90e6 100644
--- a/tests/unittests/test_datasource/test_altcloud.py
+++ b/tests/unittests/test_datasource/test_altcloud.py
@@ -33,6 +33,8 @@ import cloudinit.sources.DataSourceAltCloud
from cloudinit.sources.DataSourceAltCloud import DataSourceAltCloud
from cloudinit.sources.DataSourceAltCloud import read_user_data_callback
+OS_UNAME_ORIG = getattr(os, 'uname')
+
def _write_cloud_info_file(value):
'''
@@ -104,11 +106,16 @@ class TestGetCloudType(TestCase):
def setUp(self):
'''Set up.'''
self.paths = helpers.Paths({'cloud_dir': '/tmp'})
+ # We have a different code path for arm to deal with LP1243287
+ # We have to switch arch to x86_64 to avoid test failure
+ force_arch('x86_64')
def tearDown(self):
# Reset
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
['dmidecode', '--string', 'system-product-name']
+ # Return back to original arch
+ force_arch()
def test_rhev(self):
'''
@@ -238,6 +245,9 @@ class TestGetDataNoCloudInfoFile(TestCase):
self.paths = helpers.Paths({'cloud_dir': '/tmp'})
cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \
'no such file'
+ # We have a different code path for arm to deal with LP1243287
+ # We have to switch arch to x86_64 to avoid test failure
+ force_arch('x86_64')
def tearDown(self):
# Reset
@@ -245,6 +255,8 @@ class TestGetDataNoCloudInfoFile(TestCase):
'/etc/sysconfig/cloud-info'
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
['dmidecode', '--string', 'system-product-name']
+ # Return back to original arch
+ force_arch()
def test_rhev_no_cloud_file(self):
'''Test No cloud info file module get_data() forcing RHEV.'''
@@ -442,4 +454,16 @@ class TestReadUserDataCallback(TestCase):
_remove_user_data_files(self.mount_dir)
self.assertEquals(None, read_user_data_callback(self.mount_dir))
+
+def force_arch(arch=None):
+
+ def _os_uname():
+ return ('LINUX', 'NODENAME', 'RELEASE', 'VERSION', arch)
+
+ if arch:
+ setattr(os, 'uname', _os_uname)
+ elif arch is None:
+ setattr(os, 'uname', OS_UNAME_ORIG)
+
+
# vi: ts=4 expandtab
diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
index 44c537f4..ccfd672a 100644
--- a/tests/unittests/test_datasource/test_azure.py
+++ b/tests/unittests/test_datasource/test_azure.py
@@ -352,13 +352,13 @@ class TestAzureDataSource(MockerTestCase):
ovf_env_path = os.path.join(self.waagent_d, 'ovf-env.xml')
self.assertTrue(os.path.exists(ovf_env_path))
self.assertEqual(xml, load_file(ovf_env_path))
-
+
def test_existing_ovf_same(self):
# waagent/SharedConfig left alone if found ovf-env.xml same as cached
odata = {'UserData': base64.b64encode("SOMEUSERDATA")}
data = {'ovfcontent': construct_valid_ovf_env(data=odata)}
- populate_dir(self.waagent_d,
+ populate_dir(self.waagent_d,
{'ovf-env.xml': data['ovfcontent'],
'otherfile': 'otherfile-content',
'SharedConfig.xml': 'mysharedconfig'})
diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py
index 937b88c1..4404668e 100644
--- a/tests/unittests/test_datasource/test_configdrive.py
+++ b/tests/unittests/test_datasource/test_configdrive.py
@@ -166,7 +166,7 @@ class TestConfigDriveDataSource(MockerTestCase):
my_mock.replay()
device = cfg_ds.device_name_to_device(name)
self.assertEquals(dev_name, device)
-
+
def test_dev_ec2_map(self):
populate_dir(self.tmp, CFG_DRIVE_FILES_V2)
cfg_ds = ds.DataSourceConfigDrive(settings.CFG_BUILTIN,
diff --git a/tests/unittests/test_datasource/test_nocloud.py b/tests/unittests/test_datasource/test_nocloud.py
index af575a10..a65833eb 100644
--- a/tests/unittests/test_datasource/test_nocloud.py
+++ b/tests/unittests/test_datasource/test_nocloud.py
@@ -133,6 +133,7 @@ class TestNoCloudDataSource(MockerTestCase):
self.assertFalse(dsrc.vendordata)
self.assertTrue(ret)
+
class TestParseCommandLineData(MockerTestCase):
def test_parse_cmdline_data_valid(self):
diff --git a/tests/unittests/test_datasource/test_smartos.py b/tests/unittests/test_datasource/test_smartos.py
index 19282bac..8f9fa27d 100644
--- a/tests/unittests/test_datasource/test_smartos.py
+++ b/tests/unittests/test_datasource/test_smartos.py
@@ -158,6 +158,11 @@ class TestSmartOSDataSource(helpers.FilesystemMockingTestCase):
def _dmi_data():
return dmi_data
+ def _os_uname():
+ # LP: #1243287. tests assume this runs, but running test on
+ # arm would cause them all to fail.
+ return ('LINUX', 'NODENAME', 'RELEASE', 'VERSION', 'x86_64')
+
if sys_cfg is None:
sys_cfg = {}
@@ -168,6 +173,7 @@ class TestSmartOSDataSource(helpers.FilesystemMockingTestCase):
self.apply_patches([(mod, 'LEGACY_USER_D', self.legacy_user_d)])
self.apply_patches([(mod, 'get_serial', _get_serial)])
self.apply_patches([(mod, 'dmi_data', _dmi_data)])
+ self.apply_patches([(os, 'uname', _os_uname)])
dsrc = mod.DataSourceSmartOS(sys_cfg, distro=None,
paths=self.paths)
return dsrc
@@ -334,33 +340,6 @@ class TestSmartOSDataSource(helpers.FilesystemMockingTestCase):
shebang = f.readlines()[0].strip()
self.assertEquals(shebang, "#!/usr/bin/perl")
- def test_scripts_removed(self):
- """
- Since SmartOS requires that the user script is fetched
- each boot, we want to make sure that the information
- is backed-up for user-review later.
-
- This tests the behavior of when a script is removed. It makes
- sure that a) the previous script is backed-up; and 2) that
- there is no script remaining.
- """
-
- script_d = os.path.join(self.tmp, "instance", "data")
- os.makedirs(script_d)
-
- test_script_f = os.path.join(script_d, 'user-script')
- with open(test_script_f, 'w') as f:
- f.write("TEST DATA")
-
- my_returns = MOCK_RETURNS.copy()
- del my_returns['user-script']
-
- dsrc = self._get_ds(mockdata=my_returns)
- ret = dsrc.get_data()
- self.assertTrue(ret)
- self.assertFalse(dsrc.metadata['user-script'])
- self.assertFalse(os.path.exists(test_script_f))
-
def test_userdata_removed(self):
"""
User-data in the SmartOS world is supposed to be written to a file
diff --git a/tests/unittests/test_handler/test_handler_growpart.py b/tests/unittests/test_handler/test_handler_growpart.py
index 996526d3..f2ed4597 100644
--- a/tests/unittests/test_handler/test_handler_growpart.py
+++ b/tests/unittests/test_handler/test_handler_growpart.py
@@ -9,7 +9,6 @@ import errno
import logging
import os
import re
-import unittest
# growpart:
# mode: auto # off, on, auto, 'growpart'
@@ -203,7 +202,7 @@ def simple_device_part_info(devpath):
return x
-class Bunch:
+class Bunch(object):
st_mode = None # fix pylint complaint
def __init__(self, **kwds):
diff --git a/tests/unittests/test_handler/test_handler_seed_random.py b/tests/unittests/test_handler/test_handler_seed_random.py
index 2b21ac02..00c50fc1 100644
--- a/tests/unittests/test_handler/test_handler_seed_random.py
+++ b/tests/unittests/test_handler/test_handler_seed_random.py
@@ -42,10 +42,29 @@ class TestRandomSeed(t_help.TestCase):
def setUp(self):
super(TestRandomSeed, self).setUp()
self._seed_file = tempfile.mktemp()
+ self.unapply = []
+
+ # by default 'which' has nothing in its path
+ self.apply_patches([(util, 'which', self._which)])
+ self.apply_patches([(util, 'subp', self._subp)])
+ self.subp_called = []
+ self.whichdata = {}
def tearDown(self):
+ apply_patches([i for i in reversed(self.unapply)])
util.del_file(self._seed_file)
+ def apply_patches(self, patches):
+ ret = apply_patches(patches)
+ self.unapply += ret
+
+ def _which(self, program):
+ return self.whichdata.get(program)
+
+ def _subp(self, args):
+ self.subp_called.append(tuple(args))
+ return
+
def _compress(self, text):
contents = StringIO()
gz_fh = gzip.GzipFile(mode='wb', fileobj=contents)
@@ -148,3 +167,51 @@ class TestRandomSeed(t_help.TestCase):
cc_seed_random.handle('test', cfg, c, LOG, [])
contents = util.load_file(self._seed_file)
self.assertEquals('tiny-tim-was-here-so-was-josh', contents)
+
+ def test_seed_command_not_provided_pollinate_available(self):
+ c = self._get_cloud('ubuntu', {})
+ self.whichdata = {'pollinate': '/usr/bin/pollinate'}
+ cc_seed_random.handle('test', {}, c, LOG, [])
+
+ self.assertEquals(self.subp_called, [('pollinate', '-q')])
+
+ def test_seed_command_not_provided_pollinate_not_available(self):
+ c = self._get_cloud('ubuntu', {})
+ self.whichdata = {}
+ cc_seed_random.handle('test', {}, c, LOG, [])
+
+ # subp should not have been called as which would say not available
+ self.assertEquals(self.subp_called, list())
+
+ def test_unavailable_seed_command_and_required_raises_error(self):
+ c = self._get_cloud('ubuntu', {})
+ self.whichdata = {}
+ self.assertRaises(ValueError, cc_seed_random.handle,
+ 'test', {'random_seed': {'command_required': True}}, c, LOG, [])
+
+ def test_seed_command_and_required(self):
+ c = self._get_cloud('ubuntu', {})
+ self.whichdata = {'foo': 'foo'}
+ cfg = {'random_seed': {'command_required': True, 'command': ['foo']}}
+ cc_seed_random.handle('test', cfg, c, LOG, [])
+
+ self.assertEquals(self.subp_called, [('foo',)])
+
+ def test_seed_command_non_default(self):
+ c = self._get_cloud('ubuntu', {})
+ self.whichdata = {'foo': 'foo'}
+ cfg = {'random_seed': {'command_required': True, 'command': ['foo']}}
+ cc_seed_random.handle('test', cfg, c, LOG, [])
+
+ self.assertEquals(self.subp_called, [('foo',)])
+
+
+def apply_patches(patches):
+ ret = []
+ for (ref, name, replace) in patches:
+ if replace is None:
+ continue
+ orig = getattr(ref, name)
+ setattr(ref, name, replace)
+ ret.append((ref, name, orig))
+ return ret
diff --git a/tools/make-tarball b/tools/make-tarball
index 27f5f374..b7039150 100755
--- a/tools/make-tarball
+++ b/tools/make-tarball
@@ -27,7 +27,13 @@ else
ARCHIVE_FN="$PWD/cloud-init-$VERSION~bzr$REVNO.tar.gz"
fi
-bzr export --format=tgz --root="cloud-init-$VERSION~bzr$REVNO" \
+export_uncommitted=""
+if [ "${UNCOMMITTED:-0}" != "0" ]; then
+ export_uncommitted="--uncommitted"
+fi
+
+bzr export ${export_uncommitted} \
+ --format=tgz --root="cloud-init-$VERSION~bzr$REVNO" \
"--revision=${REVNO}" "${ARCHIVE_FN}" "$ROOT_DIR"
echo "$ARCHIVE_FN"