Merge lp:~harlowja/cloud-init/cloud-init-seedy into lp:~cloud-init-dev/cloud-init/trunk
- cloud-init-seedy
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 866 |
Proposed branch: | lp:~harlowja/cloud-init/cloud-init-seedy |
Merge into: | lp:~cloud-init-dev/cloud-init/trunk |
Diff against target: |
341 lines (+263/-14) 5 files modified
cloudinit/config/cc_seed_random.py (+61/-0) cloudinit/exceptions.py (+21/-0) cloudinit/sources/DataSourceConfigDrive.py (+30/-14) config/cloud.cfg (+1/-0) tests/unittests/test_handler/test_handler_seed_random.py (+150/-0) |
To merge this branch: | bzr merge lp:~harlowja/cloud-init/cloud-init-seedy |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
cloud-init Commiters | Pending | ||
Review via email: mp+183571@code.launchpad.net |
Commit message
Description of the change
Add config drive support for random_seed
A new field in the metadata has emerged, one
that provides a way to seed the linux random
generator. Add support for writing the seed
and rewrite parts of the on_boot code to use
a little helper class.
- 866. By Joshua Harlow
-
Raise when no seed filename provided
Scott Moser (smoser) wrote : | # |
Joshua Harlow (harlowja) wrote : | # |
Sounds good, also do u know if the file paths are right. The ones I found look right for ubunutu/rhel but second verification wouldn't hurt.
Scott Moser (smoser) wrote : | # |
Josh,
I actually think its simpler if you just write to /dev/urandom.
You should probably make that configurable via cloud-config. but just putting it into /dev/urandom correctly seeds the kernel entropy pool.
- 867. By Joshua Harlow
-
Review adjustments.
Joshua Harlow (harlowja) wrote : | # |
Ok dokie, adjustments made :) I should probably add a test though.
Scott Moser (smoser) wrote : | # |
It looks nice now. Some other thoughts:
* I don't know if I prefer cloud.distro.
You have thoughts on that? The latter allows me to change it easily from user-data.
* I wouldn't prefer the metadata from user-data, but rather just append them. the way /dev/urandom works is that "more is better".
* I guess we're expecting binary data to be in cfg.get(
* I *think* I'm moving towards getting stuff out of the top level of cloud_config and into specific dictionaries. Ie:
random_seed:
file: /dev/urandom
encoding: base64
data: Zm9vYmFyCg==
As opposed to:
random_
random_
random_seed: Zm9vYmFyCg==
* test would be good. :)
Thanks for doing this.
- 868. By Joshua Harlow
-
Add jsonschema for namespaced and verifiable module
configuration checking as well as make most of the
module logic happen in the module itself instead of
interacting with the distro object.
Joshua Harlow (harlowja) wrote : | # |
Ok I updated it with some new goodies. No tests yet, but let me know what u think :-)
- 869. By Joshua Harlow
-
Ensure validate checks key existence.
Scott Moser (smoser) wrote : | # |
Josh,
I like the direction here. I like the jsonschema, but I'm targetting 0.7.3 for later this month, and i'd rather not pick up the new dependency there. Could we drop the jsonschema stuff for now, and add it post 0.7.3 ?
I really do like the 'validate' idea though.
I think for now just take the 'validate' use out, and drop the dependency and we're probably good.
Of course a test would be nice :)
Thanks.
Joshua Harlow (harlowja) wrote : | # |
I, I captain, will do that (later tonight I hope).
- 870. By Joshua Harlow
-
Add test + remove jsonschema (for now)
Preview Diff
1 | === added file 'cloudinit/config/cc_seed_random.py' |
2 | --- cloudinit/config/cc_seed_random.py 1970-01-01 00:00:00 +0000 |
3 | +++ cloudinit/config/cc_seed_random.py 2013-09-09 05:48:24 +0000 |
4 | @@ -0,0 +1,61 @@ |
5 | +# vi: ts=4 expandtab |
6 | +# |
7 | +# Copyright (C) 2013 Yahoo! Inc. |
8 | +# |
9 | +# Author: Joshua Harlow <harlowja@yahoo-inc.com> |
10 | +# |
11 | +# This program is free software: you can redistribute it and/or modify |
12 | +# it under the terms of the GNU General Public License version 3, as |
13 | +# published by the Free Software Foundation. |
14 | +# |
15 | +# This program is distributed in the hope that it will be useful, |
16 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 | +# GNU General Public License for more details. |
19 | +# |
20 | +# You should have received a copy of the GNU General Public License |
21 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
22 | + |
23 | +import base64 |
24 | +from StringIO import StringIO |
25 | + |
26 | +from cloudinit.settings import PER_INSTANCE |
27 | +from cloudinit import util |
28 | + |
29 | +frequency = PER_INSTANCE |
30 | + |
31 | + |
32 | +def _decode(data, encoding=None): |
33 | + if not data: |
34 | + return '' |
35 | + if not encoding or encoding.lower() in ['raw']: |
36 | + return data |
37 | + elif encoding.lower() in ['base64', 'b64']: |
38 | + return base64.b64decode(data) |
39 | + elif encoding.lower() in ['gzip', 'gz']: |
40 | + return util.decomp_gzip(data, quiet=False) |
41 | + else: |
42 | + raise IOError("Unknown random_seed encoding: %s" % (encoding)) |
43 | + |
44 | + |
45 | +def handle(name, cfg, cloud, log, _args): |
46 | + if not cfg or "random_seed" not in cfg: |
47 | + log.debug(("Skipping module named %s, " |
48 | + "no 'random_seed' configuration found"), name) |
49 | + return |
50 | + |
51 | + my_cfg = cfg['random_seed'] |
52 | + seed_path = my_cfg.get('file', '/dev/urandom') |
53 | + seed_buf = StringIO() |
54 | + seed_buf.write(_decode(my_cfg.get('data', ''), |
55 | + encoding=my_cfg.get('encoding'))) |
56 | + |
57 | + metadata = cloud.datasource.metadata |
58 | + if metadata and 'random_seed' in metadata: |
59 | + seed_buf.write(metadata['random_seed']) |
60 | + |
61 | + seed_data = seed_buf.getvalue() |
62 | + if len(seed_data): |
63 | + log.debug("%s: adding %s bytes of random seed entrophy to %s", name, |
64 | + len(seed_data), seed_path) |
65 | + util.append_file(seed_path, seed_data) |
66 | |
67 | === added file 'cloudinit/exceptions.py' |
68 | --- cloudinit/exceptions.py 1970-01-01 00:00:00 +0000 |
69 | +++ cloudinit/exceptions.py 2013-09-09 05:48:24 +0000 |
70 | @@ -0,0 +1,21 @@ |
71 | +# vi: ts=4 expandtab |
72 | +# |
73 | +# Copyright (C) 2013 Yahoo! Inc. |
74 | +# |
75 | +# Author: Joshua Harlow <harlowja@yahoo-inc.com> |
76 | +# |
77 | +# This program is free software: you can redistribute it and/or modify |
78 | +# it under the terms of the GNU General Public License version 3, as |
79 | +# published by the Free Software Foundation. |
80 | +# |
81 | +# This program is distributed in the hope that it will be useful, |
82 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
83 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
84 | +# GNU General Public License for more details. |
85 | +# |
86 | +# You should have received a copy of the GNU General Public License |
87 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
88 | + |
89 | + |
90 | +class FormatValidationError(Exception): |
91 | + pass |
92 | |
93 | === modified file 'cloudinit/sources/DataSourceConfigDrive.py' |
94 | --- cloudinit/sources/DataSourceConfigDrive.py 2013-06-05 00:42:55 +0000 |
95 | +++ cloudinit/sources/DataSourceConfigDrive.py 2013-09-09 05:48:24 +0000 |
96 | @@ -18,6 +18,7 @@ |
97 | # You should have received a copy of the GNU General Public License |
98 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
99 | |
100 | +import base64 |
101 | import json |
102 | import os |
103 | |
104 | @@ -41,6 +42,25 @@ |
105 | VALID_DSMODES = ("local", "net", "pass", "disabled") |
106 | |
107 | |
108 | +class ConfigDriveHelper(object): |
109 | + def __init__(self, distro): |
110 | + self.distro = distro |
111 | + |
112 | + def on_first_boot(self, data): |
113 | + if not data: |
114 | + data = {} |
115 | + if 'network_config' in data: |
116 | + LOG.debug("Updating network interfaces from config drive") |
117 | + self.distro.apply_network(data['network_config']) |
118 | + files = data.get('files') |
119 | + if files: |
120 | + LOG.debug("Writing %s injected files", len(files)) |
121 | + try: |
122 | + write_files(files) |
123 | + except IOError: |
124 | + util.logexc(LOG, "Failed writing files") |
125 | + |
126 | + |
127 | class DataSourceConfigDrive(sources.DataSource): |
128 | def __init__(self, sys_cfg, distro, paths): |
129 | sources.DataSource.__init__(self, sys_cfg, distro, paths) |
130 | @@ -49,6 +69,7 @@ |
131 | self.seed_dir = os.path.join(paths.seed_dir, 'config_drive') |
132 | self.version = None |
133 | self.ec2_metadata = None |
134 | + self.helper = ConfigDriveHelper(distro) |
135 | |
136 | def __str__(self): |
137 | root = sources.DataSource.__str__(self) |
138 | @@ -187,20 +208,8 @@ |
139 | # instance-id |
140 | prev_iid = get_previous_iid(self.paths) |
141 | cur_iid = md['instance-id'] |
142 | - |
143 | - if ('network_config' in results and self.dsmode == "local" and |
144 | - prev_iid != cur_iid): |
145 | - LOG.debug("Updating network interfaces from config drive (%s)", |
146 | - dsmode) |
147 | - self.distro.apply_network(results['network_config']) |
148 | - |
149 | - # file writing occurs in local mode (to be as early as possible) |
150 | - if self.dsmode == "local" and prev_iid != cur_iid and results['files']: |
151 | - LOG.debug("writing injected files") |
152 | - try: |
153 | - write_files(results['files']) |
154 | - except: |
155 | - util.logexc(LOG, "Failed writing files") |
156 | + if prev_iid != cur_iid and self.dsmode == "local": |
157 | + self.helper.on_first_boot(results) |
158 | |
159 | # dsmode != self.dsmode here if: |
160 | # * dsmode = "pass", pass means it should only copy files and then |
161 | @@ -338,6 +347,13 @@ |
162 | except KeyError: |
163 | raise BrokenConfigDriveDir("No uuid entry in metadata") |
164 | |
165 | + if 'random_seed' in results['metadata']: |
166 | + random_seed = results['metadata']['random_seed'] |
167 | + try: |
168 | + results['metadata']['random_seed'] = base64.b64decode(random_seed) |
169 | + except (ValueError, TypeError) as exc: |
170 | + raise BrokenConfigDriveDir("Badly formatted random_seed: %s" % exc) |
171 | + |
172 | def read_content_path(item): |
173 | # do not use os.path.join here, as content_path starts with / |
174 | cpath = os.path.sep.join((source_dir, "openstack", |
175 | |
176 | === modified file 'config/cloud.cfg' |
177 | --- config/cloud.cfg 2013-03-01 06:43:06 +0000 |
178 | +++ config/cloud.cfg 2013-09-09 05:48:24 +0000 |
179 | @@ -24,6 +24,7 @@ |
180 | # The modules that run in the 'init' stage |
181 | cloud_init_modules: |
182 | - migrator |
183 | + - seed_random |
184 | - bootcmd |
185 | - write-files |
186 | - growpart |
187 | |
188 | === added file 'tests/unittests/test_handler/test_handler_seed_random.py' |
189 | --- tests/unittests/test_handler/test_handler_seed_random.py 1970-01-01 00:00:00 +0000 |
190 | +++ tests/unittests/test_handler/test_handler_seed_random.py 2013-09-09 05:48:24 +0000 |
191 | @@ -0,0 +1,150 @@ |
192 | + # Copyright (C) 2013 Hewlett-Packard Development Company, L.P. |
193 | +# |
194 | +# Author: Juerg Haefliger <juerg.haefliger@hp.com> |
195 | +# |
196 | +# Based on test_handler_set_hostname.py |
197 | +# |
198 | +# This program is free software: you can redistribute it and/or modify |
199 | +# it under the terms of the GNU General Public License version 3, as |
200 | +# published by the Free Software Foundation. |
201 | +# |
202 | +# This program is distributed in the hope that it will be useful, |
203 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
204 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
205 | +# GNU General Public License for more details. |
206 | +# |
207 | +# You should have received a copy of the GNU General Public License |
208 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
209 | + |
210 | +from cloudinit.config import cc_seed_random |
211 | + |
212 | +import base64 |
213 | +import tempfile |
214 | +import gzip |
215 | + |
216 | +from StringIO import StringIO |
217 | + |
218 | +from cloudinit import cloud |
219 | +from cloudinit import distros |
220 | +from cloudinit import helpers |
221 | +from cloudinit import util |
222 | + |
223 | +from cloudinit.sources import DataSourceNone |
224 | + |
225 | +from tests.unittests import helpers as t_help |
226 | + |
227 | +import logging |
228 | + |
229 | +LOG = logging.getLogger(__name__) |
230 | + |
231 | + |
232 | +class TestRandomSeed(t_help.TestCase): |
233 | + def setUp(self): |
234 | + super(TestRandomSeed, self).setUp() |
235 | + self._seed_file = tempfile.mktemp() |
236 | + |
237 | + def tearDown(self): |
238 | + util.del_file(self._seed_file) |
239 | + |
240 | + def _compress(self, text): |
241 | + contents = StringIO() |
242 | + gz_fh = gzip.GzipFile(mode='wb', fileobj=contents) |
243 | + gz_fh.write(text) |
244 | + gz_fh.close() |
245 | + return contents.getvalue() |
246 | + |
247 | + def _get_cloud(self, distro, metadata=None): |
248 | + paths = helpers.Paths({}) |
249 | + cls = distros.fetch(distro) |
250 | + ubuntu_distro = cls(distro, {}, paths) |
251 | + ds = DataSourceNone.DataSourceNone({}, ubuntu_distro, paths) |
252 | + if metadata: |
253 | + ds.metadata = metadata |
254 | + return cloud.Cloud(ds, paths, {}, ubuntu_distro, None) |
255 | + |
256 | + def test_append_random(self): |
257 | + cfg = { |
258 | + 'random_seed': { |
259 | + 'file': self._seed_file, |
260 | + 'data': 'tiny-tim-was-here', |
261 | + } |
262 | + } |
263 | + cc_seed_random.handle('test', cfg, self._get_cloud('ubuntu'), LOG, []) |
264 | + contents = util.load_file(self._seed_file) |
265 | + self.assertEquals("tiny-tim-was-here", contents) |
266 | + |
267 | + def test_append_random_unknown_encoding(self): |
268 | + data = self._compress("tiny-toe") |
269 | + cfg = { |
270 | + 'random_seed': { |
271 | + 'file': self._seed_file, |
272 | + 'data': data, |
273 | + 'encoding': 'special_encoding', |
274 | + } |
275 | + } |
276 | + self.assertRaises(IOError, cc_seed_random.handle, 'test', cfg, |
277 | + self._get_cloud('ubuntu'), LOG, []) |
278 | + |
279 | + def test_append_random_gzip(self): |
280 | + data = self._compress("tiny-toe") |
281 | + cfg = { |
282 | + 'random_seed': { |
283 | + 'file': self._seed_file, |
284 | + 'data': data, |
285 | + 'encoding': 'gzip', |
286 | + } |
287 | + } |
288 | + cc_seed_random.handle('test', cfg, self._get_cloud('ubuntu'), LOG, []) |
289 | + contents = util.load_file(self._seed_file) |
290 | + self.assertEquals("tiny-toe", contents) |
291 | + |
292 | + def test_append_random_gz(self): |
293 | + data = self._compress("big-toe") |
294 | + cfg = { |
295 | + 'random_seed': { |
296 | + 'file': self._seed_file, |
297 | + 'data': data, |
298 | + 'encoding': 'gz', |
299 | + } |
300 | + } |
301 | + cc_seed_random.handle('test', cfg, self._get_cloud('ubuntu'), LOG, []) |
302 | + contents = util.load_file(self._seed_file) |
303 | + self.assertEquals("big-toe", contents) |
304 | + |
305 | + def test_append_random_base64(self): |
306 | + data = base64.b64encode('bubbles') |
307 | + cfg = { |
308 | + 'random_seed': { |
309 | + 'file': self._seed_file, |
310 | + 'data': data, |
311 | + 'encoding': 'base64', |
312 | + } |
313 | + } |
314 | + cc_seed_random.handle('test', cfg, self._get_cloud('ubuntu'), LOG, []) |
315 | + contents = util.load_file(self._seed_file) |
316 | + self.assertEquals("bubbles", contents) |
317 | + |
318 | + def test_append_random_b64(self): |
319 | + data = base64.b64encode('kit-kat') |
320 | + cfg = { |
321 | + 'random_seed': { |
322 | + 'file': self._seed_file, |
323 | + 'data': data, |
324 | + 'encoding': 'b64', |
325 | + } |
326 | + } |
327 | + cc_seed_random.handle('test', cfg, self._get_cloud('ubuntu'), LOG, []) |
328 | + contents = util.load_file(self._seed_file) |
329 | + self.assertEquals("kit-kat", contents) |
330 | + |
331 | + def test_append_random_metadata(self): |
332 | + cfg = { |
333 | + 'random_seed': { |
334 | + 'file': self._seed_file, |
335 | + 'data': 'tiny-tim-was-here', |
336 | + } |
337 | + } |
338 | + c = self._get_cloud('ubuntu', {'random_seed': '-so-was-josh'}) |
339 | + cc_seed_random.handle('test', cfg, c, LOG, []) |
340 | + contents = util.load_file(self._seed_file) |
341 | + self.assertEquals('tiny-tim-was-here-so-was-josh', contents) |
What about just a cc_dev_random_seed that read from datasource. random_ seed() if available ?
The only reason I'd lean that way is that azure also provides random seed data.
We'd just want the cc_dev_random_seed to early in the init_modules.