Merge lp:~doanac/utah/server-cleanups into lp:utah

Proposed by Andy Doan
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
Reviewer Review Type Date Requested Status
Javier Collado (community) Approve
Review via email: mp+159705@code.launchpad.net

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.

To post a comment you must log in.
Revision history for this message
Max Brustkern (nuclearbob) 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.

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.

Revision history for this message
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?

Revision history for this message
Max Brustkern (nuclearbob) wrote :

I think it served some purposes when I was testing weird gymnastics of the original implementation regarding non-automatically-generated names, but I don't think it's really doing anything useful for us now.

Revision history for this message
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_template.py
I think `_tmpdir` should be set in a `setupClass` method rather than in defined
as a global variable.

- utah/template.py
  - _add_backslash_filter
    `: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/test_debs.py:18:1: F401 'apt' imported but unused

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.

lp:~doanac/utah/server-cleanups updated
872. By Andy Doan

improve test_template logic as per javier's review

873. By Andy Doan

template.py initialize improvement as per javier

874. By Andy Doan

clean up to test_debs.py as per javier

Revision history for this message
Andy Doan (doanac) wrote :

I think I've addressed Javier's issues now.

Revision history for this message
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_dir` in `tests/common.py` happens after
`template.as_buff._env` is already set.

I managed to make it work using something like this in `tests/common.py`:
from utah import template

...

def setUp():
    ....
    config.template_dir = ...
    reload(template)

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_backslash_filter` documentation string to be properly rendered.
    Anyway, this not really an issue since private methods aren present in the
    documentation by default.

lp:~doanac/utah/server-cleanups updated
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.

Revision history for this message
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.

Revision history for this message
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-cleanups/docs/source/reference.rst:60: WARNING: Title underline too short.

``utah.template``
----------------

review: Approve
Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'docs/source/reference.rst'
--- docs/source/reference.rst 2013-03-29 17:00:50 +0000
+++ docs/source/reference.rst 2013-04-23 17:57:25 +0000
@@ -56,6 +56,12 @@
56.. automodule:: utah.timeout56.. automodule:: utah.timeout
57 :members:57 :members:
5858
59``utah.template``
60----------------
61
62.. automodule:: utah.template
63 :members:
64
59``utah.url``65``utah.url``
60----------------66----------------
6167
6268
=== added file 'tests/__init__.py'
--- tests/__init__.py 1970-01-01 00:00:00 +0000
+++ tests/__init__.py 2013-04-23 17:57:25 +0000
@@ -0,0 +1,16 @@
1# Ubuntu Testing Automation Harness
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU General Public License version 3, as published
6# by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU General Public License for more details.
12
13# You should have received a copy of the GNU General Public License along
14# with this program. If not, see <http://www.gnu.org/licenses/>.
15
16"""tests for UTAH server code"""
017
=== added file 'tests/common.py'
--- tests/common.py 1970-01-01 00:00:00 +0000
+++ tests/common.py 2013-04-23 17:57:25 +0000
@@ -0,0 +1,33 @@
1# Ubuntu Testing Automation Harness
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU General Public License version 3, as published
6# by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU General Public License for more details.
12
13# You should have received a copy of the GNU General Public License along
14# with this program. If not, see <http://www.gnu.org/licenses/>.
15
16"""Provide common functions and content for self tests."""
17
18import shutil
19import tempfile
20
21from utah import config
22
23
24def setUp():
25 """setup function called for the duration of the test run."""
26
27 # we need a consitent template across all the runs
28 config.template_dir = tempfile.mkdtemp(prefix='utah-templates')
29
30
31def tearDown():
32 """Clean up after ourselves."""
33 shutil.rmtree(config.template_dir)
034
=== added file 'tests/test_debs.py'
--- tests/test_debs.py 1970-01-01 00:00:00 +0000
+++ tests/test_debs.py 2013-04-23 17:57:25 +0000
@@ -0,0 +1,89 @@
1# Ubuntu Testing Automation Harness
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU General Public License version 3, as published
6# by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU General Public License for more details.
12
13# You should have received a copy of the GNU General Public License along
14# with this program. If not, see <http://www.gnu.org/licenses/>.
15
16"""Unit tests for the utah.provisioning.debs module."""
17
18import os
19import shutil
20import tempfile
21import unittest
22
23from mock import patch
24
25from utah import config
26from utah.provisioning.exceptions import UTAHProvisioningException
27from utah.provisioning.debs import (
28 _schema_deb,
29 get_client_debs,
30)
31
32
33class TestDebs(unittest.TestCase):
34
35 """Tests the functions of the utah.provisioning.debs module."""
36
37 def setUp(self):
38 """Set up a temp directory for test data."""
39 self.pkgdir = config.packagedir
40 self.tmpdir = tempfile.mkdtemp(prefix='utah_debs')
41 config.packagedir = self.tmpdir
42
43 ver = 'FOO'
44
45 class dummy(object):
46 def __init__(self):
47 self.installedVersion = ver
48 self.cache = {'utah': dummy()}
49
50 for pkg in ['common', 'client']:
51 fname = 'utah-{}_{}_all.deb'.format(pkg, ver)
52 pkg = os.path.join(self.tmpdir, fname)
53 with open(pkg, 'w') as f:
54 f.write('')
55
56 def tearDown(self):
57 """Clean up stuff from the setup function."""
58 config.packagedir = self.pkgdir
59 shutil.rmtree(self.tmpdir)
60
61 def _setup_schema(self, oldest, latest):
62 pkgfmt = 'python-jsonschema_0.{}-0~ppa1_all.deb'
63
64 for x in xrange(oldest, latest + 1):
65 fname = pkgfmt.format(x)
66 with open(os.path.join(self.tmpdir, fname), 'w') as f:
67 f.write('')
68 return pkgfmt.format(latest)
69
70 def test_missing_schema(self):
71 """Ensure exception is raised when missing schema deb."""
72 self.assertRaises(UTAHProvisioningException, _schema_deb)
73
74 def test_schema_deb(self):
75 """Check that we return the latest schema deb."""
76
77 latest = self._setup_schema(1, 3)
78
79 #ensure we got the latest version of the schema
80 deb = _schema_deb()
81 self.assertEqual(latest, os.path.basename(deb))
82
83 @patch('apt.cache.Cache')
84 def test_client_debs(self, cache):
85 """Ensure get_client_debs API works."""
86
87 cache.side_effect = [self.cache, self.cache]
88 self._setup_schema(2, 4)
89 self.assertEqual(len(get_client_debs()), 3)
090
=== modified file 'tests/test_rsyslog.py'
--- tests/test_rsyslog.py 2013-04-22 11:14:38 +0000
+++ tests/test_rsyslog.py 2013-04-23 17:57:25 +0000
@@ -131,8 +131,9 @@
131 r = RSyslog("utah-test", '/tmp')131 r = RSyslog("utah-test", '/tmp')
132 threading.Thread(target=self.producer, args=(r.port, messages)).start()132 threading.Thread(target=self.producer, args=(r.port, messages)).start()
133133
134 def booted_cb():134 def booted_cb(match):
135 self.test_future_booted = True135 self.test_future_booted = True
136 self.assertEqual(match.string.rstrip(), steps[1]['message'])
136137
137 self.test_future_booted = False138 self.test_future_booted = False
138 r.wait_for_install(steps, booted_cb)139 r.wait_for_install(steps, booted_cb)
@@ -169,11 +170,13 @@
169 r = RSyslog("utah-test", '/tmp')170 r = RSyslog("utah-test", '/tmp')
170 threading.Thread(target=self.producer, args=(r.port, messages)).start()171 threading.Thread(target=self.producer, args=(r.port, messages)).start()
171172
172 def booted_cb():173 def booted_cb(match):
173 self.test_callbacks_booted = True174 self.test_callbacks_booted = True
175 self.assertEqual(match.string.rstrip(), messages[1])
174176
175 def blah_cb():177 def blah_cb(match):
176 self.test_callbacks_blah = True178 self.test_callbacks_blah = True
179 self.assertEqual(match.string.rstrip(), messages[-1])
177180
178 self.test_callbacks_booted = self.test_callbacks_blah = False181 self.test_callbacks_booted = self.test_callbacks_blah = False
179 callbacks = {182 callbacks = {
180183
=== added file 'tests/test_ssh.py'
--- tests/test_ssh.py 1970-01-01 00:00:00 +0000
+++ tests/test_ssh.py 2013-04-23 17:57:25 +0000
@@ -0,0 +1,33 @@
1# Ubuntu Testing Automation Harness
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU General Public License version 3, as published
6# by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU General Public License for more details.
12
13# You should have received a copy of the GNU General Public License along
14# with this program. If not, see <http://www.gnu.org/licenses/>.
15
16"""Simple tests for the SSHMixin Object."""
17
18import unittest
19
20from utah.provisioning.exceptions import UTAHProvisioningException
21from utah.provisioning.ssh import SSHMixin
22
23
24class TestSSHMixin(unittest.TestCase):
25
26 """Performs testing of our SSHMixin class."""
27
28 def test_missing_files(self):
29 """Make sure we exit *before* trying to provision if missing files."""
30 ssh = SSHMixin()
31 fname = '/this does not exist'
32 with self.assertRaisesRegexp(UTAHProvisioningException, fname):
33 ssh.uploadfiles(fname, '/tmp/')
034
=== added file 'tests/test_template.py'
--- tests/test_template.py 1970-01-01 00:00:00 +0000
+++ tests/test_template.py 2013-04-23 17:57:25 +0000
@@ -0,0 +1,50 @@
1# Ubuntu Testing Automation Harness
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU General Public License version 3, as published
6# by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU General Public License for more details.
12
13# You should have received a copy of the GNU General Public License along
14# with this program. If not, see <http://www.gnu.org/licenses/>.
15
16"""Module to test our template utility."""
17
18import os
19import unittest
20
21from utah import config
22from utah import template
23
24from tests.common import ( # NOQA
25 setUp, # Used by nosetests
26 tearDown, # Used by nosetests
27)
28
29
30class TestTemplate(unittest.TestCase):
31
32 """Unit test class for our template utility."""
33
34 def setUp(self):
35 """Executed before each test_ method is called."""
36 with open(os.path.join(config.template_dir, 'tmp.jinja2'), 'w') as f:
37 f.write('line1\nfoo={{foo}}')
38
39 def test_as_buff(self):
40 """minimal test to make sure the template api works."""
41 buff = template.as_buff('tmp.jinja2', foo='bar')
42 self.assertEqual(buff, 'line1\nfoo=bar')
43
44 def test_write(self):
45 """minimal test to make sure the file write api works."""
46 self.setUp()
47 f = os.path.join(config.template_dir, 'tmp.txt')
48 template.write('tmp.jinja2', f, foo='bar')
49 with open(f, 'r') as f:
50 self.assertEqual(f.read(), 'line1\nfoo=bar')
051
=== modified file 'utah/provisioning/baremetal/bamboofeeder.py'
--- utah/provisioning/baremetal/bamboofeeder.py 2013-04-04 18:11:06 +0000
+++ utah/provisioning/baremetal/bamboofeeder.py 2013-04-23 17:57:25 +0000
@@ -284,7 +284,6 @@
284 retry(self.sshcheck, logmethod=self.logger.info,284 retry(self.sshcheck, logmethod=self.logger.info,
285 retry_timeout=config.checktimeout)285 retry_timeout=config.checktimeout)
286286
287 self.provisioned = True
288 self.active = True287 self.active = True
289 self._uuid_check()288 self._uuid_check()
290 self.logger.info('System installed')289 self.logger.info('System installed')
291290
=== modified file 'utah/provisioning/baremetal/cobbler.py'
--- utah/provisioning/baremetal/cobbler.py 2013-04-10 15:26:26 +0000
+++ utah/provisioning/baremetal/cobbler.py 2013-04-23 17:57:25 +0000
@@ -84,8 +84,6 @@
84 if self.name not in machines:84 if self.name not in machines:
85 raise UTAHBMProvisioningException(85 raise UTAHBMProvisioningException(
86 'No machine named {} exists in cobbler'.format(self.name))86 'No machine named {} exists in cobbler'.format(self.name))
87 else:
88 self.provisioned = True
8987
90 def _create(self):88 def _create(self):
91 """Install the OS on the machine."""89 """Install the OS on the machine."""
@@ -218,12 +216,11 @@
218 if self.installtype == 'desktop':216 if self.installtype == 'desktop':
219 self._removenfs()217 self._removenfs()
220218
221 self.provisioned = True
222 self.active = True219 self.active = True
223 self._uuid_check()220 self._uuid_check()
224 self.logger.info('System installed')221 self.logger.info('System installed')
225222
226 def _disable_netboot(self):223 def _disable_netboot(self, match):
227 self.logger.info('install seems to have booted, disabling netboot')224 self.logger.info('install seems to have booted, disabling netboot')
228 self._cobble(['system', 'edit', '--name={}'.format(self.name),225 self._cobble(['system', 'edit', '--name={}'.format(self.name),
229 '--netboot-enabled=N'])226 '--netboot-enabled=N'])
230227
=== added file 'utah/provisioning/debs.py'
--- utah/provisioning/debs.py 1970-01-01 00:00:00 +0000
+++ utah/provisioning/debs.py 2013-04-23 17:57:25 +0000
@@ -0,0 +1,59 @@
1# Ubuntu Testing Automation Harness
2# Copyright 2012 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU General Public License version 3, as published
6# by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU General Public License for more details.
12
13# You should have received a copy of the GNU General Public License along
14# with this program. If not, see <http://www.gnu.org/licenses/>.
15
16"""Module provides paths to the utah client debs needed for install."""
17
18import apt.cache
19import os
20
21from glob import glob
22
23from utah import config
24from utah.provisioning.exceptions import UTAHProvisioningException
25
26
27def _utah_deb(deb):
28 utah_version = apt.cache.Cache()['utah'].installedVersion
29 if utah_version is None:
30 raise UTAHProvisioningException("UTAH package isn't installed")
31
32 basename = ('utah-{}_{}_all.deb'.format(deb, utah_version))
33 path = os.path.join(config.packagedir, basename)
34 if not os.path.isfile(path):
35 raise UTAHProvisioningException(
36 'UTAH {} binary package file not found in {}'.format(path, deb))
37 return path
38
39
40def _schema_deb():
41 debpath = config.packagedir
42 deb_file_glob = os.path.join(debpath, 'python-jsonschema_*_all.deb')
43 deb_files = glob(deb_file_glob)
44 if not deb_files:
45 msg = 'python-jsonschema package file not found in {}'.format(debpath)
46 raise UTAHProvisioningException(msg)
47 deb_files.sort()
48 return deb_files[-1]
49
50
51def get_client_debs():
52 """Return paths to all .debs required to install the utah-client.
53
54 :returns: Local paths to deb files
55 :rtype: list
56 :raises UTAHProvisioningException: When package(s) can't be found
57
58 """
59 return [_schema_deb(), _utah_deb('common'), _utah_deb('client')]
060
=== modified file 'utah/provisioning/provisioning.py'
--- utah/provisioning/provisioning.py 2013-04-19 11:42:45 +0000
+++ utah/provisioning/provisioning.py 2013-04-23 17:57:25 +0000
@@ -38,10 +38,12 @@
38import utah.timeout38import utah.timeout
3939
40from utah import config40from utah import config
41from utah import template
41from utah.cleanup import cleanup42from utah.cleanup import cleanup
42from utah.process import ProcessRunner43from utah.process import ProcessRunner
43from utah.iso import ISO44from utah.iso import ISO
44from utah.preseed import Preseed45from utah.preseed import Preseed
46from utah.provisioning.debs import get_client_debs
45from utah.provisioning.rsyslog import RSyslog47from utah.provisioning.rsyslog import RSyslog
46from utah.provisioning.exceptions import UTAHProvisioningException48from utah.provisioning.exceptions import UTAHProvisioningException
47from utah.retry import retry49from utah.retry import retry
@@ -206,17 +208,6 @@
206 path, getattr(self, item))208 path, getattr(self, item))
207209
208 self.finalpreseed = self.preseed210 self.finalpreseed = self.preseed
209
210 self.template_env = \
211 Environment(loader=FileSystemLoader(config.template_dir))
212
213 def add_backslash_filter(value):
214 """Append backslash character to each line"""
215 # TODO: consider moving this elsewhere
216 return '\n'.join(['{}\\'.format(line)
217 for line in value.splitlines()])
218 self.template_env.filters['add_backslash'] = add_backslash_filter
219
220 self.logger.debug('Machine init finished')211 self.logger.debug('Machine init finished')
221212
222 @property213 @property
@@ -368,6 +359,7 @@
368 utah.timeout.timeout(timeout, retry, self.pingcheck,359 utah.timeout.timeout(timeout, retry, self.pingcheck,
369 logmethod=logmethod, retry_timeout=checktimeout)360 logmethod=logmethod, retry_timeout=checktimeout)
370361
362<<<<<<< TREE
371 def getutahdeb(self, deb):363 def getutahdeb(self, deb):
372 """Return path of utah deb files.364 """Return path of utah deb files.
373365
@@ -417,6 +409,8 @@
417 deb_file = deb_files[0]409 deb_file = deb_files[0]
418 return deb_file410 return deb_file
419411
412=======
413>>>>>>> MERGE-SOURCE
420 def installclient(self):414 def installclient(self):
421 """Install the required packages on the machine.415 """Install the required packages on the machine.
422416
@@ -427,9 +421,7 @@
427 """421 """
428 self.logger.info('Installing client deb on machine')422 self.logger.info('Installing client deb on machine')
429 tmppath = os.path.normpath('/tmp')423 tmppath = os.path.normpath('/tmp')
430 debs = [self.getjsonschemadeb(), self.getutahdeb('common'),424 for deb in get_client_debs():
431 self.getutahdeb('client')]
432 for deb in debs:
433 try:425 try:
434 self.uploadfiles([deb], tmppath)426 self.uploadfiles([deb], tmppath)
435 except UTAHProvisioningException as err:427 except UTAHProvisioningException as err:
@@ -446,11 +438,9 @@
446 except AttributeError:438 except AttributeError:
447 raise err439 raise err
448440
449 remote_deb = os.path.join(tmppath, os.path.basename(deb))441 deb = os.path.join(tmppath, os.path.basename(deb))
450 template = self.template_env.get_template(442 cmd = template.as_buff('install-deb-command.jinja2', deb=deb)
451 'install-deb-command.jinja2')443 returncode, _stdout, stderr = self.run(cmd, root=True)
452 install_command = template.render(deb=remote_deb)
453 returncode, _stdout, stderr = self.run(install_command, root=True)
454 if (returncode != 0 or444 if (returncode != 0 or
455 re.search(r'script returned error exit status \d+',445 re.search(r'script returned error exit status \d+',
456 stderr)):446 stderr)):
@@ -468,20 +458,16 @@
468 provisioncheck() or activecheck() should be used.458 provisioncheck() or activecheck() should be used.
469459
470 """460 """
471 # TODO: make sure provisioned is set consistently by
472 # provisioncheck, _provision, and _load
473 # i.e., right now CobblerMachine has a _load method that sets it
474 # VMs have a separate _provision that does set it and a _load that
475 # by necessity doesn't
476 # Do we really need this function separate from provisioncheck?
477 # Let's discuss this one
478 if not self.new:461 if not self.new:
479 try:462 try:
480 self.logger.debug('Trying to load existing machine')463 self.logger.debug('Trying to load existing machine')
481 self._load()464 self._load()
465 self.provisioned = True
466 return
482 except Exception as err:467 except Exception as err:
483 self.logger.debug('Failed to load machine: %s', str(err))468 self.logger.debug('Failed to load machine: %s', str(err))
484 self._create()469 self._create()
470 self.provisioned = True
485471
486 def _create(self):472 def _create(self):
487 # TODO: discuss separation of this vs. start when separating Install473 # TODO: discuss separation of this vs. start when separating Install
@@ -814,23 +800,19 @@
814 os.path.join(tmpdir, 'initrd.d', 'utah-ssh-key'))800 os.path.join(tmpdir, 'initrd.d', 'utah-ssh-key'))
815801
816 self.logger.info('Creating latecommand scripts')802 self.logger.info('Creating latecommand scripts')
817 template = self.template_env.get_template('utah-latecommand.jinja2')
818 latecommand = template.render(user=config.user,
819 uuid=self.uuid,
820 log_file='/target/var/log/utah-install')
821 filename = os.path.join(tmpdir, 'initrd.d', 'utah-latecommand')803 filename = os.path.join(tmpdir, 'initrd.d', 'utah-latecommand')
822 with open(filename, 'w') as f:804 template.write('utah-latecommand.jinja2',
823 f.write(latecommand)805 filename,
806 user=config.user,
807 uuid=self.uuid,
808 log_file='/target/var/log/utah-install')
824809
825 template = self.template_env.get_template(
826 'utah-latecommand-in-target.jinja2')
827 latecommand = template.render(
828 packages=config.installpackages,
829 log_file='/var/log/utah-install')
830 filename = os.path.join(tmpdir, 'initrd.d',810 filename = os.path.join(tmpdir, 'initrd.d',
831 'utah-latecommand-in-target')811 'utah-latecommand-in-target')
832 with open(filename, 'w') as f:812 template.write('utah-latecommand-in-target.jinja2',
833 f.write(latecommand)813 filename,
814 packages=config.installpackages,
815 log_file='/var/log/utah-install')
834816
835 def _setuppreseed(self, tmpdir=None):817 def _setuppreseed(self, tmpdir=None):
836 """Rewrite the preseed to automate installation and access."""818 """Rewrite the preseed to automate installation and access."""
@@ -884,12 +866,12 @@
884 question.prepend('ubiquity ubiquity/summary note')866 question.prepend('ubiquity ubiquity/summary note')
885 question.prepend('ubiquity ubiquity/reboot boolean true')867 question.prepend('ubiquity ubiquity/reboot boolean true')
886868
887 template = self.template_env.get_template('latecommand-wrapper.jinja2')
888 filename = os.path.join(tmpdir, 'initrd.d', 'latecommand-wrapper')869 filename = os.path.join(tmpdir, 'initrd.d', 'latecommand-wrapper')
889 target_log_file = '/target{}'.format(log_file)870 target_log_file = '/target{}'.format(log_file)
890 with open(filename, 'w') as f:871 template.write('latecommand-wrapper.jinja2',
891 f.write(template.render(latecommand=question.value.text,872 filename,
892 log_file=target_log_file))873 latecommand=question.value.text,
874 log_file=target_log_file)
893 question.value = (875 question.value = (
894 'sh latecommand-wrapper || '876 'sh latecommand-wrapper || '
895 'logger -s -t utah "Late command failure detected" '877 'logger -s -t utah "Late command failure detected" '
@@ -956,14 +938,12 @@
956 self.logger.info('Inserting preseed into casper')938 self.logger.info('Inserting preseed into casper')
957 if tmpdir is None:939 if tmpdir is None:
958 tmpdir = self.tmpdir940 tmpdir = self.tmpdir
959 template = self.template_env.get_template(941
960 'casper-preseed-script.jinja2')
961 preseedscript = template.render()
962 casper_dir = os.path.join(tmpdir, 'initrd.d', 'scripts',942 casper_dir = os.path.join(tmpdir, 'initrd.d', 'scripts',
963 'casper-bottom')943 'casper-bottom')
944
964 filename = os.path.join(casper_dir, 'utah')945 filename = os.path.join(casper_dir, 'utah')
965 with open(filename, 'w') as f:946 template.write('casper-preseed-script.jinja2', filename)
966 f.write(preseedscript)
967 os.chmod(filename, 0755)947 os.chmod(filename, 0755)
968948
969 orderfilename = os.path.join(casper_dir, 'ORDER')949 orderfilename = os.path.join(casper_dir, 'ORDER')
@@ -1009,7 +989,6 @@
1009 "-f /var/log/syslog \n")989 "-f /var/log/syslog \n")
1010990
1011 self.logger.info('Creating rsyslog config file')991 self.logger.info('Creating rsyslog config file')
1012 template = self.template_env.get_template('50-utahdefault.conf.jinja2')
1013 conffilename = os.path.join(tmpdir, 'initrd.d', '50-utahdefault.conf')992 conffilename = os.path.join(tmpdir, 'initrd.d', '50-utahdefault.conf')
1014 with open(conffilename, 'w') as f:993 with open(conffilename, 'w') as f:
1015 if self.rsyslog.port:994 if self.rsyslog.port:
@@ -1018,7 +997,7 @@
1018 else:997 else:
1019 self.logger.debug('setting up logging to go to serial console')998 self.logger.debug('setting up logging to go to serial console')
1020 dest = '|/dev/ttyS0'999 dest = '|/dev/ttyS0'
1021 f.write(template.render(dest=dest))1000 f.write(template.as_buff('50-utahdefault.conf.jinja2', dest=dest))
10221001
1023 def _repackinitrd(self, tmpdir=None):1002 def _repackinitrd(self, tmpdir=None):
1024 """Pack an initrd from our directory.1003 """Pack an initrd from our directory.
10251004
=== modified file 'utah/provisioning/rsyslog.py'
--- utah/provisioning/rsyslog.py 2013-04-22 10:59:26 +0000
+++ utah/provisioning/rsyslog.py 2013-04-23 17:57:25 +0000
@@ -132,7 +132,11 @@
132 # i.e. LP#1100386132 # i.e. LP#1100386
133 try:133 try:
134 self._wait_for_steps(steps, logfile, callbacks)134 self._wait_for_steps(steps, logfile, callbacks)
135<<<<<<< TREE
135 except UTAHTimeout:136 except UTAHTimeout:
137=======
138 except UTAHException:
139>>>>>>> MERGE-SOURCE
136 self.logger.warning('Timed out waiting for boot, but continuing')140 self.logger.warning('Timed out waiting for boot, but continuing')
137141
138 def _wait_for_steps(self, steps, logfile, callbacks):142 def _wait_for_steps(self, steps, logfile, callbacks):
@@ -152,6 +156,7 @@
152 future_pats = self._future_patterns(steps, x)156 future_pats = self._future_patterns(steps, x)
153 pattern.extend(future_pats)157 pattern.extend(future_pats)
154 self.logger.info('Waiting %ds for: %s', timeout, message)158 self.logger.info('Waiting %ds for: %s', timeout, message)
159<<<<<<< TREE
155 match = self._wait_for(f, pattern, message, timeout)160 match = self._wait_for(f, pattern, message, timeout)
156 if match is None:161 if match is None:
157 remaining_messages = [step['message']162 remaining_messages = [step['message']
@@ -166,24 +171,28 @@
166 self.logger.error(log_message)171 self.logger.error(log_message)
167 raise UTAHTimeout(log_message)172 raise UTAHTimeout(log_message)
168 if match in fail_pattern:173 if match in fail_pattern:
174=======
175 pattern, match = self._wait_for(f, pattern, message, timeout)
176 if pattern in fail_pattern:
177>>>>>>> MERGE-SOURCE
169 raise UTAHException('Failure pattern found: {}'178 raise UTAHException('Failure pattern found: {}'
170 .format(match))179 .format(pattern))
171 if match in future_pats:180 if pattern in future_pats:
172 msg = 'Expected pattern missed, matched future pattern: %s'181 msg = 'Expected pattern missed, matched future pattern: %s'
173 self.logger.warn(msg, match)182 self.logger.warn(msg, match)
174 x = self._fast_forward(steps, match, callbacks)183 x = self._fast_forward(steps, pattern, match, callbacks)
175 else:184 else:
176 self.logger.info('Matched pattern %r for %r message',185 self.logger.info('Matched pattern %r for %r message',
177 match, message)186 pattern, message)
178187
179 self._do_callback(steps[x], callbacks)188 self._do_callback(steps[x], callbacks, match)
180 x += 1189 x += 1
181190
182 @staticmethod191 @staticmethod
183 def _do_callback(step, callbacks):192 def _do_callback(step, callbacks, match):
184 for name, func in callbacks.iteritems():193 for name, func in callbacks.iteritems():
185 if func and step.get(name, False):194 if func and step.get(name, False):
186 func()195 func(match)
187196
188 @staticmethod197 @staticmethod
189 def _future_patterns(steps, index):198 def _future_patterns(steps, index):
@@ -198,7 +207,7 @@
198 return patterns207 return patterns
199208
200 @staticmethod209 @staticmethod
201 def _fast_forward(steps, pattern, callbacks):210 def _fast_forward(steps, pattern, match, callbacks):
202 """Figure out what should be the next step.211 """Figure out what should be the next step.
203212
204 Look through each item in the steps array to find the index of the213 Look through each item in the steps array to find the index of the
@@ -211,7 +220,7 @@
211 x = 0220 x = 0
212 while x < len(steps):221 while x < len(steps):
213 # make sure we don't skip a callback222 # make sure we don't skip a callback
214 RSyslog._do_callback(steps[x], callbacks)223 RSyslog._do_callback(steps[x], callbacks, match)
215224
216 patterns = steps[x]['pattern']225 patterns = steps[x]['pattern']
217 if not isinstance(patterns, list):226 if not isinstance(patterns, list):
@@ -232,8 +241,19 @@
232 sys.stderr.write(data)241 sys.stderr.write(data)
233 writer.write(data)242 writer.write(data)
234 for pat in pats:243 for pat in pats:
244<<<<<<< TREE
235 if pat.match(data):245 if pat.match(data):
236 return pat.pattern246 return pat.pattern
247=======
248 match = pat.match(data)
249 if match:
250 return (pat.pattern, match)
251 log_message = ('Timeout ({}) occurred for {!r} message'
252 .format(timeout, message))
253 # Log error message to write the timestamp when the timeout happened
254 self.logger.error(log_message)
255 raise UTAHException(log_message)
256>>>>>>> MERGE-SOURCE
237257
238 def _read_udp(self):258 def _read_udp(self):
239 data = None259 data = None
240260
=== modified file 'utah/provisioning/ssh.py'
--- utah/provisioning/ssh.py 2013-04-18 14:27:33 +0000
+++ utah/provisioning/ssh.py 2013-04-23 17:57:25 +0000
@@ -166,6 +166,18 @@
166166
167 return retval, ''.join(stdout_lines), ''.join(stderr_lines)167 return retval, ''.join(stdout_lines), ''.join(stderr_lines)
168168
169 @staticmethod
170 def _check_files(files):
171 failed = []
172 for f in files:
173 if not os.path.isfile(f):
174 failed.append(f)
175 if len(failed):
176 msg = 'Files do not exist: {}'.format(' '.join(failed))
177 err = UTAHProvisioningException(msg)
178 err.files = failed
179 raise err
180
169 def uploadfiles(self, files, target=os.path.normpath('/tmp/')):181 def uploadfiles(self, files, target=os.path.normpath('/tmp/')):
170 """Copy a file or list of files to a target directory on the machine.182 """Copy a file or list of files to a target directory on the machine.
171183
@@ -178,8 +190,9 @@
178 if isinstance(files, basestring):190 if isinstance(files, basestring):
179 files = [files]191 files = [files]
180192
193 self._check_files(files)
194
181 self.activecheck()195 self.activecheck()
182 failed = []
183 sftp_client = None196 sftp_client = None
184 try:197 try:
185 self.ssh_client.connect(self.name,198 self.ssh_client.connect(self.name,
@@ -187,15 +200,12 @@
187 key_filename=config.sshprivatekey)200 key_filename=config.sshprivatekey)
188 sftp_client = self.ssh_client.open_sftp()201 sftp_client = self.ssh_client.open_sftp()
189 for localpath in files:202 for localpath in files:
190 if os.path.isfile(localpath):203 self.ssh_logger.info(
191 self.ssh_logger.info('Uploading {} from the host '204 'Uploading %s from the host to %s on the machine',
192 'to {} on the machine'205 localpath,
193 .format(localpath, target))206 target)
194 remotepath = os.path.join(target,207 remotepath = os.path.join(target, os.path.basename(localpath))
195 os.path.basename(localpath))208 sftp_client.put(localpath, remotepath)
196 sftp_client.put(localpath, remotepath)
197 else:
198 failed.append(localpath)
199 except paramiko.BadHostKeyException as err:209 except paramiko.BadHostKeyException as err:
200 raise UTAHProvisioningException(210 raise UTAHProvisioningException(
201 'Host key exception encountered; '211 'Host key exception encountered; '
@@ -207,11 +217,6 @@
207 finally:217 finally:
208 if sftp_client:218 if sftp_client:
209 sftp_client.close()219 sftp_client.close()
210 if len(failed) > 0:
211 err = UTAHProvisioningException('Files do not exist: {}'
212 .format(' '.join(failed)))
213 err.files = failed
214 raise err
215220
216 def downloadfiles(self, files, target=os.path.normpath('/tmp/')):221 def downloadfiles(self, files, target=os.path.normpath('/tmp/')):
217 """Copy a file or list of files from the machine to a local target.222 """Copy a file or list of files from the machine to a local target.
@@ -382,6 +387,9 @@
382 # No cleanup needed for systems that are already provisioned387 # No cleanup needed for systems that are already provisioned
383 self.clean = False388 self.clean = False
384389
390 self.active = False
391 self.provisioned = True
392
385 # System is expected to be available already, so there's no need to393 # System is expected to be available already, so there's no need to
386 # wait before trying to connect through ssh394 # wait before trying to connect through ssh
387 self.check_timeout = 3395 self.check_timeout = 3
@@ -391,9 +399,6 @@
391 if installtype is None:399 if installtype is None:
392 self.installtype = config.installtype400 self.installtype = config.installtype
393401
394 self.template_env = \
395 Environment(loader=FileSystemLoader(config.template_dir))
396
397 def activecheck(self):402 def activecheck(self):
398 """Check if machine is active.403 """Check if machine is active.
399404
400405
=== modified file 'utah/provisioning/vm/vm.py'
--- utah/provisioning/vm/vm.py 2013-04-04 17:17:21 +0000
+++ utah/provisioning/vm/vm.py 2013-04-23 17:57:25 +0000
@@ -71,41 +71,6 @@
71 self.vm = self.lv.lookupByName(self.name)71 self.vm = self.lv.lookupByName(self.name)
72 self.logger.info('VM loaded')72 self.logger.info('VM loaded')
7373
74 def _provision(self):
75 """Make an existing VM available using libvirt to look up the VM."""
76 self.logger.info('Provisioning VM')
77 if self.new:
78 self.logger.debug('New VM requested')
79 try:
80 self._load()
81 self.logger.error('VM already exists')
82 raise UTAHVMProvisioningException(
83 'New VM requested, but {} already exists'
84 .format(self.name))
85 except libvirt.libvirtError as err:
86 if err.get_error_code() == 42:
87 self._create()
88 else:
89 raise err
90
91 try:
92 self._load()
93 except libvirt.libvirtError as err:
94 if err.get_error_code() == 42:
95 self.logger.debug('Lookup failed')
96 try:
97 self._create()
98 self._load()
99 except UTAHVMProvisioningException as error:
100 self.logger.error('VM lookup failed')
101 raise UTAHVMProvisioningException(
102 'Cannot find VM named {}: {}'
103 .format(self.name, str(error)))
104 else:
105 raise err
106 self.provisioned = True
107 self.logger.info('VM provisioned')
108
109 def activecheck(self):74 def activecheck(self):
110 """Verify the machine is provisioned, then start it if needed. """75 """Verify the machine is provisioned, then start it if needed. """
111 self.logger.debug('Checking if VM is active')76 self.logger.debug('Checking if VM is active')
11277
=== added file 'utah/template.py'
--- utah/template.py 1970-01-01 00:00:00 +0000
+++ utah/template.py 2013-04-23 17:57:25 +0000
@@ -0,0 +1,53 @@
1# Ubuntu Testing Automation Harness
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU General Public License version 3, as published
6# by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU General Public License for more details.
12
13# You should have received a copy of the GNU General Public License along
14# with this program. If not, see <http://www.gnu.org/licenses/>.
15
16"""Provide easy accessors to the Jinja2 template library"""
17
18import os
19
20from jinja2 import Environment, FileSystemLoader
21
22from utah import config
23
24
25def _add_backslash_filter(value):
26 r"""Append backslash character to each line.
27
28 :returns: an version of the string where \\'s have been to each \\n
29 :rtype: string
30
31 """
32 return '\n'.join(['{}\\'.format(line) for line in value.splitlines()])
33
34
35def as_buff(template, **kw):
36 """Return the rendered template as a string."""
37 if not getattr(as_buff, '_env', None):
38 paths = [
39 config.template_dir,
40 os.path.join(os.path.dirname(__file__), '../templates'),
41 ]
42 as_buff._env = \
43 Environment(loader=FileSystemLoader(paths))
44 as_buff._env.filters['add_backslash'] = _add_backslash_filter
45 template = as_buff._env.get_template(template)
46 return template.render(kw)
47
48
49def write(template, path, **kw):
50 """Render the given template as a file."""
51 content = as_buff(template, **kw)
52 with open(path, 'w') as f:
53 f.write(content)

Subscribers

People subscribed via source and target branches

to all changes: