merge from trunk
This commit is contained in:
commit
3e7fd392e8
@ -15,6 +15,8 @@
|
||||
which also reads from uptime. uptime is useful as clock may change during
|
||||
boot due to ntp.
|
||||
- prefer growpart resizer to 'parted resizepart' (LP: #1212492)
|
||||
- support random data seed from config drive or azure, and a module
|
||||
'seed_random' to read that and write it to /dev/urandom.
|
||||
- add OpenNebula Datasource [Vlastimil Holer]
|
||||
0.7.2:
|
||||
- add a debian watch file
|
||||
|
61
cloudinit/config/cc_seed_random.py
Normal file
61
cloudinit/config/cc_seed_random.py
Normal file
@ -0,0 +1,61 @@
|
||||
# vi: ts=4 expandtab
|
||||
#
|
||||
# Copyright (C) 2013 Yahoo! Inc.
|
||||
#
|
||||
# Author: Joshua Harlow <harlowja@yahoo-inc.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
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# 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 base64
|
||||
from StringIO import StringIO
|
||||
|
||||
from cloudinit.settings import PER_INSTANCE
|
||||
from cloudinit import util
|
||||
|
||||
frequency = PER_INSTANCE
|
||||
|
||||
|
||||
def _decode(data, encoding=None):
|
||||
if not data:
|
||||
return ''
|
||||
if not encoding or encoding.lower() in ['raw']:
|
||||
return data
|
||||
elif encoding.lower() in ['base64', 'b64']:
|
||||
return base64.b64decode(data)
|
||||
elif encoding.lower() in ['gzip', 'gz']:
|
||||
return util.decomp_gzip(data, quiet=False)
|
||||
else:
|
||||
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)
|
||||
return
|
||||
|
||||
my_cfg = cfg['random_seed']
|
||||
seed_path = my_cfg.get('file', '/dev/urandom')
|
||||
seed_buf = StringIO()
|
||||
seed_buf.write(_decode(my_cfg.get('data', ''),
|
||||
encoding=my_cfg.get('encoding')))
|
||||
|
||||
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,
|
||||
len(seed_data), seed_path)
|
||||
util.append_file(seed_path, seed_data)
|
@ -292,11 +292,16 @@ class ContentHandlers(object):
|
||||
def is_registered(self, content_type):
|
||||
return content_type in self.registered
|
||||
|
||||
def register(self, mod, initialized=False):
|
||||
def register(self, mod, initialized=False, overwrite=True):
|
||||
types = set()
|
||||
for t in mod.list_types():
|
||||
if overwrite:
|
||||
types.add(t)
|
||||
else:
|
||||
if not self.is_registered(t):
|
||||
types.add(t)
|
||||
for t in types:
|
||||
self.registered[t] = mod
|
||||
types.add(t)
|
||||
if initialized and mod not in self.initialized:
|
||||
self.initialized.append(mod)
|
||||
return types
|
||||
@ -310,15 +315,6 @@ class ContentHandlers(object):
|
||||
def iteritems(self):
|
||||
return self.registered.iteritems()
|
||||
|
||||
def register_defaults(self, defs):
|
||||
registered = set()
|
||||
for mod in defs:
|
||||
for t in mod.list_types():
|
||||
if not self.is_registered(t):
|
||||
self.registered[t] = mod
|
||||
registered.add(t)
|
||||
return registered
|
||||
|
||||
|
||||
class Paths(object):
|
||||
def __init__(self, path_cfgs, ds=None):
|
||||
|
@ -106,6 +106,11 @@ class DataSourceAzureNet(sources.DataSource):
|
||||
if found == ddir:
|
||||
LOG.debug("using files cached in %s", ddir)
|
||||
|
||||
# azure / hyper-v provides random data here
|
||||
seed = util.load_file("/sys/firmware/acpi/tables/OEM0", quiet=True)
|
||||
if seed:
|
||||
self.metadata['random_seed'] = seed
|
||||
|
||||
# now update ds_cfg to reflect contents pass in config
|
||||
usercfg = util.get_cfg_by_path(self.cfg, DS_CFG_PATH, {})
|
||||
self.ds_cfg = util.mergemanydict([usercfg, self.ds_cfg])
|
||||
|
@ -18,6 +18,7 @@
|
||||
# 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 base64
|
||||
import json
|
||||
import os
|
||||
|
||||
@ -41,6 +42,25 @@ DEFAULT_METADATA = {
|
||||
VALID_DSMODES = ("local", "net", "pass", "disabled")
|
||||
|
||||
|
||||
class ConfigDriveHelper(object):
|
||||
def __init__(self, distro):
|
||||
self.distro = distro
|
||||
|
||||
def on_first_boot(self, data):
|
||||
if not data:
|
||||
data = {}
|
||||
if 'network_config' in data:
|
||||
LOG.debug("Updating network interfaces from config drive")
|
||||
self.distro.apply_network(data['network_config'])
|
||||
files = data.get('files')
|
||||
if files:
|
||||
LOG.debug("Writing %s injected files", len(files))
|
||||
try:
|
||||
write_files(files)
|
||||
except IOError:
|
||||
util.logexc(LOG, "Failed writing files")
|
||||
|
||||
|
||||
class DataSourceConfigDrive(sources.DataSource):
|
||||
def __init__(self, sys_cfg, distro, paths):
|
||||
sources.DataSource.__init__(self, sys_cfg, distro, paths)
|
||||
@ -49,6 +69,7 @@ class DataSourceConfigDrive(sources.DataSource):
|
||||
self.seed_dir = os.path.join(paths.seed_dir, 'config_drive')
|
||||
self.version = None
|
||||
self.ec2_metadata = None
|
||||
self.helper = ConfigDriveHelper(distro)
|
||||
|
||||
def __str__(self):
|
||||
root = sources.DataSource.__str__(self)
|
||||
@ -187,20 +208,8 @@ class DataSourceConfigDrive(sources.DataSource):
|
||||
# instance-id
|
||||
prev_iid = get_previous_iid(self.paths)
|
||||
cur_iid = md['instance-id']
|
||||
|
||||
if ('network_config' in results and self.dsmode == "local" and
|
||||
prev_iid != cur_iid):
|
||||
LOG.debug("Updating network interfaces from config drive (%s)",
|
||||
dsmode)
|
||||
self.distro.apply_network(results['network_config'])
|
||||
|
||||
# file writing occurs in local mode (to be as early as possible)
|
||||
if self.dsmode == "local" and prev_iid != cur_iid and results['files']:
|
||||
LOG.debug("writing injected files")
|
||||
try:
|
||||
write_files(results['files'])
|
||||
except:
|
||||
util.logexc(LOG, "Failed writing files")
|
||||
if prev_iid != cur_iid and self.dsmode == "local":
|
||||
self.helper.on_first_boot(results)
|
||||
|
||||
# dsmode != self.dsmode here if:
|
||||
# * dsmode = "pass", pass means it should only copy files and then
|
||||
@ -338,6 +347,13 @@ def read_config_drive_dir_v2(source_dir, version="2012-08-10"):
|
||||
except KeyError:
|
||||
raise BrokenConfigDriveDir("No uuid entry in metadata")
|
||||
|
||||
if 'random_seed' in results['metadata']:
|
||||
random_seed = results['metadata']['random_seed']
|
||||
try:
|
||||
results['metadata']['random_seed'] = base64.b64decode(random_seed)
|
||||
except (ValueError, TypeError) as exc:
|
||||
raise BrokenConfigDriveDir("Badly formatted random_seed: %s" % exc)
|
||||
|
||||
def read_content_path(item):
|
||||
# do not use os.path.join here, as content_path starts with /
|
||||
cpath = os.path.sep.join((source_dir, "openstack",
|
||||
|
@ -375,7 +375,9 @@ class Init(object):
|
||||
mod = importer.import_module(mod_locs[0])
|
||||
mod = handlers.fixup_handler(mod)
|
||||
types = c_handlers.register(mod)
|
||||
LOG.debug("Added handler for %s from %s", types, fname)
|
||||
if types:
|
||||
LOG.debug("Added custom handler for %s from %s",
|
||||
types, fname)
|
||||
except Exception:
|
||||
util.logexc(LOG, "Failed to register handler from %s",
|
||||
fname)
|
||||
@ -386,10 +388,10 @@ class Init(object):
|
||||
# Register any other handlers that come from the default set. This
|
||||
# is done after the cloud-dir handlers so that the cdir modules can
|
||||
# take over the default user-data handler content-types.
|
||||
def_handlers = self._default_userdata_handlers()
|
||||
applied_def_handlers = c_handlers.register_defaults(def_handlers)
|
||||
if applied_def_handlers:
|
||||
LOG.debug("Registered default handlers: %s", applied_def_handlers)
|
||||
for mod in self._default_userdata_handlers():
|
||||
types = c_handlers.register(mod, overwrite=False)
|
||||
if types:
|
||||
LOG.debug("Added default handler for %s from %s", types, mod)
|
||||
|
||||
# Form our cloud interface
|
||||
data = self.cloudify()
|
||||
|
@ -1798,15 +1798,19 @@ def log_time(logfunc, msg, func, args=None, kwargs=None, get_uptime=False):
|
||||
ret = func(*args, **kwargs)
|
||||
finally:
|
||||
delta = time.time() - start
|
||||
udelta = None
|
||||
if ustart is not None:
|
||||
try:
|
||||
udelta = float(uptime()) - ustart
|
||||
except ValueError:
|
||||
udelta = "N/A"
|
||||
pass
|
||||
|
||||
tmsg = " took %0.3f seconds" % delta
|
||||
if get_uptime:
|
||||
tmsg += "(%0.2f)" % udelta
|
||||
if isinstance(udelta, (float)):
|
||||
tmsg += " (%0.2f)" % udelta
|
||||
else:
|
||||
tmsg += " (N/A)"
|
||||
try:
|
||||
logfunc(msg + tmsg)
|
||||
except:
|
||||
|
@ -24,6 +24,7 @@ preserve_hostname: false
|
||||
# The modules that run in the 'init' stage
|
||||
cloud_init_modules:
|
||||
- migrator
|
||||
- seed_random
|
||||
- bootcmd
|
||||
- write-files
|
||||
- growpart
|
||||
|
150
tests/unittests/test_handler/test_handler_seed_random.py
Normal file
150
tests/unittests/test_handler/test_handler_seed_random.py
Normal file
@ -0,0 +1,150 @@
|
||||
# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Author: Juerg Haefliger <juerg.haefliger@hp.com>
|
||||
#
|
||||
# Based on test_handler_set_hostname.py
|
||||
#
|
||||
# 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
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from cloudinit.config import cc_seed_random
|
||||
|
||||
import base64
|
||||
import tempfile
|
||||
import gzip
|
||||
|
||||
from StringIO import StringIO
|
||||
|
||||
from cloudinit import cloud
|
||||
from cloudinit import distros
|
||||
from cloudinit import helpers
|
||||
from cloudinit import util
|
||||
|
||||
from cloudinit.sources import DataSourceNone
|
||||
|
||||
from tests.unittests import helpers as t_help
|
||||
|
||||
import logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TestRandomSeed(t_help.TestCase):
|
||||
def setUp(self):
|
||||
super(TestRandomSeed, self).setUp()
|
||||
self._seed_file = tempfile.mktemp()
|
||||
|
||||
def tearDown(self):
|
||||
util.del_file(self._seed_file)
|
||||
|
||||
def _compress(self, text):
|
||||
contents = StringIO()
|
||||
gz_fh = gzip.GzipFile(mode='wb', fileobj=contents)
|
||||
gz_fh.write(text)
|
||||
gz_fh.close()
|
||||
return contents.getvalue()
|
||||
|
||||
def _get_cloud(self, distro, metadata=None):
|
||||
paths = helpers.Paths({})
|
||||
cls = distros.fetch(distro)
|
||||
ubuntu_distro = cls(distro, {}, paths)
|
||||
ds = DataSourceNone.DataSourceNone({}, ubuntu_distro, paths)
|
||||
if metadata:
|
||||
ds.metadata = metadata
|
||||
return cloud.Cloud(ds, paths, {}, ubuntu_distro, None)
|
||||
|
||||
def test_append_random(self):
|
||||
cfg = {
|
||||
'random_seed': {
|
||||
'file': self._seed_file,
|
||||
'data': 'tiny-tim-was-here',
|
||||
}
|
||||
}
|
||||
cc_seed_random.handle('test', cfg, self._get_cloud('ubuntu'), LOG, [])
|
||||
contents = util.load_file(self._seed_file)
|
||||
self.assertEquals("tiny-tim-was-here", contents)
|
||||
|
||||
def test_append_random_unknown_encoding(self):
|
||||
data = self._compress("tiny-toe")
|
||||
cfg = {
|
||||
'random_seed': {
|
||||
'file': self._seed_file,
|
||||
'data': data,
|
||||
'encoding': 'special_encoding',
|
||||
}
|
||||
}
|
||||
self.assertRaises(IOError, cc_seed_random.handle, 'test', cfg,
|
||||
self._get_cloud('ubuntu'), LOG, [])
|
||||
|
||||
def test_append_random_gzip(self):
|
||||
data = self._compress("tiny-toe")
|
||||
cfg = {
|
||||
'random_seed': {
|
||||
'file': self._seed_file,
|
||||
'data': data,
|
||||
'encoding': 'gzip',
|
||||
}
|
||||
}
|
||||
cc_seed_random.handle('test', cfg, self._get_cloud('ubuntu'), LOG, [])
|
||||
contents = util.load_file(self._seed_file)
|
||||
self.assertEquals("tiny-toe", contents)
|
||||
|
||||
def test_append_random_gz(self):
|
||||
data = self._compress("big-toe")
|
||||
cfg = {
|
||||
'random_seed': {
|
||||
'file': self._seed_file,
|
||||
'data': data,
|
||||
'encoding': 'gz',
|
||||
}
|
||||
}
|
||||
cc_seed_random.handle('test', cfg, self._get_cloud('ubuntu'), LOG, [])
|
||||
contents = util.load_file(self._seed_file)
|
||||
self.assertEquals("big-toe", contents)
|
||||
|
||||
def test_append_random_base64(self):
|
||||
data = base64.b64encode('bubbles')
|
||||
cfg = {
|
||||
'random_seed': {
|
||||
'file': self._seed_file,
|
||||
'data': data,
|
||||
'encoding': 'base64',
|
||||
}
|
||||
}
|
||||
cc_seed_random.handle('test', cfg, self._get_cloud('ubuntu'), LOG, [])
|
||||
contents = util.load_file(self._seed_file)
|
||||
self.assertEquals("bubbles", contents)
|
||||
|
||||
def test_append_random_b64(self):
|
||||
data = base64.b64encode('kit-kat')
|
||||
cfg = {
|
||||
'random_seed': {
|
||||
'file': self._seed_file,
|
||||
'data': data,
|
||||
'encoding': 'b64',
|
||||
}
|
||||
}
|
||||
cc_seed_random.handle('test', cfg, self._get_cloud('ubuntu'), LOG, [])
|
||||
contents = util.load_file(self._seed_file)
|
||||
self.assertEquals("kit-kat", contents)
|
||||
|
||||
def test_append_random_metadata(self):
|
||||
cfg = {
|
||||
'random_seed': {
|
||||
'file': self._seed_file,
|
||||
'data': 'tiny-tim-was-here',
|
||||
}
|
||||
}
|
||||
c = self._get_cloud('ubuntu', {'random_seed': '-so-was-josh'})
|
||||
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)
|
@ -12,20 +12,20 @@ find_root() {
|
||||
[ $? -eq 0 -a -f "${topd}/setup.py" ] || return
|
||||
ROOT_DIR="$topd"
|
||||
}
|
||||
fail() { echo "$0:" "$@" 1>&2; exit 1; }
|
||||
|
||||
if ! find_root; then
|
||||
echo "Unable to locate 'setup.py' file that should" \
|
||||
"exist in the cloud-init root directory." 1>&2
|
||||
exit 1;
|
||||
fail "Unable to locate 'setup.py' file that should " \
|
||||
"exist in the cloud-init root directory."
|
||||
fi
|
||||
|
||||
REQUIRES="$ROOT_DIR/Requires"
|
||||
|
||||
if [ ! -e "$REQUIRES" ]; then
|
||||
echo "Unable to find 'Requires' file located at $REQUIRES"
|
||||
exit 1
|
||||
fail "Unable to find 'Requires' file located at '$REQUIRES'"
|
||||
fi
|
||||
|
||||
# Filter out comments and empty liens
|
||||
DEPS=$(grep -Pv "^\s*#" "$REQUIRES" | grep -Pv '^\s*$')
|
||||
# Filter out comments and empty lines
|
||||
DEPS=$(sed -n -e 's,#.*,,' -e '/./p' "$REQUIRES") ||
|
||||
fail "failed to read deps from '${REQUIRES}'"
|
||||
echo "$DEPS" | sort -d -f
|
||||
|
@ -12,20 +12,20 @@ find_root() {
|
||||
[ $? -eq 0 -a -f "${topd}/setup.py" ] || return
|
||||
ROOT_DIR="$topd"
|
||||
}
|
||||
fail() { echo "$0:" "$@" 1>&2; exit 1; }
|
||||
|
||||
if ! find_root; then
|
||||
echo "Unable to locate 'setup.py' file that should" \
|
||||
"exist in the cloud-init root directory." 1>&2
|
||||
exit 1;
|
||||
fail "Unable to locate 'setup.py' file that should " \
|
||||
"exist in the cloud-init root directory."
|
||||
fi
|
||||
|
||||
CHNG_LOG="$ROOT_DIR/ChangeLog"
|
||||
|
||||
if [ ! -e "$CHNG_LOG" ]
|
||||
then
|
||||
echo "Unable to find 'ChangeLog' file located at $CHNG_LOG"
|
||||
exit 1
|
||||
if [ ! -e "$CHNG_LOG" ]; then
|
||||
fail "Unable to find 'ChangeLog' file located at '$CHNG_LOG'"
|
||||
fi
|
||||
|
||||
VERSION=$(grep -P "\d+.\d+.\d+:" "$CHNG_LOG" | cut -f1 -d ":" | head -n 1)
|
||||
VERSION=$(sed -n '/^[0-9]\+[.][0-9]\+[.][0-9]\+:/ {s/://; p; :a;n; ba}' \
|
||||
"$CHNG_LOG") ||
|
||||
fail "failed to get version from '$CHNG_LOG'"
|
||||
echo "$VERSION"
|
||||
|
Loading…
x
Reference in New Issue
Block a user