Merge lp:~bac/lpsetup/juju-lxc into lp:lpsetup

Proposed by Brad Crittenden
Status: Merged
Approved by: Brad Crittenden
Approved revision: 76
Merged at revision: 62
Proposed branch: lp:~bac/lpsetup/juju-lxc
Merge into: lp:lpsetup
Diff against target: 508 lines (+221/-186)
7 files modified
.bzrignore (+2/-0)
README.rst (+33/-3)
lpsetup/subcommands/inithost.py (+1/-1)
lpsetup/tests/integration/common.py (+76/-0)
lpsetup/tests/integration/lxc.py (+0/-106)
lpsetup/tests/integration/test_init_host.py (+18/-76)
lpsetup/tests/integration/test_install_lxc.py (+91/-0)
To merge this branch: bzr merge lp:~bac/lpsetup/juju-lxc
Reviewer Review Type Date Requested Status
Gary Poster (community) Approve
Review via email: mp+116529@code.launchpad.net

Commit message

Adds integration test for install-lxc. Currently only works with EC2 as the target.

Description of the change

Provides a new integration test called juju-lxc.py which replaces lxc.py.

Refactored the integration tests more.

Now both tests take an environment name (must exist in ~/.juju/environments.yaml) and can be run against ec2 or in an lxc locally.

The juju-lxc.py test cannot be run locally at present due to bug 924281.

To post a comment you must log in.
Revision history for this message
Gary Poster (gary) wrote :

For future readers, Brad discovered from hallyn and stgraber that the way to fix the given bug is to adjust apparmor as described in http://www.stgraber.org/2012/05/04/lxc-in-ubuntu-12-04-lts/ , in the section labeled "Container nesting."

Thank you, Brad. It is very nice to have this moved further along.

This looks like it is doing the right thing, but some of the text confuses me a lot. For instance, there's something that says "Run an end-to-end integration tests of the non-LXC lpsetup story" when I think it is doing the opposite. The file names for the two tests confuse me also.

I think I need to talk through this with you, so I'll hope to do that tomorrow morning.

Revision history for this message
Brad Crittenden (bac) wrote :

Thanks for finding the c-n-p confusion in the test description. Fixed.

Yes, the naming for these two strategies is confusing. Let's talk to figure out how to describe the two tests and new names.

Revision history for this message
Gary Poster (gary) wrote :

With the naming changes we agreed on in call, I'm happy with the branch. Thanks again.

review: Approve
lp:~bac/lpsetup/juju-lxc updated
76. By Brad Crittenden

Renaming test files and cleaned up descriptions due to review input.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2012-06-21 14:37:02 +0000
3+++ .bzrignore 2012-07-25 13:13:19 +0000
4@@ -10,3 +10,5 @@
5 MANIFEST
6 parts
7 tags
8+.emacs.desktop*
9+*.log
10
11=== modified file 'README.rst'
12--- README.rst 2012-07-18 19:27:07 +0000
13+++ README.rst 2012-07-25 13:13:19 +0000
14@@ -62,9 +62,39 @@
15 charm repository from lp:~charmers/charms/precise/ubuntu/trunk. The
16 test expects your Juju repository to be at ~/juju-charms
17
18-The integration tests use a juju environment named "lpsetup-testing" so
19-you must create an appropriately configured juju environment with that
20-name before running the tests in ~/.juju/environments.yaml.
21+The integration tests use two juju environment named
22+"lpsetup-testing-ec2" and "lpsetup-testing-lxc", which run in the
23+respective environments. You must create an appropriately
24+configured juju environment with that name before running the tests in
25+~/.juju/environments.yaml. The two stanzas will look something like:
26+
27+ lpsetup-testing-ec2:
28+ type: ec2
29+ access-key: <your EC2 access key>
30+ secret-key: <your EC2 secret key>
31+ control-bucket: <a unique control bucket name>
32+ admin-secret: <your admin secret>
33+ default-series: precise
34+
35+ lpsetup-testing-lxc:
36+ type: local
37+ data-dir: /tmp/juju-local
38+ control-bucket: local-bucket
39+ admin-secret: <your admin secret>
40+ default-series: precise
41+ juju-origin: distro
42+
43+There are two tests provided and each can run in either environment as
44+long as you specify using the '-e' command line option. The tests
45+are:
46+
47+lpsetup/tests/integration/non-lxc.py
48+lpsetup/tests/integration/juju-lxc.py
49+
50+The first only tests the *init-host* command but goes no further.
51+The second tests *install-lxc*. Due to bug 924281 (supposedly
52+*fix-released* but still seen) this test cannot currently be run in an
53+lxc container as it would require an lxc inside an lxc.
54
55 The tests bootstrap the environment for you and fail if it is already
56 running.
57
58=== modified file 'lpsetup/subcommands/inithost.py'
59--- lpsetup/subcommands/inithost.py 2012-07-20 14:49:59 +0000
60+++ lpsetup/subcommands/inithost.py 2012-07-25 13:13:19 +0000
61@@ -194,7 +194,7 @@
62 # Set up bzr and Launchpad authentication.
63 call('bzr', 'whoami', formataddr([full_name, email]))
64 if valid_ssh_keys:
65- subprocess.call(['bzr', 'lp-login', lpuser])
66+ subprocess.call(['bzr', 'launchpad-login', lpuser])
67
68
69 def initialize_lxc():
70
71=== modified file 'lpsetup/tests/integration/common.py'
72--- lpsetup/tests/integration/common.py 2012-07-19 20:58:52 +0000
73+++ lpsetup/tests/integration/common.py 2012-07-25 13:13:19 +0000
74@@ -11,37 +11,113 @@
75
76 import argparse
77 from datetime import datetime
78+from email.Utils import formataddr
79+import os
80+import time
81+from subprocess import (
82+ check_call as check_call_orig,
83+ PIPE,
84+ Popen,
85+ )
86
87
88 class IntegrationTestBase:
89
90 banner_width = 70
91 test_type = None
92+ juju_target = None
93+ default_juju_env = None
94
95 def __init__(self):
96 self.parser = argparse.ArgumentParser()
97 self.parser.add_argument(
98 '--no-tear-down', action='store_true', default=False,
99 help='Do not run the tear down method for debugging.')
100+
101+ self.parser.add_argument(
102+ '-e', '--environment', default=self.default_juju_env,
103+ help='Juju environment to use as defined in '
104+ '~/.juju/environment.yaml')
105 self.args = self.parser.parse_args()
106+ self.juju_env = self.args.environment
107
108 def banner(self, msg, width=banner_width):
109 print '*' * width
110 print '* ' + msg.ljust(width - 4) + ' *'
111 print '*' * width
112
113+ def check_call(self, *args):
114+ print '-' * 70
115+ print "check_call:"
116+ print ' '.join(*args)
117+ print '-' * 70
118+ check_call_orig(*args)
119+
120+ def on_remote(self, args):
121+ if isinstance(args, basestring):
122+ args = args.split()
123+
124+ self.check_call(
125+ 'juju ssh {} -o StrictHostKeyChecking=no'
126+ ' -o UserKnownHostsFile=/dev/null'
127+ ' -e {}'.format(self.juju_target, self.juju_env).split()
128+ + list(args))
129+
130+ def bzr_whoami(self):
131+ addr = formataddr(['Not A Real Person', 'no@example.com'])
132+ self.on_remote('bzr whoami "{}"'.format(addr))
133+
134 def set_up(self):
135+ """Set up a juju-managed instance to run the tests on."""
136 self.banner(
137 'Setting up the test environment for {}.'.format(self.test_type))
138+ self.check_call('juju bootstrap -e {}'.format(self.juju_env).split())
139+ # XXX The "ubuntu" charm is broken, so it has to be loaded from the
140+ # local repository. Get it from
141+ # lp:~charmers/charms/precise/ubuntu/trunk.
142+ self.check_call(
143+ 'juju deploy local:ubuntu --repository ~/juju-charms'
144+ ' -e {}'.format(self.juju_env).split())
145+
146+ start_time = time.time()
147+ while time.time() - start_time < 600:
148+ process = Popen(
149+ 'juju status -e {}'.format(self.juju_env).split(), stdout=PIPE)
150+ stdout, stderr = process.communicate()
151+ if 'instance-state: running' in stdout:
152+ break
153+ else:
154+ raise RuntimeError('starting the instance took too long')
155+
156+ # Even though the instance-state is "running", it's still not ready, so
157+ # wait a little while before continuing.
158+ time.sleep(30)
159+
160+ self.on_remote('sudo apt-add-repository --yes ppa:yellow/ppa')
161+ self.on_remote('sudo apt-get update')
162+ self.on_remote('sudo apt-get install python-shelltoolbox')
163+ self.check_call(
164+ 'juju scp -o StrictHostKeyChecking=no'
165+ ' -o UserKnownHostsFile=/dev/null -e {}'
166+ ' -r . {}:lpsetup'.format(self.juju_env, self.juju_target).split())
167
168 def tear_down(self):
169 self.banner('Cleaning up.')
170+ code = os.system(
171+ 'echo y | juju destroy-environment -e {}'.format(self.juju_env))
172+ if code != 0:
173+ raise RuntimeError('Destroying the test juju environment failed.')
174
175 def check_environment(self):
176 self.banner('Checking test environment.')
177+ code = os.system('juju status -e {}'.format(self.juju_env))
178+ if code == 0:
179+ # The "juju status" should have failed.
180+ raise RuntimeError('A juju environment unexpectedly exists.')
181
182 def do_test(self):
183 self.banner('Running integration tests for {}.'.format(self.test_type))
184+ self.bzr_whoami()
185
186 def run(self):
187 start_time = datetime.now()
188
189=== removed file 'lpsetup/tests/integration/lxc.py'
190--- lpsetup/tests/integration/lxc.py 2012-07-19 20:58:52 +0000
191+++ lpsetup/tests/integration/lxc.py 1970-01-01 00:00:00 +0000
192@@ -1,106 +0,0 @@
193-#!/usr/bin/env python
194-# Copyright 2012 Canonical Ltd. This software is licensed under the
195-# GNU Affero General Public License version 3 (see the file LICENSE).
196-
197-"""A simple, end-to-end integration test."""
198-
199-from bzrlib.branch import Branch
200-from bzrlib.errors import NotBranchError
201-from bzrlib.missing import find_unmerged
202-from bzrlib.status import show_tree_status
203-from bzrlib.workingtree import WorkingTree
204-from StringIO import StringIO
205-from subprocess import check_call
206-import sys
207-
208-from shelltoolbox import run
209-
210-from common import IntegrationTestBase
211-
212-
213-LXC_NAME = 'lpsetup-testing'
214-
215-
216-class LXCIntegrationTest(IntegrationTestBase):
217-
218- test_type = 'LXC container'
219- repo = '~/launchpad-testing/lp-branches'
220-
221- def get_branch(self, dir='.'):
222- branch, _ = Branch.open_containing(dir)
223- return branch
224-
225- def get_push_location(self, branch=None):
226- if branch is None:
227- branch = self.get_branch()
228- return branch.get_push_location()
229-
230- def check_environment(self):
231- """Be sure the test environment doesn't exist."""
232- # We want to be really sure we do not clobber an existing LXC
233- # container, therefore we make sure one doesn't exist before
234- # proceeding.
235- super(LXCIntegrationTest, self).check_environment()
236- cmd = 'sudo lxc-info -n {}'.format(LXC_NAME)
237- results = run(*cmd.split())
238- if results.split()[1] == 'RUNNING':
239- # The container should not be active.
240- raise RuntimeError(
241- "An LXC container named '{}' is unexpectedly "
242- "running.".format(LXC_NAME))
243- # Ensure the local branch has been pushed.
244- # For some reason 'bzr missing' cannot use a 'lp:' URL. http or
245- # bzr+ssh URLs work.
246- self.branch = self.get_branch()
247- self.push_location = self.get_push_location(self.branch)
248- if self.push_location.startswith('lp:'):
249- raise RuntimeError(
250- "Push location must use http or bzr+ssh "
251- "protocol: {}".format(self.push_location))
252- # Ensure the branch has been pushed and has no missing revisions.
253- try:
254- push_branch = self.get_branch(self.push_location)
255- except NotBranchError:
256- raise RuntimeError(
257- 'The branch has never been pushed to Launchpad.')
258- tree, _ = WorkingTree.open_containing_paths(None)
259- stream = StringIO()
260- show_tree_status(tree, to_file=stream)
261- if stream.getvalue():
262- raise RuntimeError(
263- 'The tree has uncommitted changes. Check them in '
264- 'and push to Launchpad.')
265- near, far = find_unmerged(self.branch, push_branch)
266- if near or far:
267- raise RuntimeError('The local branch and the pushed version '
268- 'have diverged.')
269-
270- def set_up(self):
271- """Set up the LXC container environment."""
272- super(LXCIntegrationTest, self).set_up()
273- cmd = 'sudo python setup.py install'
274- check_call(cmd.split())
275-
276- def do_test(self):
277- """Run an end-to-end integration tests of the non-LXC lpsetup story."""
278- super(LXCIntegrationTest, self).do_test()
279- cmd = 'lp-setup install-lxc -B {} -r {} {}'.format(
280- self.push_location, self.repo, LXC_NAME)
281- check_call(cmd.split())
282-
283- def tear_down(self):
284- super(LXCIntegrationTest, self).tear_down()
285-
286- def lxc_cmd(cmd_name):
287- cmd = 'sudo {} -n {}'.format(cmd_name, LXC_NAME)
288- check_call(cmd.split())
289-
290- try:
291- lxc_cmd('lxc-stop')
292- lxc_cmd('lxc-destroy')
293- except:
294- pass
295-
296-
297-if __name__ == '__main__':
298- sys.exit(LXCIntegrationTest().run())
299
300=== renamed file 'lpsetup/tests/integration/non-lxc.py' => 'lpsetup/tests/integration/test_init_host.py'
301--- lpsetup/tests/integration/non-lxc.py 2012-07-19 18:24:13 +0000
302+++ lpsetup/tests/integration/test_init_host.py 2012-07-25 13:13:19 +0000
303@@ -2,91 +2,33 @@
304 # Copyright 2012 Canonical Ltd. This software is licensed under the
305 # GNU Affero General Public License version 3 (see the file LICENSE).
306
307-"""A simple, end-to-end integration test."""
308-
309-import os
310+"""A simple, integration test of init-host.
311+
312+Tests initialization of the host without init-lxc in order to demonstrate that
313+it can be run in isolation, as part of a non-LXC environment.
314+"""
315+
316 import sys
317-import time
318-
319-from subprocess import (
320- check_call,
321- Popen,
322- PIPE,
323- )
324
325 from common import IntegrationTestBase
326
327
328-class NonLXCIntegrationTest(IntegrationTestBase):
329-
330- test_type = 'non-LXC target'
331-
332- def on_remote(self, args):
333- if type(args) == str:
334- args = args.split()
335-
336- check_call('juju ssh 1 -o StrictHostKeyChecking=no'
337- ' -o UserKnownHostsFile=/dev/null -e lpsetup-testing'.split()
338- + list(args))
339-
340- def check_environment(self):
341- """Be sure the test environment doesn't exist."""
342- # We want to be really sure we do not clobber an already-existing juju
343- # environment. Therefore we make sure one doesn't exist before
344- # bootstrapping.
345- super(NonLXCIntegrationTest, self).check_environment()
346- code = os.system('juju status -e lpsetup-testing')
347- if code == 0:
348- # The "juju status" should have failed.
349- raise RuntimeError('A juju environment unexpectedly exists.')
350-
351- def set_up(self):
352- """Set up a juju-managed instance to run the tests on."""
353- super(NonLXCIntegrationTest, self).set_up()
354- check_call('juju bootstrap -e lpsetup-testing'.split())
355- # XXX The "ubuntu" charm is broken, so it has to be loaded from the
356- # local repository. Get it from
357- # lp:~charmers/charms/precise/ubuntu/trunk.
358- check_call('juju deploy local:ubuntu --repository ~/juju-charms'
359- ' -e lpsetup-testing --constraints instance-type=m1.large'.split())
360-
361- start_time = time.time()
362- while time.time() - start_time < 600:
363- process = Popen(
364- 'juju status -e lpsetup-testing'.split(), stdout=PIPE)
365- stdout, stderr = process.communicate()
366- if 'instance-state: running' in stdout:
367- break
368- else:
369- raise RuntimeError('starting the instance took too long')
370-
371- # Even though the instance-state is "running", it's still not ready, so
372- # wait a little while before continuing.
373- time.sleep(30)
374-
375- self.on_remote('sudo apt-add-repository --yes ppa:yellow/ppa')
376- self.on_remote('sudo apt-get update')
377- self.on_remote('sudo apt-get install python-shelltoolbox')
378- check_call('juju scp -o StrictHostKeyChecking=no'
379- ' -o UserKnownHostsFile=/dev/null -e lpsetup-testing'
380- ' -r . 1:lpsetup'.split())
381+class InitHostTest(IntegrationTestBase):
382+
383+ test_type = 'init-host tests'
384+ default_juju_env = 'lpsetup-testing-ec2'
385+ juju_target = 'ubuntu/0'
386
387 def do_test(self):
388- """Run an end-to-end integration tests of the non-LXC lpsetup story."""
389+ """Run an integration test that only address host issues.
390+
391+ Can be run on EC2 or LXC as the host machine.
392+ """
393 # Since the most common scenario is to have bzr whoami setup, we do
394 # that instead of providing the arguments directly to lpsetup.
395- super(NonLXCIntegrationTest, self).do_test()
396- self.on_remote(
397- ('bzr', 'whoami', '"Not A Real Person <no@example.com>"'))
398- self.on_remote('cd lpsetup; ./lp-setup init-host'.split())
399-
400- def tear_down(self):
401- super(NonLXCIntegrationTest, self).tear_down()
402- code = os.system(
403- 'echo y | juju destroy-environment -e lpsetup-testing')
404- if code != 0:
405- raise RuntimeError('Destroying the test juju environment failed.')
406+ super(InitHostTest, self).do_test()
407+ self.on_remote('cd lpsetup; ./lp-setup init-host')
408
409
410 if __name__ == '__main__':
411- sys.exit(NonLXCIntegrationTest().run())
412+ sys.exit(InitHostTest().run())
413
414=== added file 'lpsetup/tests/integration/test_install_lxc.py'
415--- lpsetup/tests/integration/test_install_lxc.py 1970-01-01 00:00:00 +0000
416+++ lpsetup/tests/integration/test_install_lxc.py 2012-07-25 13:13:19 +0000
417@@ -0,0 +1,91 @@
418+#!/usr/bin/env python
419+# Copyright 2012 Canonical Ltd. This software is licensed under the
420+# GNU Affero General Public License version 3 (see the file LICENSE).
421+
422+"""A simple, end-to-end integration test of install-lxc."""
423+
424+from StringIO import StringIO
425+import sys
426+import urlparse
427+
428+from bzrlib.branch import Branch
429+from bzrlib.errors import NotBranchError
430+from bzrlib.missing import find_unmerged
431+from bzrlib.status import show_tree_status
432+from bzrlib.workingtree import WorkingTree
433+
434+
435+from common import IntegrationTestBase
436+
437+
438+class InstallLXCTest(IntegrationTestBase):
439+
440+ test_type = 'local LXC target'
441+ default_juju_env = 'lpsetup-testing-lxc'
442+ juju_target = 'ubuntu/0'
443+ test_type = 'LXC container'
444+ repo = '~/launchpad-testing/lp-branches'
445+
446+ def get_branch(self, dir='.'):
447+ branch, _ = Branch.open_containing(dir)
448+ return branch
449+
450+ def get_push_location(self, branch=None):
451+ if branch is None:
452+ branch = self.get_branch()
453+ return branch.get_push_location()
454+
455+ def check_environment(self):
456+ """Be sure the test environment doesn't exist."""
457+
458+ # We want to be really sure we do not clobber an already-existing juju
459+ # environment. Therefore we make sure one doesn't exist before
460+ # bootstrapping.
461+ super(InstallLXCTest, self).check_environment()
462+
463+ # Ensure the local branch has been pushed.
464+ # For some reason 'bzr missing' cannot use a 'lp:' URL. http or
465+ # bzr+ssh URLs work.
466+ self.branch = self.get_branch()
467+ self.push_location = self.get_push_location(self.branch)
468+ if self.push_location.startswith('lp:'):
469+ raise RuntimeError(
470+ "Push location must use http or bzr+ssh "
471+ "protocol: {}".format(self.push_location))
472+ # Ensure the branch has been pushed and has no missing revisions.
473+ try:
474+ push_branch = self.get_branch(self.push_location)
475+ except NotBranchError:
476+ raise RuntimeError(
477+ 'The branch has never been pushed to Launchpad.')
478+ tree, _ = WorkingTree.open_containing_paths(None)
479+ stream = StringIO()
480+ show_tree_status(tree, to_file=stream)
481+ if stream.getvalue():
482+ raise RuntimeError(
483+ 'The tree has uncommitted changes. Check them in '
484+ 'and push to Launchpad.')
485+ near, far = find_unmerged(self.branch, push_branch)
486+ if near or far:
487+ raise RuntimeError('The local branch and the pushed version '
488+ 'have diverged.')
489+
490+ def set_up(self):
491+ super(InstallLXCTest, self).set_up()
492+ self.on_remote('cd lpsetup; sudo python setup.py install')
493+
494+ def do_test(self):
495+ """Run an end-to-end integration tests of the LXC lpsetup story."""
496+ # Since the most common scenario is to have bzr whoami setup, we do
497+ # that instead of providing the arguments directly to lpsetup.
498+ super(InstallLXCTest, self).do_test()
499+ urlparts = list(urlparse.urlsplit(self.push_location))
500+ urlparts[0] = 'http'
501+ branch_location = urlparse.urlunsplit(urlparts)
502+ cmd = 'lpsetup/lp-setup install-lxc --use-http -B {} -r {}'.format(
503+ branch_location, self.repo)
504+ self.on_remote(cmd)
505+
506+
507+if __name__ == '__main__':
508+ sys.exit(InstallLXCTest().run())

Subscribers

People subscribed via source and target branches