cc_seed_random: fix bug and support pollinate command

there was a bug that prevented seeding of /dev/urandom from metadata provided
by the datasource unless the user provided random_seed config.
This should, instead, be the default behavior.
This commit is contained in:
Scott Moser 2014-03-03 15:01:18 -05:00
parent 40a2f859c1
commit 0f9df1ebba
2 changed files with 117 additions and 10 deletions

View File

@ -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
@ -17,12 +20,15 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64
import os
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 +44,50 @@ 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, env=None):
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')
seed_buf = StringIO()
seed_buf.write(_decode(my_cfg.get('data', ''),
encoding=my_cfg.get('encoding')))
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, env=env)
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()
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:
env = os.environ.copy()
env['RANDOM_SEED_FILE'] = seed_path
handle_random_seed_command(command=command, required=req, env=env)
except ValueError as e:
log.warn("handling random command [%s] failed: %s", command, e)
raise e

View File

@ -42,10 +42,32 @@ 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, **kwargs):
# supports subp calling with cmd as args or kwargs
if 'args' not in kwargs:
kwargs['args'] = args[0]
self.subp_called.append(kwargs)
return
def _compress(self, text):
contents = StringIO()
gz_fh = gzip.GzipFile(mode='wb', fileobj=contents)
@ -148,3 +170,56 @@ 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, [])
subp_args = [f['args'] for f in self.subp_called]
self.assertIn(['pollinate', '-q'], subp_args)
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.assertIn(['foo'], [f['args'] for f in self.subp_called])
def test_file_in_environment_for_command(self):
c = self._get_cloud('ubuntu', {})
self.whichdata = {'foo': 'foo'}
cfg = {'random_seed': {'command_required': True, 'command': ['foo'],
'file': self._seed_file}}
cc_seed_random.handle('test', cfg, c, LOG, [])
# this just instists that the first time subp was called,
# RANDOM_SEED_FILE was in the environment set up correctly
subp_env = [f['env'] for f in self.subp_called]
self.assertEqual(subp_env[0].get('RANDOM_SEED_FILE'), self._seed_file)
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