Merge lp:~doanac/utah/server-cleanups into lp:utah
- server-cleanups
- Merge into dev
Status: | Merged |
---|---|
Approved by: | Javier Collado |
Approved revision: | 876 |
Merged at revision: | 873 |
Proposed branch: | lp:~doanac/utah/server-cleanups |
Merge into: | lp:utah |
Diff against target: |
873 lines (+427/-120) (has conflicts) 15 files modified
docs/source/reference.rst (+6/-0) tests/__init__.py (+16/-0) tests/common.py (+33/-0) tests/test_debs.py (+89/-0) tests/test_rsyslog.py (+6/-3) tests/test_ssh.py (+33/-0) tests/test_template.py (+50/-0) utah/provisioning/baremetal/bamboofeeder.py (+0/-1) utah/provisioning/baremetal/cobbler.py (+1/-4) utah/provisioning/debs.py (+59/-0) utah/provisioning/provisioning.py (+29/-50) utah/provisioning/rsyslog.py (+29/-9) utah/provisioning/ssh.py (+23/-18) utah/provisioning/vm/vm.py (+0/-35) utah/template.py (+53/-0) Text conflict in utah/provisioning/provisioning.py Text conflict in utah/provisioning/rsyslog.py |
To merge this branch: | bzr merge lp:~doanac/utah/server-cleanups |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Javier Collado (community) | Approve | ||
Review via email: mp+159705@code.launchpad.net |
Commit message
Description of the change
this shouldn't change any functionality. however, it cleans some things up so that a new feature I'm working on will be easier/cleaner to merge.
Max Brustkern (nuclearbob) wrote : | # |
Andy Doan (doanac) wrote : | # |
On 04/18/2013 03:50 PM, Max Brustkern wrote:
> I see two things that look like they'll work differently.
>
> If you try to upload files and some of them don't exist, the exception will be raised before uploading any of them instead of after. That seems like it's probably fine.
yeah, the results from the run don't change, so it seemed harmless.
> Also, the handling of exceptions related to weird provisioning possibilities for Libvirt goes away. I think that makes the class slightly less robust for situations that we're not seeing at all right now. I wonder if we will see them in the future. Probably not. Seems reasonable, but I haven't tested anything yet. If you have, we're probably in good shape.
the main difference was something I thought didn't make sense. In the
original code, you could specify !new, and if _load failed, we'd call
_create and try and correct your mistake for you. In the new code, that
would fail. alternatively it did a similar logic for new=True. I found
the logic confusing, not sure if it has practical uses?
Max Brustkern (nuclearbob) wrote : | # |
I think it served some purposes when I was testing weird gymnastics of the original implementation regarding non-automatical
Javier Collado (javier.collado) wrote : | # |
The changes look good and I have been able to run the pass runlist
successfully. Please find below a few comments:
- tests/test_
I think `_tmpdir` should be set in a `setupClass` method rather than in defined
as a global variable.
- utah/template.py
- _add_backslash_
`:rtype:` should be `string` and `:returns:` a description of what is
returned.
- as_buff
The backslash filter could be added to `as_buff` at import time like this:
def as_buff(template, **kw):
<code>
as_buff._env = ...
This way `as_buff` would be easier to read and the code would take care
only of the rendering, not the initialization.
flake8 output:
$ find . -name '*.py' | xargs flake8
./tests/
pep257 output:
$ find . -name '*.py' | xargs pep257
test_debs.py:38:4: PEP257 Exported definitions should have docstrings.
test_debs.py:56:4: PEP257 Exported definitions should have docstrings.
Andy Doan (doanac) wrote : | # |
I think I've addressed Javier's issues now.
Javier Collado (javier.collado) wrote : | # |
Thanks for the update. A few more comments:
- Test cases
The test cases are broken. It looks like the idea of initializing the template
environment at import time wasn't good for testing. The problem is the update
to `config.
`template.
I managed to make it work using something like this in `tests/common.py`:
from utah import template
...
def setUp():
....
config.
reload(
However, I don't like much this trick, so I'll let you decide if you prefer
this or revert to the original code in which the environment was created when
the first template was rendered.
- Documentation
- Please add ``utah.template`` to ``reference.rst``, so that it's included in
the documentation.
- Note that backslash characters must be escaped in the
`_add_
Anyway, this not really an issue since private methods aren present in the
documentation by default.
- 875. By Andy Doan
-
add new template module
- 876. By Andy Doan
-
fix broken test case
The test cases are broken. It looks like the idea of initializing the template
environment at import time wasn't good for testing. The problem is the update
to `config.template_ dir` in `tests/common.py` happens after
`template.as_buff. _env` is already set.
Andy Doan (doanac) wrote : | # |
> However, I don't like much this trick, so I'll let you decide if you prefer
> this or revert to the original code in which the environment was created when
> the first template was rendered.
yeah - i think my first way is probably cleaner. last two revno's 875 and 876 should fix.
Javier Collado (javier.collado) wrote : | # |
Thanks for all the updates. I think the changes are ready to be merged.
There's a small warning when building the documentation, but I'll fix it now
before merging:
server-
``utah.template``
----------------
Javier Collado (javier.collado) wrote : | # |
I needed to remove 'tests/__init__.py' since it was causing packaging problems.
If it's needed to have that file, we can address the issue in a separate merge
request.
Preview Diff
1 | === modified file 'docs/source/reference.rst' |
2 | --- docs/source/reference.rst 2013-03-29 17:00:50 +0000 |
3 | +++ docs/source/reference.rst 2013-04-23 17:57:25 +0000 |
4 | @@ -56,6 +56,12 @@ |
5 | .. automodule:: utah.timeout |
6 | :members: |
7 | |
8 | +``utah.template`` |
9 | +---------------- |
10 | + |
11 | +.. automodule:: utah.template |
12 | + :members: |
13 | + |
14 | ``utah.url`` |
15 | ---------------- |
16 | |
17 | |
18 | === added file 'tests/__init__.py' |
19 | --- tests/__init__.py 1970-01-01 00:00:00 +0000 |
20 | +++ tests/__init__.py 2013-04-23 17:57:25 +0000 |
21 | @@ -0,0 +1,16 @@ |
22 | +# Ubuntu Testing Automation Harness |
23 | +# Copyright 2013 Canonical Ltd. |
24 | + |
25 | +# This program is free software: you can redistribute it and/or modify it |
26 | +# under the terms of the GNU General Public License version 3, as published |
27 | +# by the Free Software Foundation. |
28 | + |
29 | +# This program is distributed in the hope that it will be useful, but |
30 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
31 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
32 | +# PURPOSE. See the GNU General Public License for more details. |
33 | + |
34 | +# You should have received a copy of the GNU General Public License along |
35 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
36 | + |
37 | +"""tests for UTAH server code""" |
38 | |
39 | === added file 'tests/common.py' |
40 | --- tests/common.py 1970-01-01 00:00:00 +0000 |
41 | +++ tests/common.py 2013-04-23 17:57:25 +0000 |
42 | @@ -0,0 +1,33 @@ |
43 | +# Ubuntu Testing Automation Harness |
44 | +# Copyright 2013 Canonical Ltd. |
45 | + |
46 | +# This program is free software: you can redistribute it and/or modify it |
47 | +# under the terms of the GNU General Public License version 3, as published |
48 | +# by the Free Software Foundation. |
49 | + |
50 | +# This program is distributed in the hope that it will be useful, but |
51 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
52 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
53 | +# PURPOSE. See the GNU General Public License for more details. |
54 | + |
55 | +# You should have received a copy of the GNU General Public License along |
56 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
57 | + |
58 | +"""Provide common functions and content for self tests.""" |
59 | + |
60 | +import shutil |
61 | +import tempfile |
62 | + |
63 | +from utah import config |
64 | + |
65 | + |
66 | +def setUp(): |
67 | + """setup function called for the duration of the test run.""" |
68 | + |
69 | + # we need a consitent template across all the runs |
70 | + config.template_dir = tempfile.mkdtemp(prefix='utah-templates') |
71 | + |
72 | + |
73 | +def tearDown(): |
74 | + """Clean up after ourselves.""" |
75 | + shutil.rmtree(config.template_dir) |
76 | |
77 | === added file 'tests/test_debs.py' |
78 | --- tests/test_debs.py 1970-01-01 00:00:00 +0000 |
79 | +++ tests/test_debs.py 2013-04-23 17:57:25 +0000 |
80 | @@ -0,0 +1,89 @@ |
81 | +# Ubuntu Testing Automation Harness |
82 | +# Copyright 2013 Canonical Ltd. |
83 | + |
84 | +# This program is free software: you can redistribute it and/or modify it |
85 | +# under the terms of the GNU General Public License version 3, as published |
86 | +# by the Free Software Foundation. |
87 | + |
88 | +# This program is distributed in the hope that it will be useful, but |
89 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
90 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
91 | +# PURPOSE. See the GNU General Public License for more details. |
92 | + |
93 | +# You should have received a copy of the GNU General Public License along |
94 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
95 | + |
96 | +"""Unit tests for the utah.provisioning.debs module.""" |
97 | + |
98 | +import os |
99 | +import shutil |
100 | +import tempfile |
101 | +import unittest |
102 | + |
103 | +from mock import patch |
104 | + |
105 | +from utah import config |
106 | +from utah.provisioning.exceptions import UTAHProvisioningException |
107 | +from utah.provisioning.debs import ( |
108 | + _schema_deb, |
109 | + get_client_debs, |
110 | +) |
111 | + |
112 | + |
113 | +class TestDebs(unittest.TestCase): |
114 | + |
115 | + """Tests the functions of the utah.provisioning.debs module.""" |
116 | + |
117 | + def setUp(self): |
118 | + """Set up a temp directory for test data.""" |
119 | + self.pkgdir = config.packagedir |
120 | + self.tmpdir = tempfile.mkdtemp(prefix='utah_debs') |
121 | + config.packagedir = self.tmpdir |
122 | + |
123 | + ver = 'FOO' |
124 | + |
125 | + class dummy(object): |
126 | + def __init__(self): |
127 | + self.installedVersion = ver |
128 | + self.cache = {'utah': dummy()} |
129 | + |
130 | + for pkg in ['common', 'client']: |
131 | + fname = 'utah-{}_{}_all.deb'.format(pkg, ver) |
132 | + pkg = os.path.join(self.tmpdir, fname) |
133 | + with open(pkg, 'w') as f: |
134 | + f.write('') |
135 | + |
136 | + def tearDown(self): |
137 | + """Clean up stuff from the setup function.""" |
138 | + config.packagedir = self.pkgdir |
139 | + shutil.rmtree(self.tmpdir) |
140 | + |
141 | + def _setup_schema(self, oldest, latest): |
142 | + pkgfmt = 'python-jsonschema_0.{}-0~ppa1_all.deb' |
143 | + |
144 | + for x in xrange(oldest, latest + 1): |
145 | + fname = pkgfmt.format(x) |
146 | + with open(os.path.join(self.tmpdir, fname), 'w') as f: |
147 | + f.write('') |
148 | + return pkgfmt.format(latest) |
149 | + |
150 | + def test_missing_schema(self): |
151 | + """Ensure exception is raised when missing schema deb.""" |
152 | + self.assertRaises(UTAHProvisioningException, _schema_deb) |
153 | + |
154 | + def test_schema_deb(self): |
155 | + """Check that we return the latest schema deb.""" |
156 | + |
157 | + latest = self._setup_schema(1, 3) |
158 | + |
159 | + #ensure we got the latest version of the schema |
160 | + deb = _schema_deb() |
161 | + self.assertEqual(latest, os.path.basename(deb)) |
162 | + |
163 | + @patch('apt.cache.Cache') |
164 | + def test_client_debs(self, cache): |
165 | + """Ensure get_client_debs API works.""" |
166 | + |
167 | + cache.side_effect = [self.cache, self.cache] |
168 | + self._setup_schema(2, 4) |
169 | + self.assertEqual(len(get_client_debs()), 3) |
170 | |
171 | === modified file 'tests/test_rsyslog.py' |
172 | --- tests/test_rsyslog.py 2013-04-22 11:14:38 +0000 |
173 | +++ tests/test_rsyslog.py 2013-04-23 17:57:25 +0000 |
174 | @@ -131,8 +131,9 @@ |
175 | r = RSyslog("utah-test", '/tmp') |
176 | threading.Thread(target=self.producer, args=(r.port, messages)).start() |
177 | |
178 | - def booted_cb(): |
179 | + def booted_cb(match): |
180 | self.test_future_booted = True |
181 | + self.assertEqual(match.string.rstrip(), steps[1]['message']) |
182 | |
183 | self.test_future_booted = False |
184 | r.wait_for_install(steps, booted_cb) |
185 | @@ -169,11 +170,13 @@ |
186 | r = RSyslog("utah-test", '/tmp') |
187 | threading.Thread(target=self.producer, args=(r.port, messages)).start() |
188 | |
189 | - def booted_cb(): |
190 | + def booted_cb(match): |
191 | self.test_callbacks_booted = True |
192 | + self.assertEqual(match.string.rstrip(), messages[1]) |
193 | |
194 | - def blah_cb(): |
195 | + def blah_cb(match): |
196 | self.test_callbacks_blah = True |
197 | + self.assertEqual(match.string.rstrip(), messages[-1]) |
198 | |
199 | self.test_callbacks_booted = self.test_callbacks_blah = False |
200 | callbacks = { |
201 | |
202 | === added file 'tests/test_ssh.py' |
203 | --- tests/test_ssh.py 1970-01-01 00:00:00 +0000 |
204 | +++ tests/test_ssh.py 2013-04-23 17:57:25 +0000 |
205 | @@ -0,0 +1,33 @@ |
206 | +# Ubuntu Testing Automation Harness |
207 | +# Copyright 2013 Canonical Ltd. |
208 | + |
209 | +# This program is free software: you can redistribute it and/or modify it |
210 | +# under the terms of the GNU General Public License version 3, as published |
211 | +# by the Free Software Foundation. |
212 | + |
213 | +# This program is distributed in the hope that it will be useful, but |
214 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
215 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
216 | +# PURPOSE. See the GNU General Public License for more details. |
217 | + |
218 | +# You should have received a copy of the GNU General Public License along |
219 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
220 | + |
221 | +"""Simple tests for the SSHMixin Object.""" |
222 | + |
223 | +import unittest |
224 | + |
225 | +from utah.provisioning.exceptions import UTAHProvisioningException |
226 | +from utah.provisioning.ssh import SSHMixin |
227 | + |
228 | + |
229 | +class TestSSHMixin(unittest.TestCase): |
230 | + |
231 | + """Performs testing of our SSHMixin class.""" |
232 | + |
233 | + def test_missing_files(self): |
234 | + """Make sure we exit *before* trying to provision if missing files.""" |
235 | + ssh = SSHMixin() |
236 | + fname = '/this does not exist' |
237 | + with self.assertRaisesRegexp(UTAHProvisioningException, fname): |
238 | + ssh.uploadfiles(fname, '/tmp/') |
239 | |
240 | === added file 'tests/test_template.py' |
241 | --- tests/test_template.py 1970-01-01 00:00:00 +0000 |
242 | +++ tests/test_template.py 2013-04-23 17:57:25 +0000 |
243 | @@ -0,0 +1,50 @@ |
244 | +# Ubuntu Testing Automation Harness |
245 | +# Copyright 2013 Canonical Ltd. |
246 | + |
247 | +# This program is free software: you can redistribute it and/or modify it |
248 | +# under the terms of the GNU General Public License version 3, as published |
249 | +# by the Free Software Foundation. |
250 | + |
251 | +# This program is distributed in the hope that it will be useful, but |
252 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
253 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
254 | +# PURPOSE. See the GNU General Public License for more details. |
255 | + |
256 | +# You should have received a copy of the GNU General Public License along |
257 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
258 | + |
259 | +"""Module to test our template utility.""" |
260 | + |
261 | +import os |
262 | +import unittest |
263 | + |
264 | +from utah import config |
265 | +from utah import template |
266 | + |
267 | +from tests.common import ( # NOQA |
268 | + setUp, # Used by nosetests |
269 | + tearDown, # Used by nosetests |
270 | +) |
271 | + |
272 | + |
273 | +class TestTemplate(unittest.TestCase): |
274 | + |
275 | + """Unit test class for our template utility.""" |
276 | + |
277 | + def setUp(self): |
278 | + """Executed before each test_ method is called.""" |
279 | + with open(os.path.join(config.template_dir, 'tmp.jinja2'), 'w') as f: |
280 | + f.write('line1\nfoo={{foo}}') |
281 | + |
282 | + def test_as_buff(self): |
283 | + """minimal test to make sure the template api works.""" |
284 | + buff = template.as_buff('tmp.jinja2', foo='bar') |
285 | + self.assertEqual(buff, 'line1\nfoo=bar') |
286 | + |
287 | + def test_write(self): |
288 | + """minimal test to make sure the file write api works.""" |
289 | + self.setUp() |
290 | + f = os.path.join(config.template_dir, 'tmp.txt') |
291 | + template.write('tmp.jinja2', f, foo='bar') |
292 | + with open(f, 'r') as f: |
293 | + self.assertEqual(f.read(), 'line1\nfoo=bar') |
294 | |
295 | === modified file 'utah/provisioning/baremetal/bamboofeeder.py' |
296 | --- utah/provisioning/baremetal/bamboofeeder.py 2013-04-04 18:11:06 +0000 |
297 | +++ utah/provisioning/baremetal/bamboofeeder.py 2013-04-23 17:57:25 +0000 |
298 | @@ -284,7 +284,6 @@ |
299 | retry(self.sshcheck, logmethod=self.logger.info, |
300 | retry_timeout=config.checktimeout) |
301 | |
302 | - self.provisioned = True |
303 | self.active = True |
304 | self._uuid_check() |
305 | self.logger.info('System installed') |
306 | |
307 | === modified file 'utah/provisioning/baremetal/cobbler.py' |
308 | --- utah/provisioning/baremetal/cobbler.py 2013-04-10 15:26:26 +0000 |
309 | +++ utah/provisioning/baremetal/cobbler.py 2013-04-23 17:57:25 +0000 |
310 | @@ -84,8 +84,6 @@ |
311 | if self.name not in machines: |
312 | raise UTAHBMProvisioningException( |
313 | 'No machine named {} exists in cobbler'.format(self.name)) |
314 | - else: |
315 | - self.provisioned = True |
316 | |
317 | def _create(self): |
318 | """Install the OS on the machine.""" |
319 | @@ -218,12 +216,11 @@ |
320 | if self.installtype == 'desktop': |
321 | self._removenfs() |
322 | |
323 | - self.provisioned = True |
324 | self.active = True |
325 | self._uuid_check() |
326 | self.logger.info('System installed') |
327 | |
328 | - def _disable_netboot(self): |
329 | + def _disable_netboot(self, match): |
330 | self.logger.info('install seems to have booted, disabling netboot') |
331 | self._cobble(['system', 'edit', '--name={}'.format(self.name), |
332 | '--netboot-enabled=N']) |
333 | |
334 | === added file 'utah/provisioning/debs.py' |
335 | --- utah/provisioning/debs.py 1970-01-01 00:00:00 +0000 |
336 | +++ utah/provisioning/debs.py 2013-04-23 17:57:25 +0000 |
337 | @@ -0,0 +1,59 @@ |
338 | +# Ubuntu Testing Automation Harness |
339 | +# Copyright 2012 Canonical Ltd. |
340 | + |
341 | +# This program is free software: you can redistribute it and/or modify it |
342 | +# under the terms of the GNU General Public License version 3, as published |
343 | +# by the Free Software Foundation. |
344 | + |
345 | +# This program is distributed in the hope that it will be useful, but |
346 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
347 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
348 | +# PURPOSE. See the GNU General Public License for more details. |
349 | + |
350 | +# You should have received a copy of the GNU General Public License along |
351 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
352 | + |
353 | +"""Module provides paths to the utah client debs needed for install.""" |
354 | + |
355 | +import apt.cache |
356 | +import os |
357 | + |
358 | +from glob import glob |
359 | + |
360 | +from utah import config |
361 | +from utah.provisioning.exceptions import UTAHProvisioningException |
362 | + |
363 | + |
364 | +def _utah_deb(deb): |
365 | + utah_version = apt.cache.Cache()['utah'].installedVersion |
366 | + if utah_version is None: |
367 | + raise UTAHProvisioningException("UTAH package isn't installed") |
368 | + |
369 | + basename = ('utah-{}_{}_all.deb'.format(deb, utah_version)) |
370 | + path = os.path.join(config.packagedir, basename) |
371 | + if not os.path.isfile(path): |
372 | + raise UTAHProvisioningException( |
373 | + 'UTAH {} binary package file not found in {}'.format(path, deb)) |
374 | + return path |
375 | + |
376 | + |
377 | +def _schema_deb(): |
378 | + debpath = config.packagedir |
379 | + deb_file_glob = os.path.join(debpath, 'python-jsonschema_*_all.deb') |
380 | + deb_files = glob(deb_file_glob) |
381 | + if not deb_files: |
382 | + msg = 'python-jsonschema package file not found in {}'.format(debpath) |
383 | + raise UTAHProvisioningException(msg) |
384 | + deb_files.sort() |
385 | + return deb_files[-1] |
386 | + |
387 | + |
388 | +def get_client_debs(): |
389 | + """Return paths to all .debs required to install the utah-client. |
390 | + |
391 | + :returns: Local paths to deb files |
392 | + :rtype: list |
393 | + :raises UTAHProvisioningException: When package(s) can't be found |
394 | + |
395 | + """ |
396 | + return [_schema_deb(), _utah_deb('common'), _utah_deb('client')] |
397 | |
398 | === modified file 'utah/provisioning/provisioning.py' |
399 | --- utah/provisioning/provisioning.py 2013-04-19 11:42:45 +0000 |
400 | +++ utah/provisioning/provisioning.py 2013-04-23 17:57:25 +0000 |
401 | @@ -38,10 +38,12 @@ |
402 | import utah.timeout |
403 | |
404 | from utah import config |
405 | +from utah import template |
406 | from utah.cleanup import cleanup |
407 | from utah.process import ProcessRunner |
408 | from utah.iso import ISO |
409 | from utah.preseed import Preseed |
410 | +from utah.provisioning.debs import get_client_debs |
411 | from utah.provisioning.rsyslog import RSyslog |
412 | from utah.provisioning.exceptions import UTAHProvisioningException |
413 | from utah.retry import retry |
414 | @@ -206,17 +208,6 @@ |
415 | path, getattr(self, item)) |
416 | |
417 | self.finalpreseed = self.preseed |
418 | - |
419 | - self.template_env = \ |
420 | - Environment(loader=FileSystemLoader(config.template_dir)) |
421 | - |
422 | - def add_backslash_filter(value): |
423 | - """Append backslash character to each line""" |
424 | - # TODO: consider moving this elsewhere |
425 | - return '\n'.join(['{}\\'.format(line) |
426 | - for line in value.splitlines()]) |
427 | - self.template_env.filters['add_backslash'] = add_backslash_filter |
428 | - |
429 | self.logger.debug('Machine init finished') |
430 | |
431 | @property |
432 | @@ -368,6 +359,7 @@ |
433 | utah.timeout.timeout(timeout, retry, self.pingcheck, |
434 | logmethod=logmethod, retry_timeout=checktimeout) |
435 | |
436 | +<<<<<<< TREE |
437 | def getutahdeb(self, deb): |
438 | """Return path of utah deb files. |
439 | |
440 | @@ -417,6 +409,8 @@ |
441 | deb_file = deb_files[0] |
442 | return deb_file |
443 | |
444 | +======= |
445 | +>>>>>>> MERGE-SOURCE |
446 | def installclient(self): |
447 | """Install the required packages on the machine. |
448 | |
449 | @@ -427,9 +421,7 @@ |
450 | """ |
451 | self.logger.info('Installing client deb on machine') |
452 | tmppath = os.path.normpath('/tmp') |
453 | - debs = [self.getjsonschemadeb(), self.getutahdeb('common'), |
454 | - self.getutahdeb('client')] |
455 | - for deb in debs: |
456 | + for deb in get_client_debs(): |
457 | try: |
458 | self.uploadfiles([deb], tmppath) |
459 | except UTAHProvisioningException as err: |
460 | @@ -446,11 +438,9 @@ |
461 | except AttributeError: |
462 | raise err |
463 | |
464 | - remote_deb = os.path.join(tmppath, os.path.basename(deb)) |
465 | - template = self.template_env.get_template( |
466 | - 'install-deb-command.jinja2') |
467 | - install_command = template.render(deb=remote_deb) |
468 | - returncode, _stdout, stderr = self.run(install_command, root=True) |
469 | + deb = os.path.join(tmppath, os.path.basename(deb)) |
470 | + cmd = template.as_buff('install-deb-command.jinja2', deb=deb) |
471 | + returncode, _stdout, stderr = self.run(cmd, root=True) |
472 | if (returncode != 0 or |
473 | re.search(r'script returned error exit status \d+', |
474 | stderr)): |
475 | @@ -468,20 +458,16 @@ |
476 | provisioncheck() or activecheck() should be used. |
477 | |
478 | """ |
479 | - # TODO: make sure provisioned is set consistently by |
480 | - # provisioncheck, _provision, and _load |
481 | - # i.e., right now CobblerMachine has a _load method that sets it |
482 | - # VMs have a separate _provision that does set it and a _load that |
483 | - # by necessity doesn't |
484 | - # Do we really need this function separate from provisioncheck? |
485 | - # Let's discuss this one |
486 | if not self.new: |
487 | try: |
488 | self.logger.debug('Trying to load existing machine') |
489 | self._load() |
490 | + self.provisioned = True |
491 | + return |
492 | except Exception as err: |
493 | self.logger.debug('Failed to load machine: %s', str(err)) |
494 | self._create() |
495 | + self.provisioned = True |
496 | |
497 | def _create(self): |
498 | # TODO: discuss separation of this vs. start when separating Install |
499 | @@ -814,23 +800,19 @@ |
500 | os.path.join(tmpdir, 'initrd.d', 'utah-ssh-key')) |
501 | |
502 | self.logger.info('Creating latecommand scripts') |
503 | - template = self.template_env.get_template('utah-latecommand.jinja2') |
504 | - latecommand = template.render(user=config.user, |
505 | - uuid=self.uuid, |
506 | - log_file='/target/var/log/utah-install') |
507 | filename = os.path.join(tmpdir, 'initrd.d', 'utah-latecommand') |
508 | - with open(filename, 'w') as f: |
509 | - f.write(latecommand) |
510 | + template.write('utah-latecommand.jinja2', |
511 | + filename, |
512 | + user=config.user, |
513 | + uuid=self.uuid, |
514 | + log_file='/target/var/log/utah-install') |
515 | |
516 | - template = self.template_env.get_template( |
517 | - 'utah-latecommand-in-target.jinja2') |
518 | - latecommand = template.render( |
519 | - packages=config.installpackages, |
520 | - log_file='/var/log/utah-install') |
521 | filename = os.path.join(tmpdir, 'initrd.d', |
522 | 'utah-latecommand-in-target') |
523 | - with open(filename, 'w') as f: |
524 | - f.write(latecommand) |
525 | + template.write('utah-latecommand-in-target.jinja2', |
526 | + filename, |
527 | + packages=config.installpackages, |
528 | + log_file='/var/log/utah-install') |
529 | |
530 | def _setuppreseed(self, tmpdir=None): |
531 | """Rewrite the preseed to automate installation and access.""" |
532 | @@ -884,12 +866,12 @@ |
533 | question.prepend('ubiquity ubiquity/summary note') |
534 | question.prepend('ubiquity ubiquity/reboot boolean true') |
535 | |
536 | - template = self.template_env.get_template('latecommand-wrapper.jinja2') |
537 | filename = os.path.join(tmpdir, 'initrd.d', 'latecommand-wrapper') |
538 | target_log_file = '/target{}'.format(log_file) |
539 | - with open(filename, 'w') as f: |
540 | - f.write(template.render(latecommand=question.value.text, |
541 | - log_file=target_log_file)) |
542 | + template.write('latecommand-wrapper.jinja2', |
543 | + filename, |
544 | + latecommand=question.value.text, |
545 | + log_file=target_log_file) |
546 | question.value = ( |
547 | 'sh latecommand-wrapper || ' |
548 | 'logger -s -t utah "Late command failure detected" ' |
549 | @@ -956,14 +938,12 @@ |
550 | self.logger.info('Inserting preseed into casper') |
551 | if tmpdir is None: |
552 | tmpdir = self.tmpdir |
553 | - template = self.template_env.get_template( |
554 | - 'casper-preseed-script.jinja2') |
555 | - preseedscript = template.render() |
556 | + |
557 | casper_dir = os.path.join(tmpdir, 'initrd.d', 'scripts', |
558 | 'casper-bottom') |
559 | + |
560 | filename = os.path.join(casper_dir, 'utah') |
561 | - with open(filename, 'w') as f: |
562 | - f.write(preseedscript) |
563 | + template.write('casper-preseed-script.jinja2', filename) |
564 | os.chmod(filename, 0755) |
565 | |
566 | orderfilename = os.path.join(casper_dir, 'ORDER') |
567 | @@ -1009,7 +989,6 @@ |
568 | "-f /var/log/syslog \n") |
569 | |
570 | self.logger.info('Creating rsyslog config file') |
571 | - template = self.template_env.get_template('50-utahdefault.conf.jinja2') |
572 | conffilename = os.path.join(tmpdir, 'initrd.d', '50-utahdefault.conf') |
573 | with open(conffilename, 'w') as f: |
574 | if self.rsyslog.port: |
575 | @@ -1018,7 +997,7 @@ |
576 | else: |
577 | self.logger.debug('setting up logging to go to serial console') |
578 | dest = '|/dev/ttyS0' |
579 | - f.write(template.render(dest=dest)) |
580 | + f.write(template.as_buff('50-utahdefault.conf.jinja2', dest=dest)) |
581 | |
582 | def _repackinitrd(self, tmpdir=None): |
583 | """Pack an initrd from our directory. |
584 | |
585 | === modified file 'utah/provisioning/rsyslog.py' |
586 | --- utah/provisioning/rsyslog.py 2013-04-22 10:59:26 +0000 |
587 | +++ utah/provisioning/rsyslog.py 2013-04-23 17:57:25 +0000 |
588 | @@ -132,7 +132,11 @@ |
589 | # i.e. LP#1100386 |
590 | try: |
591 | self._wait_for_steps(steps, logfile, callbacks) |
592 | +<<<<<<< TREE |
593 | except UTAHTimeout: |
594 | +======= |
595 | + except UTAHException: |
596 | +>>>>>>> MERGE-SOURCE |
597 | self.logger.warning('Timed out waiting for boot, but continuing') |
598 | |
599 | def _wait_for_steps(self, steps, logfile, callbacks): |
600 | @@ -152,6 +156,7 @@ |
601 | future_pats = self._future_patterns(steps, x) |
602 | pattern.extend(future_pats) |
603 | self.logger.info('Waiting %ds for: %s', timeout, message) |
604 | +<<<<<<< TREE |
605 | match = self._wait_for(f, pattern, message, timeout) |
606 | if match is None: |
607 | remaining_messages = [step['message'] |
608 | @@ -166,24 +171,28 @@ |
609 | self.logger.error(log_message) |
610 | raise UTAHTimeout(log_message) |
611 | if match in fail_pattern: |
612 | +======= |
613 | + pattern, match = self._wait_for(f, pattern, message, timeout) |
614 | + if pattern in fail_pattern: |
615 | +>>>>>>> MERGE-SOURCE |
616 | raise UTAHException('Failure pattern found: {}' |
617 | - .format(match)) |
618 | - if match in future_pats: |
619 | + .format(pattern)) |
620 | + if pattern in future_pats: |
621 | msg = 'Expected pattern missed, matched future pattern: %s' |
622 | self.logger.warn(msg, match) |
623 | - x = self._fast_forward(steps, match, callbacks) |
624 | + x = self._fast_forward(steps, pattern, match, callbacks) |
625 | else: |
626 | self.logger.info('Matched pattern %r for %r message', |
627 | - match, message) |
628 | + pattern, message) |
629 | |
630 | - self._do_callback(steps[x], callbacks) |
631 | + self._do_callback(steps[x], callbacks, match) |
632 | x += 1 |
633 | |
634 | @staticmethod |
635 | - def _do_callback(step, callbacks): |
636 | + def _do_callback(step, callbacks, match): |
637 | for name, func in callbacks.iteritems(): |
638 | if func and step.get(name, False): |
639 | - func() |
640 | + func(match) |
641 | |
642 | @staticmethod |
643 | def _future_patterns(steps, index): |
644 | @@ -198,7 +207,7 @@ |
645 | return patterns |
646 | |
647 | @staticmethod |
648 | - def _fast_forward(steps, pattern, callbacks): |
649 | + def _fast_forward(steps, pattern, match, callbacks): |
650 | """Figure out what should be the next step. |
651 | |
652 | Look through each item in the steps array to find the index of the |
653 | @@ -211,7 +220,7 @@ |
654 | x = 0 |
655 | while x < len(steps): |
656 | # make sure we don't skip a callback |
657 | - RSyslog._do_callback(steps[x], callbacks) |
658 | + RSyslog._do_callback(steps[x], callbacks, match) |
659 | |
660 | patterns = steps[x]['pattern'] |
661 | if not isinstance(patterns, list): |
662 | @@ -232,8 +241,19 @@ |
663 | sys.stderr.write(data) |
664 | writer.write(data) |
665 | for pat in pats: |
666 | +<<<<<<< TREE |
667 | if pat.match(data): |
668 | return pat.pattern |
669 | +======= |
670 | + match = pat.match(data) |
671 | + if match: |
672 | + return (pat.pattern, match) |
673 | + log_message = ('Timeout ({}) occurred for {!r} message' |
674 | + .format(timeout, message)) |
675 | + # Log error message to write the timestamp when the timeout happened |
676 | + self.logger.error(log_message) |
677 | + raise UTAHException(log_message) |
678 | +>>>>>>> MERGE-SOURCE |
679 | |
680 | def _read_udp(self): |
681 | data = None |
682 | |
683 | === modified file 'utah/provisioning/ssh.py' |
684 | --- utah/provisioning/ssh.py 2013-04-18 14:27:33 +0000 |
685 | +++ utah/provisioning/ssh.py 2013-04-23 17:57:25 +0000 |
686 | @@ -166,6 +166,18 @@ |
687 | |
688 | return retval, ''.join(stdout_lines), ''.join(stderr_lines) |
689 | |
690 | + @staticmethod |
691 | + def _check_files(files): |
692 | + failed = [] |
693 | + for f in files: |
694 | + if not os.path.isfile(f): |
695 | + failed.append(f) |
696 | + if len(failed): |
697 | + msg = 'Files do not exist: {}'.format(' '.join(failed)) |
698 | + err = UTAHProvisioningException(msg) |
699 | + err.files = failed |
700 | + raise err |
701 | + |
702 | def uploadfiles(self, files, target=os.path.normpath('/tmp/')): |
703 | """Copy a file or list of files to a target directory on the machine. |
704 | |
705 | @@ -178,8 +190,9 @@ |
706 | if isinstance(files, basestring): |
707 | files = [files] |
708 | |
709 | + self._check_files(files) |
710 | + |
711 | self.activecheck() |
712 | - failed = [] |
713 | sftp_client = None |
714 | try: |
715 | self.ssh_client.connect(self.name, |
716 | @@ -187,15 +200,12 @@ |
717 | key_filename=config.sshprivatekey) |
718 | sftp_client = self.ssh_client.open_sftp() |
719 | for localpath in files: |
720 | - if os.path.isfile(localpath): |
721 | - self.ssh_logger.info('Uploading {} from the host ' |
722 | - 'to {} on the machine' |
723 | - .format(localpath, target)) |
724 | - remotepath = os.path.join(target, |
725 | - os.path.basename(localpath)) |
726 | - sftp_client.put(localpath, remotepath) |
727 | - else: |
728 | - failed.append(localpath) |
729 | + self.ssh_logger.info( |
730 | + 'Uploading %s from the host to %s on the machine', |
731 | + localpath, |
732 | + target) |
733 | + remotepath = os.path.join(target, os.path.basename(localpath)) |
734 | + sftp_client.put(localpath, remotepath) |
735 | except paramiko.BadHostKeyException as err: |
736 | raise UTAHProvisioningException( |
737 | 'Host key exception encountered; ' |
738 | @@ -207,11 +217,6 @@ |
739 | finally: |
740 | if sftp_client: |
741 | sftp_client.close() |
742 | - if len(failed) > 0: |
743 | - err = UTAHProvisioningException('Files do not exist: {}' |
744 | - .format(' '.join(failed))) |
745 | - err.files = failed |
746 | - raise err |
747 | |
748 | def downloadfiles(self, files, target=os.path.normpath('/tmp/')): |
749 | """Copy a file or list of files from the machine to a local target. |
750 | @@ -382,6 +387,9 @@ |
751 | # No cleanup needed for systems that are already provisioned |
752 | self.clean = False |
753 | |
754 | + self.active = False |
755 | + self.provisioned = True |
756 | + |
757 | # System is expected to be available already, so there's no need to |
758 | # wait before trying to connect through ssh |
759 | self.check_timeout = 3 |
760 | @@ -391,9 +399,6 @@ |
761 | if installtype is None: |
762 | self.installtype = config.installtype |
763 | |
764 | - self.template_env = \ |
765 | - Environment(loader=FileSystemLoader(config.template_dir)) |
766 | - |
767 | def activecheck(self): |
768 | """Check if machine is active. |
769 | |
770 | |
771 | === modified file 'utah/provisioning/vm/vm.py' |
772 | --- utah/provisioning/vm/vm.py 2013-04-04 17:17:21 +0000 |
773 | +++ utah/provisioning/vm/vm.py 2013-04-23 17:57:25 +0000 |
774 | @@ -71,41 +71,6 @@ |
775 | self.vm = self.lv.lookupByName(self.name) |
776 | self.logger.info('VM loaded') |
777 | |
778 | - def _provision(self): |
779 | - """Make an existing VM available using libvirt to look up the VM.""" |
780 | - self.logger.info('Provisioning VM') |
781 | - if self.new: |
782 | - self.logger.debug('New VM requested') |
783 | - try: |
784 | - self._load() |
785 | - self.logger.error('VM already exists') |
786 | - raise UTAHVMProvisioningException( |
787 | - 'New VM requested, but {} already exists' |
788 | - .format(self.name)) |
789 | - except libvirt.libvirtError as err: |
790 | - if err.get_error_code() == 42: |
791 | - self._create() |
792 | - else: |
793 | - raise err |
794 | - |
795 | - try: |
796 | - self._load() |
797 | - except libvirt.libvirtError as err: |
798 | - if err.get_error_code() == 42: |
799 | - self.logger.debug('Lookup failed') |
800 | - try: |
801 | - self._create() |
802 | - self._load() |
803 | - except UTAHVMProvisioningException as error: |
804 | - self.logger.error('VM lookup failed') |
805 | - raise UTAHVMProvisioningException( |
806 | - 'Cannot find VM named {}: {}' |
807 | - .format(self.name, str(error))) |
808 | - else: |
809 | - raise err |
810 | - self.provisioned = True |
811 | - self.logger.info('VM provisioned') |
812 | - |
813 | def activecheck(self): |
814 | """Verify the machine is provisioned, then start it if needed. """ |
815 | self.logger.debug('Checking if VM is active') |
816 | |
817 | === added file 'utah/template.py' |
818 | --- utah/template.py 1970-01-01 00:00:00 +0000 |
819 | +++ utah/template.py 2013-04-23 17:57:25 +0000 |
820 | @@ -0,0 +1,53 @@ |
821 | +# Ubuntu Testing Automation Harness |
822 | +# Copyright 2013 Canonical Ltd. |
823 | + |
824 | +# This program is free software: you can redistribute it and/or modify it |
825 | +# under the terms of the GNU General Public License version 3, as published |
826 | +# by the Free Software Foundation. |
827 | + |
828 | +# This program is distributed in the hope that it will be useful, but |
829 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
830 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
831 | +# PURPOSE. See the GNU General Public License for more details. |
832 | + |
833 | +# You should have received a copy of the GNU General Public License along |
834 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
835 | + |
836 | +"""Provide easy accessors to the Jinja2 template library""" |
837 | + |
838 | +import os |
839 | + |
840 | +from jinja2 import Environment, FileSystemLoader |
841 | + |
842 | +from utah import config |
843 | + |
844 | + |
845 | +def _add_backslash_filter(value): |
846 | + r"""Append backslash character to each line. |
847 | + |
848 | + :returns: an version of the string where \\'s have been to each \\n |
849 | + :rtype: string |
850 | + |
851 | + """ |
852 | + return '\n'.join(['{}\\'.format(line) for line in value.splitlines()]) |
853 | + |
854 | + |
855 | +def as_buff(template, **kw): |
856 | + """Return the rendered template as a string.""" |
857 | + if not getattr(as_buff, '_env', None): |
858 | + paths = [ |
859 | + config.template_dir, |
860 | + os.path.join(os.path.dirname(__file__), '../templates'), |
861 | + ] |
862 | + as_buff._env = \ |
863 | + Environment(loader=FileSystemLoader(paths)) |
864 | + as_buff._env.filters['add_backslash'] = _add_backslash_filter |
865 | + template = as_buff._env.get_template(template) |
866 | + return template.render(kw) |
867 | + |
868 | + |
869 | +def write(template, path, **kw): |
870 | + """Render the given template as a file.""" |
871 | + content = as_buff(template, **kw) |
872 | + with open(path, 'w') as f: |
873 | + f.write(content) |
I see two things that look like they'll work differently.
If you try to upload files and some of them don't exist, the exception will be raised before uploading any of them instead of after. That seems like it's probably fine.
Also, the handling of exceptions related to weird provisioning possibilities for Libvirt goes away. I think that makes the class slightly less robust for situations that we're not seeing at all right now. I wonder if we will see them in the future. Probably not. Seems reasonable, but I haven't tested anything yet. If you have, we're probably in good shape.