From f38a2047731530dfa796056c6b1a07d2a9158e66 Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Wed, 11 Feb 2015 10:33:00 +0000 Subject: Fix reference to non-existent variable. --- cloudinit/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudinit/util.py b/cloudinit/util.py index b845adfd..d63b4bf2 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -1481,8 +1481,8 @@ def mount_cb(device, callback, data=None, rw=False, mtype=None, sync=True): device, mtype, exc) pass if not mountpoint: - raise MountFailedError("Failed mounting %s to %s due to: %s" % - (device, tmpd, exc)) + raise MountFailedError( + "Failed mounting %s to %s".format(device, tmpd)) # Be nice and ensure it ends with a slash if not mountpoint.endswith("/"): -- cgit v1.2.3 From 3b605b691d4fde6be0ccb8034b4c3fc8299f3858 Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Wed, 11 Feb 2015 11:41:04 +0000 Subject: Enable passing arguments to nose via tox. --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index d04cd47c..686a1e7a 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ envlist = py26,py27,py34 recreate = True [testenv] -commands = python -m nose tests +commands = python -m nose {posargs:tests} deps = contextlib2 httpretty>=0.7.1 @@ -13,7 +13,7 @@ deps = pyflakes [testenv:py26] -commands = nosetests tests +commands = nosetests {posargs:tests} deps = contextlib2 httpretty>=0.7.1 -- cgit v1.2.3 From 4cfdde8be624f5dc9a9ec214ea60f9d1f43ee424 Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Wed, 11 Feb 2015 11:54:20 +0000 Subject: Fix import ordering in test_util.py. --- tests/unittests/test_util.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py index a1bd2c46..23821521 100644 --- a/tests/unittests/test_util.py +++ b/tests/unittests/test_util.py @@ -1,21 +1,21 @@ from __future__ import print_function import os -import stat -import yaml import shutil +import stat import tempfile -from . import helpers import six +import yaml + +from cloudinit import importer, util +from . import helpers try: from unittest import mock except ImportError: import mock -from cloudinit import importer -from cloudinit import util class FakeSelinux(object): -- cgit v1.2.3 From abccec1150d6fada29eae9819968e3d4419440ab Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Wed, 11 Feb 2015 11:54:48 +0000 Subject: Add helpers for patching open and stdout/stderr. --- tests/unittests/helpers.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index 6b9394b3..ce77af93 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -254,6 +254,19 @@ class FilesystemMockingTestCase(ResourceUsingTestCase): self.patched_funcs.enter_context( mock.patch.object(mod, f, trap_func)) + def patchOpen(self, new_root): + trap_func = retarget_many_wrapper(new_root, 1, open) + name = 'builtins.open' if PY3 else '__builtin__.open' + self.patched_funcs.enter_context(mock.patch(name, trap_func)) + + def patchStdoutAndStderr(self, stdout=None, stderr=None): + if stdout is not None: + self.patched_funcs.enter_context( + mock.patch.object(sys, 'stdout', stdout)) + if stderr is not None: + self.patched_funcs.enter_context( + mock.patch.object(sys, 'stderr', stderr)) + class HttprettyTestCase(TestCase): # necessary as http_proxy gets in the way of httpretty -- cgit v1.2.3 From 62f5f10c2572585b89f5c837b84015a86e6af357 Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Wed, 11 Feb 2015 11:55:06 +0000 Subject: Add unittests for util.multi_log. --- tests/unittests/test_util.py | 68 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py index 23821521..2772ce58 100644 --- a/tests/unittests/test_util.py +++ b/tests/unittests/test_util.py @@ -1,5 +1,6 @@ from __future__ import print_function +import logging import os import shutil import stat @@ -377,4 +378,71 @@ class TestReadDMIData(helpers.FilesystemMockingTestCase): self.assertFalse(None, util.read_dmi_data("key")) +class TestMultiLog(helpers.FilesystemMockingTestCase): + + def _createConsole(self, root): + os.mkdir(os.path.join(root, 'dev')) + open(os.path.join(root, 'dev', 'console'), 'a').close() + + def setUp(self): + super(TestMultiLog, self).setUp() + self.root = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.root) + self.patchOS(self.root) + self.patchUtils(self.root) + self.patchOpen(self.root) + self.stdout = six.StringIO() + self.stderr = six.StringIO() + self.patchStdoutAndStderr(self.stdout, self.stderr) + + def test_stderr_used_by_default(self): + logged_string = 'test stderr output' + util.multi_log(logged_string) + self.assertEqual(logged_string, self.stderr.getvalue()) + + def test_stderr_not_used_if_false(self): + util.multi_log('should not see this', stderr=False) + self.assertEqual('', self.stderr.getvalue()) + + def test_logs_go_to_console_by_default(self): + self._createConsole(self.root) + logged_string = 'something very important' + util.multi_log(logged_string) + self.assertEqual(logged_string, open('/dev/console').read()) + + def test_logs_dont_go_to_stdout_if_console_exists(self): + self._createConsole(self.root) + util.multi_log('something') + self.assertEqual('', self.stdout.getvalue()) + + def test_logs_go_to_stdout_if_console_does_not_exist(self): + logged_string = 'something very important' + util.multi_log(logged_string) + self.assertEqual(logged_string, self.stdout.getvalue()) + + def test_logs_go_to_log_if_given(self): + log = mock.MagicMock() + logged_string = 'something very important' + util.multi_log(logged_string, log=log) + self.assertEqual([((mock.ANY, logged_string), {})], + log.log.call_args_list) + + def test_newlines_stripped_from_log_call(self): + log = mock.MagicMock() + expected_string = 'something very important' + util.multi_log('{0}\n'.format(expected_string), log=log) + self.assertEqual((mock.ANY, expected_string), log.log.call_args[0]) + + def test_log_level_defaults_to_debug(self): + log = mock.MagicMock() + util.multi_log('message', log=log) + self.assertEqual((logging.DEBUG, mock.ANY), log.log.call_args[0]) + + def test_given_log_level_used(self): + log = mock.MagicMock() + log_level = mock.Mock() + util.multi_log('message', log=log, log_level=log_level) + self.assertEqual((log_level, mock.ANY), log.log.call_args[0]) + + # vi: ts=4 expandtab -- cgit v1.2.3 From cf48254eb4a8364ceaeb710607c4b21ba5cbe8a6 Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Wed, 11 Feb 2015 11:55:25 +0000 Subject: Encode messages to utf-8 before writing them to the console in util.multi_log. --- cloudinit/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudinit/util.py b/cloudinit/util.py index d63b4bf2..eba93d1c 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -405,7 +405,7 @@ def multi_log(text, console=True, stderr=True, conpath = "/dev/console" if os.path.exists(conpath): with open(conpath, 'wb') as wfh: - wfh.write(text) + wfh.write(text.encode('utf-8')) wfh.flush() else: # A container may lack /dev/console (arguably a container bug). If -- cgit v1.2.3 From 95f135e9f2415b8f89534528641220a0ef9fe570 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 11 Feb 2015 12:08:23 -0500 Subject: add tools/tox-venv for running in a tox env --- tools/tox-venv | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100755 tools/tox-venv diff --git a/tools/tox-venv b/tools/tox-venv new file mode 100755 index 00000000..76ed5076 --- /dev/null +++ b/tools/tox-venv @@ -0,0 +1,42 @@ +#!/bin/sh + +error() { echo "$@" 1>&2; } +fail() { [ $# -eq 0 ] || error "$@"; exit 1; } +Usage() { + cat <&2; exit 1; } +[ "$1" = "-h" -o "$1" = "--help" ] && { Usage; exit 0; } + +env="$1" +shift +tox_d="${0%/*}/../.tox" +activate="$tox_d/$env/bin/activate" + + +[ -d "$tox_d" ] || fail "$tox_d: not a dir. maybe run 'tox'?" + +[ "$env" = "-l" -o "$env" = "--list" ] && { list_toxes ; exit ; } + +if [ ! -f "$activate" ]; then + error "$env: not a valid tox environment?" + error "try one of:" + list_toxes "$tox_d" " " + fail +fi +. "$activate" + +[ "$#" -gt 0 ] || set -- ${SHELL:-/bin/bash} +debian_chroot="tox:$env" exec "$@" -- cgit v1.2.3 From d5c93d393ed41b33522c2d6f12465b1f12d28e95 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 11 Feb 2015 12:10:34 -0500 Subject: pep8 --- tests/unittests/test_util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py index 2772ce58..b96da663 100644 --- a/tests/unittests/test_util.py +++ b/tests/unittests/test_util.py @@ -444,5 +444,4 @@ class TestMultiLog(helpers.FilesystemMockingTestCase): util.multi_log('message', log=log, log_level=log_level) self.assertEqual((log_level, mock.ANY), log.log.call_args[0]) - # vi: ts=4 expandtab -- cgit v1.2.3 From 85953f737b77b55a0fbe160b158f2ce77730e523 Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Wed, 11 Feb 2015 17:24:08 +0000 Subject: Open /dev/console in text mode (so we don't have to encode strings to write them). --- cloudinit/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudinit/util.py b/cloudinit/util.py index d63b4bf2..fe606f23 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -404,7 +404,7 @@ def multi_log(text, console=True, stderr=True, if console: conpath = "/dev/console" if os.path.exists(conpath): - with open(conpath, 'wb') as wfh: + with open(conpath, 'w') as wfh: wfh.write(text) wfh.flush() else: -- cgit v1.2.3 From ceb229043cec98d79aa8e72c6eb5e79f796a96d7 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 11 Feb 2015 12:57:50 -0500 Subject: provide default final message in jinja to avoid WARN in log --- cloudinit/config/cc_final_message.py | 9 ++++++--- doc/examples/cloud-config.txt | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cloudinit/config/cc_final_message.py b/cloudinit/config/cc_final_message.py index b24294e4..ad957e12 100644 --- a/cloudinit/config/cc_final_message.py +++ b/cloudinit/config/cc_final_message.py @@ -26,9 +26,12 @@ from cloudinit.settings import PER_ALWAYS frequency = PER_ALWAYS -# Cheetah formated default message -FINAL_MESSAGE_DEF = ("Cloud-init v. ${version} finished at ${timestamp}." - " Datasource ${datasource}. Up ${uptime} seconds") +# Jinja formated default message +FINAL_MESSAGE_DEF = ( + "## template: jinja\n" + "Cloud-init v. {{version}} finished at {{timestamp}}." + " Datasource {{datasource}}. Up {{uptime}} seconds" +) def handle(_name, cfg, cloud, log, args): diff --git a/doc/examples/cloud-config.txt b/doc/examples/cloud-config.txt index ed4eb7fc..1c59c2cf 100644 --- a/doc/examples/cloud-config.txt +++ b/doc/examples/cloud-config.txt @@ -484,7 +484,9 @@ resize_rootfs: True # final_message # default: cloud-init boot finished at $TIMESTAMP. Up $UPTIME seconds # this message is written by cloud-final when the system is finished -# its first boot +# its first boot. +# This message is rendered as if it were a template. If you +# want jinja, you have to start the line with '## template:jinja\n' final_message: "The system is finally up, after $UPTIME seconds" # configure where output will go -- cgit v1.2.3 From 9acda162465a9b580a79a0fdc2c8e003151c7ead Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 11 Feb 2015 13:45:55 -0500 Subject: pickle contents: be careful loading and storing pickle to be binary --- cloudinit/stages.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cloudinit/stages.py b/cloudinit/stages.py index f4f4591d..c5b1ded0 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -180,9 +180,12 @@ class Init(object): pickled_fn = self.paths.get_ipath_cur('obj_pkl') pickle_contents = None try: - pickle_contents = util.load_file(pickled_fn) - except Exception: + pickle_contents = util.load_file(pickled_fn, decode=False) + except Exception as e: + if os.path.isfile(pickled_fn): + LOG.warn("failed loading pickle in %s: %s" % (pickled_fn, e)) pass + # This is expected so just return nothing # successfully loaded... if not pickle_contents: @@ -203,7 +206,7 @@ class Init(object): util.logexc(LOG, "Failed pickling datasource %s", self.datasource) return False try: - util.write_file(pickled_fn, pk_contents, mode=0o400) + util.write_file(pickled_fn, pk_contents, omode="wb", mode=0o400) except Exception: util.logexc(LOG, "Failed pickling datasource to %s", pickled_fn) return False -- cgit v1.2.3 From 587387cfbff7a89573128dc958df903d1becbde1 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 11 Feb 2015 13:58:23 -0500 Subject: include exception in error again. it is admittedly not clear, but 'exc' should be definied if mountpoint is not. --- cloudinit/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudinit/util.py b/cloudinit/util.py index fe606f23..67ea5553 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -1481,8 +1481,8 @@ def mount_cb(device, callback, data=None, rw=False, mtype=None, sync=True): device, mtype, exc) pass if not mountpoint: - raise MountFailedError( - "Failed mounting %s to %s".format(device, tmpd)) + raise MountFailedError("Failed mounting %s to %s due to: %s" % + (device, tmpd, exc)) # Be nice and ensure it ends with a slash if not mountpoint.endswith("/"): -- cgit v1.2.3