Merge lp:~frankban/lpsetup/create-scripts into lp:lpsetup

Proposed by Francesco Banconi on 2012-04-11
Status: Merged
Approved by: Graham Binns on 2012-04-11
Approved revision: 19
Merged at revision: 12
Proposed branch: lp:~frankban/lpsetup/create-scripts
Merge into: lp:lpsetup
Diff against target: 558 lines (+393/-4)
8 files modified
lpsetup/settings.py (+7/-0)
lpsetup/subcommands/lxcinstall.py (+49/-0)
lpsetup/templates/lp-setup-lxc-build (+48/-0)
lpsetup/templates/lp-setup-lxc-cleanup (+33/-0)
lpsetup/templates/lp-setup-lxc-test (+20/-0)
lpsetup/tests/test_handlers.py (+2/-1)
lpsetup/tests/test_utils.py (+107/-0)
lpsetup/utils.py (+127/-3)
To merge this branch: bzr merge lp:~frankban/lpsetup/create-scripts
Reviewer Review Type Date Requested Status
Graham Binns (community) code 2012-04-11 Approve on 2012-04-11
Review via email: mp+101536@code.launchpad.net

Description of the Change

== Changes ==

lpsetup is now capable to generate the scripts used by buildbot in parallel tests. A new step `create_scripts` is now part of the lxc-install sub command.
Since the script generation is probably only needed by buildbot, this step is only activated if --create-scripts argument is passed to lxc-install.
The script templates are present in the `templates` directory of the lpsetup package: theese templates are used to actually render the scripts (usually saving them in /usr/local/bin).

The Scrubber class is now separated from the cleanup script, and the tests Brad wrote are integrated in lpsetup, with some fixes:
- Scrubber now takes the lxc name to dynamically generate the directory patterns used to clean up the file system.
- I've found that, having containers running in my machine, Scrubber tests failed because I need to be root to run lxc-info. Vice versa, running the tests as root, they passed but my containers were killed. To work around this, I've added an optional argument `ignored_containers` to the Scrubber's __init__.
- PEP8 compliant naming for the Scrubber methods.

Updated the (naive) method used to retrieve the user home directory in a test.

== Tests ==

$ bin/test
Running zope.testrunner.layer.UnitTests tests:
  Set up zope.testrunner.layer.UnitTests in 0.000 seconds.
  Ran 46 tests with 0 failures and 0 errors in 0.469 seconds.
Tearing down left over layers:
  Tear down zope.testrunner.layer.UnitTests in 0.000 seconds.

To post a comment you must log in.
Brad Crittenden (bac) wrote :

Hi Francesco,

This isn't a full review as Graham is doing it. I would like to comment on your decision to use PEP-8 naming for methods.

For Launchpad-related projects such as this one I think we should continue to follow the Launchpad style guide: https://dev.launchpad.net/PythonStyleGuide

Launchpad has agreed to use camelCase for method names as an exception to the PEP-8 guide.

I'd like for us to either continue following the LP style guide or agree not to.

Francesco Banconi (frankban) wrote :

Thanks for your comment Brad, but now I'm confused: of course lpsetup is a launchpad related project, but it's not lp itself, it's a separate branch/project, so I thought consistency with the launchpad code style was not a problem. Indeed right now lpsetup uses PEP-8 naming convention everywhere in the code.
However, there is a high possibility that I was wrong at the time (in my interpretation of what is "launchpad related") and there is no problem in replacing method names if we decide so.

Graham Binns (gmb) wrote :

Context for latecomers: After conversation with Gary, it's been decided to leave the PEP8 naming in place, since it's common throughout lpsetup.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lpsetup/settings.py'
2--- lpsetup/settings.py 2012-03-30 10:47:23 +0000
3+++ lpsetup/settings.py 2012-04-11 10:42:23 +0000
4@@ -61,6 +61,12 @@
5 LXC_GUEST_ARCH = 'i386'
6 LXC_GUEST_CHOICES = ('lucid', 'oneiric', 'precise')
7 LXC_GUEST_OS = LXC_GUEST_CHOICES[0]
8+LXC_LEASES = (
9+ '/var/lib/dhcp3/dhclient.eth0.leases',
10+ '/var/lib/dhcp/dhclient.eth0.leases',
11+ )
12+LXC_LP_DIR_PATTERN = '/tmp/lxc-lp-*'
13+LXC_LP_TEST_DIR_PATTERN = '/var/lib/lxc/{lxc_name}-tmp-*'
14 LXC_NAME = 'lptests'
15 LXC_OPTIONS = """
16 lxc.network.type = veth
17@@ -70,4 +76,5 @@
18 LXC_PACKAGES = ['lxc', 'libvirt-bin']
19 LXC_PATH = '/var/lib/lxc/'
20 RESOLV_FILE = '/etc/resolv.conf'
21+SCRIPTS = ('lp-setup-lxc-build', 'lp-setup-lxc-cleanup', 'lp-setup-lxc-test')
22 SSH_KEY_NAME = 'id_rsa'
23
24=== modified file 'lpsetup/subcommands/lxcinstall.py'
25--- lpsetup/subcommands/lxcinstall.py 2012-04-10 09:22:25 +0000
26+++ lpsetup/subcommands/lxcinstall.py 2012-04-11 10:42:23 +0000
27@@ -7,6 +7,7 @@
28 __metaclass__ = type
29 __all__ = [
30 'create_lxc',
31+ 'create_scripts',
32 'initialize_lxc',
33 'SubCommand',
34 'setup_launchpad_lxc',
35@@ -36,10 +37,12 @@
36 LXC_GUEST_ARCH,
37 LXC_GUEST_CHOICES,
38 LXC_GUEST_OS,
39+ LXC_LEASES,
40 LXC_NAME,
41 LXC_OPTIONS,
42 LXC_PACKAGES,
43 RESOLV_FILE,
44+ SCRIPTS,
45 )
46 from lpsetup.subcommands import install
47 from lpsetup.utils import (
48@@ -47,10 +50,46 @@
49 get_container_path,
50 get_lxc_gateway,
51 lxc_stopped,
52+ render_to_file,
53 this_command,
54 )
55
56
57+def create_scripts(user, lxc_name, ssh_key_path):
58+ """Create scripts to update the Launchpad environment and run tests."""
59+ leases1, leases2 = [get_container_path(lxc_name, i) for i in LXC_LEASES]
60+ context = {
61+ 'leases1': leases1,
62+ 'leases2': leases2,
63+ 'lxc_name': lxc_name,
64+ 'pattern':
65+ r's/.* ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*/\1/',
66+ 'ssh_key_path': ssh_key_path,
67+ 'user': user,
68+ }
69+ scripts = []
70+ # Generate the scripts.
71+ for template_name in SCRIPTS:
72+ dest = os.path.join(os.path.sep, 'usr', 'local', 'bin', template_name)
73+ render_to_file(template_name, context, dest)
74+ os.chmod(dest, 0555)
75+ scripts.append(dest)
76+ # Add a file to sudoers.d that will let the buildbot user run the above.
77+ sudoers_file = '/etc/sudoers.d/launchpad-' + user
78+ sudoers_contents = '{user} ALL = (ALL) NOPASSWD: {scripts}\n'.format(
79+ user=user, scripts=', '. join(scripts))
80+ with open(sudoers_file, 'w') as sudoers:
81+ sudoers.write(sudoers_contents)
82+ # The sudoers must have this mode or it will be ignored.
83+ os.chmod(sudoers_file, 0440)
84+ # XXX 2012-03-13 frankban bug=944386:
85+ # Disable hardlink restriction. This workaround needs
86+ # to be removed once the kernel bug is resolved.
87+ procfile = '/proc/sys/kernel/yama/protected_nonaccess_hardlinks'
88+ with open(procfile, 'w') as f:
89+ f.write('0\n')
90+
91+
92 def create_lxc(user, lxc_name, lxc_arch, lxc_os):
93 """Create the LXC named `lxc_name` sharing `user` home directory.
94
95@@ -159,6 +198,8 @@
96 'user', 'full_name', 'email', 'lpuser',
97 'private_key', 'public_key', 'valid_ssh_keys', 'ssh_key_path',
98 'use_urandom', 'dependencies_dir', 'directory'),
99+ (create_scripts,
100+ 'user', 'lxc_name', 'ssh_key_path'),
101 (create_lxc,
102 'user', 'lxc_name', 'lxc_arch', 'lxc_os'),
103 (start_lxc,
104@@ -177,6 +218,11 @@
105 ssh_key_name_help = ('The ssh key name used to connect to Launchpad '
106 'and to to the LXC container.')
107
108+ def call_create_scripts(self, namespace, step, args):
109+ """Run the `create_scripts` step only if the related flag is set."""
110+ if namespace.create_scripts:
111+ return step(*args)
112+
113 def add_arguments(self, parser):
114 super(SubCommand, self).add_arguments(parser)
115 parser.add_argument(
116@@ -192,3 +238,6 @@
117 choices=LXC_GUEST_CHOICES,
118 help='The LXC container distro codename. '
119 '[DEFAULT={0}]'.format(LXC_GUEST_OS))
120+ parser.add_argument(
121+ '-C', '--create-scripts', action='store_true',
122+ help='Create the scripts used by buildbot for parallel testing.')
123
124=== added directory 'lpsetup/templates'
125=== added file 'lpsetup/templates/lp-setup-lxc-build'
126--- lpsetup/templates/lp-setup-lxc-build 1970-01-01 00:00:00 +0000
127+++ lpsetup/templates/lp-setup-lxc-build 2012-04-11 10:42:23 +0000
128@@ -0,0 +1,48 @@
129+#!/bin/sh
130+# Copyright 2012 Canonical Ltd. This software is licensed under the
131+# GNU Affero General Public License version 3 (see the file LICENSE).
132+
133+# Run the Launchpad build inside LXC.
134+
135+# Assumptions:
136+# * This script is run as root.
137+
138+set -ux
139+truncate -c -s0 {leases1}
140+truncate -c -s0 {leases2}
141+
142+lxc-start -n {lxc_name} -d
143+lxc-wait -n {lxc_name} -s RUNNING
144+
145+delay=30
146+while [ "$delay" -gt 0 -a ! -s {leases1} -a ! -s {leases2} ]
147+do
148+ delay=$(( $delay - 1 ))
149+ sleep 1
150+done
151+
152+[ -s {leases1} ] && LEASES={leases1} || LEASES={leases2}
153+IP_ADDRESS=`grep fixed-address $LEASES | tail -n 1 | sed -r '{pattern}'`
154+
155+if [ 0 -eq $? -a -n "$IP_ADDRESS" ]; then
156+ for i in $(seq 1 30); do
157+ su {user} -c "/usr/bin/ssh -o StrictHostKeyChecking=no \
158+ -i '{ssh_key_path}' $IP_ADDRESS make -C $PWD schema"
159+ if [ ! 255 -eq $? ]; then
160+ # If ssh returns 255 then its connection failed.
161+ # Anything else is either success (status 0) or a
162+ # failure from whatever we ran over the SSH connection.
163+ # In those cases we want to stop looping, so we break
164+ # here.
165+ break;
166+ fi
167+ sleep 1
168+ done
169+else
170+ echo "could not get IP address - aborting." >&2
171+ echo "content of $LEASES:" >&2
172+ cat $LEASES >&2
173+fi
174+
175+lxc-stop -n {lxc_name}
176+lxc-wait -n {lxc_name} -s STOPPED
177
178=== added file 'lpsetup/templates/lp-setup-lxc-cleanup'
179--- lpsetup/templates/lp-setup-lxc-cleanup 1970-01-01 00:00:00 +0000
180+++ lpsetup/templates/lp-setup-lxc-cleanup 2012-04-11 10:42:23 +0000
181@@ -0,0 +1,33 @@
182+#!/usr/bin/env python
183+# Copyright 2012 Canonical Ltd. This software is licensed under the
184+# GNU Affero General Public License version 3 (see the file LICENSE).
185+
186+# Cleanup remnants of LXC containers from previous runs.
187+
188+# Runs of LXC may leave cruft laying around that interferes with
189+# the next run. These items need to be cleaned up.
190+
191+# 1) Shut down all running containers
192+
193+# 2) for every /var/lib/lxc/{lxc_name}-tmp-* directory:
194+# umount [directory]/ephemeralbind
195+# umount [directory]
196+# rm -rf [directory]
197+
198+# 3) for every /tmp/lxc-lp-* (or something like that?) directory:
199+# umount [directory]
200+# rm -rf [directory]
201+
202+# Assumptions:
203+# * This script is run as root.
204+
205+from lpsetup.utils import Scrubber
206+
207+
208+USER = '{user}'
209+LXC_NAME = '{lxc_name}'
210+
211+
212+if __name__ == '__main__':
213+ scrubber = Scrubber(USER, LXC_NAME)
214+ scrubber.run()
215
216=== added file 'lpsetup/templates/lp-setup-lxc-test'
217--- lpsetup/templates/lp-setup-lxc-test 1970-01-01 00:00:00 +0000
218+++ lpsetup/templates/lp-setup-lxc-test 2012-04-11 10:42:23 +0000
219@@ -0,0 +1,20 @@
220+#!/bin/sh
221+# Copyright 2012 Canonical Ltd. This software is licensed under the
222+# GNU Affero General Public License version 3 (see the file LICENSE).
223+
224+# Test Launchpad using LXC ephemeral instances.
225+
226+# Assumptions:
227+# * This script is run as root.
228+
229+# We intentionally generate a very long line for the
230+# lxc-start-ephemeral command below because ssh does not propagate
231+# quotes the way we want. E.g.,
232+# touch a; touch b; ssh localhost -- ls "a b"
233+# succeeds, when it should say that the file "a b" does not exist.
234+
235+set -uex
236+lxc-start-ephemeral -u {user} -S '{ssh_key_path}' -o {lxc_name} -- \
237+ "xvfb-run --error-file=/var/tmp/xvfb-errors.log \
238+ --server-args='-screen 0 1024x768x24' \
239+ -a $PWD/bin/test --shuffle --subunit $@"
240
241=== modified file 'lpsetup/tests/test_handlers.py'
242--- lpsetup/tests/test_handlers.py 2012-03-16 16:49:23 +0000
243+++ lpsetup/tests/test_handlers.py 2012-04-11 10:42:23 +0000
244@@ -7,6 +7,7 @@
245 import argparse
246 from contextlib import contextmanager
247 import getpass
248+import pwd
249 import unittest
250
251 from lpsetup.exceptions import ValidationError
252@@ -157,7 +158,7 @@
253 username = getpass.getuser()
254 namespace = argparse.Namespace(user=username)
255 handle_user(namespace)
256- self.assertEqual('/home/' + username, namespace.home_dir)
257+ self.assertEqual(pwd.getpwnam(username).pw_dir, namespace.home_dir)
258
259 def test_failure(self):
260 # Ensure the validation fails if the current user is root and
261
262=== modified file 'lpsetup/tests/test_utils.py'
263--- lpsetup/tests/test_utils.py 2012-03-23 11:50:11 +0000
264+++ lpsetup/tests/test_utils.py 2012-04-11 10:42:23 +0000
265@@ -4,11 +4,25 @@
266
267 """Tests for the utils module."""
268
269+import getpass
270+import os
271+import shutil
272 import sys
273+import tempfile
274 import unittest
275
276+from shelltoolbox import run
277+
278+from lpsetup.settings import (
279+ LXC_LP_DIR_PATTERN,
280+ LXC_LP_TEST_DIR_PATTERN,
281+ LXC_NAME,
282+ )
283 from lpsetup.utils import (
284 get_container_path,
285+ get_running_containers,
286+ render_to_file,
287+ Scrubber,
288 this_command,
289 )
290
291@@ -31,6 +45,99 @@
292 get_container_path('mycontainer', 'home'))
293
294
295+class GetRunningContainersTest(unittest.TestCase):
296+
297+ def assertRunning(self, expected, containers):
298+ running = get_running_containers(containers)
299+ self.assertItemsEqual(expected, list(running))
300+
301+ def test_running_container(self):
302+ # Ensure one container is returned if running.
303+ self.assertRunning(['c1'], ['c1', 'c2', 'c1'])
304+
305+ def test_multiple_running_containers(self):
306+ # Ensure multple running containers are correctly reported.
307+ self.assertRunning(['c1', 'c3'], ['c1', 'c2', 'c3', 'c3', 'c1'])
308+
309+ def test_no_containers_running(self):
310+ # Ensure an empty iterable is returned if no containers are running.
311+ self.assertRunning([], ['c1', 'c2', 'c3'])
312+
313+
314+class RenderToFileTest(unittest.TestCase):
315+
316+ def setUp(self):
317+ self.dest = tempfile.mktemp()
318+ template_path = tempfile.mktemp()
319+ self.templates_dir, self.template_name = os.path.split(template_path)
320+ self.template = '{var1}, {var2}'
321+ with open(template_path, 'w') as f:
322+ f.write(self.template)
323+
324+ def test_render_to_file(self):
325+ # Ensure the template is correctly rendered using the given context.
326+ context = {'var1': 'foo', 'var2': 'bar'}
327+ contents = render_to_file(
328+ self.template_name, context, self.dest,
329+ templates_dir=self.templates_dir)
330+ expected = self.template.format(**context)
331+ self.assertEqual(expected, contents)
332+ with open(self.dest) as f:
333+ self.assertEqual(expected, f.read())
334+
335+
336+class TestScrubber(unittest.TestCase):
337+
338+ def make_dirs(self, tmp_path, pattern, num=10, extra=None):
339+ dir_ = os.path.split(pattern)[0]
340+ path = tmp_path + dir_
341+ os.makedirs(path)
342+ for i in xrange(num):
343+ tmp_dirname = pattern.replace('*', str(i))
344+ dirname = tmp_path + tmp_dirname
345+ os.makedirs(dirname)
346+ if extra is not None:
347+ os.makedirs(os.path.join(dirname, extra))
348+ return path
349+
350+ def setUp(self):
351+ self.tmp = tempfile.mkdtemp()
352+ test_dir_pattern = LXC_LP_TEST_DIR_PATTERN.format(lxc_name=LXC_NAME)
353+ self.lp_pattern = self.tmp + test_dir_pattern
354+ self.lxc_pattern = self.tmp + LXC_LP_DIR_PATTERN
355+ self.lp_dir = self.make_dirs(
356+ self.tmp, test_dir_pattern)
357+ self.lxc_dir = self.make_dirs(
358+ self.tmp, LXC_LP_DIR_PATTERN)
359+ self.user = getpass.getuser()
360+
361+ def tearDown(self):
362+ if os.path.exists(self.tmp):
363+ shutil.rmtree(self.tmp)
364+
365+ def test_scrub(self):
366+ scrubber = Scrubber(self.user, LXC_NAME, self.lp_pattern,
367+ self.lxc_pattern, get_running_containers())
368+ scrubber.run()
369+ self.assertEqual([], os.listdir(self.lp_dir))
370+ self.assertEqual([], os.listdir(self.lxc_dir))
371+
372+ def test_scrub_no_dir(self):
373+ # Running the scrubber when no directories to be scrubbed exist does
374+ # not cause an error.
375+ shutil.rmtree(self.tmp)
376+ scrubber = Scrubber(self.user, LXC_NAME, self.lp_pattern,
377+ self.lxc_pattern, get_running_containers())
378+ scrubber.run()
379+
380+ def test_idempotent(self):
381+ # Running the scrubber multiple times does not cause an error.
382+ scrubber = Scrubber(self.user, LXC_NAME, self.lp_pattern,
383+ self.lxc_pattern, get_running_containers())
384+ scrubber.run()
385+ scrubber.run()
386+
387+
388 class ThisCommandTest(unittest.TestCase):
389
390 script_name = sys.argv[0]
391
392=== modified file 'lpsetup/utils.py'
393--- lpsetup/utils.py 2012-03-23 11:50:11 +0000
394+++ lpsetup/utils.py 2012-04-11 10:42:23 +0000
395@@ -12,13 +12,18 @@
396 'lxc_in_state',
397 'lxc_running',
398 'lxc_stopped',
399+ 'render_to_file',
400+ 'Scrubber',
401 'this_command',
402 ]
403
404 from functools import partial
405+import glob
406 import os
407 import platform
408+import re
409 import subprocess
410+import shutil
411 import sys
412 import time
413
414@@ -27,9 +32,14 @@
415 run,
416 )
417
418-from lpsetup.settings import LXC_PATH
419-
420-
421+from lpsetup.settings import (
422+ LXC_LP_DIR_PATTERN,
423+ LXC_LP_TEST_DIR_PATTERN,
424+ LXC_PATH,
425+ )
426+
427+
428+PID_RE = re.compile("pid:\s+(\d+)")
429 call = partial(run, stdout=None)
430
431
432@@ -61,6 +71,18 @@
433 return 'lxcbr0', '10.0.3.1'
434
435
436+def get_running_containers(containers=None):
437+ """Return an iterable of currently running LXC container's names."""
438+ if containers is None:
439+ containers = run('lxc-ls').split()
440+ visited = {}
441+ for container in containers:
442+ if container in visited:
443+ yield container
444+ else:
445+ visited[container] = 1
446+
447+
448 def lxc_in_state(state, lxc_name, timeout=30):
449 """Return True if the LXC named `lxc_name` is in state `state`.
450
451@@ -83,6 +105,108 @@
452 lxc_stopped = partial(lxc_in_state, 'STOPPED')
453
454
455+def render_to_file(template_name, context, dest, templates_dir=None):
456+ """Render `template_name` using `context`. Write the result inside `dest`.
457+
458+ The argument `template_dir` is the directory containing the template:
459+ if None, the `templates` directory inside the lpsetup package is used.
460+
461+ Return the rendered contents.
462+ """
463+ if templates_dir is None:
464+ templates_dir = os.path.join(os.path.dirname(__file__), 'templates')
465+ with open(os.path.join(templates_dir, template_name)) as source:
466+ template = source.read()
467+ contents = template.format(**context)
468+ with open(dest, 'w') as destination:
469+ destination.write(contents)
470+ return contents
471+
472+
473+class Scrubber(object):
474+ """Scrubber will cleanup after lxc ephemeral uncleanliness.
475+
476+ All running containers will be killed, except for the ones listed
477+ in the `ignored_containers` iterable.
478+
479+ Those directories corresponding the lp_test_dir_pattern will
480+ be unmounted and removed. The 'ephemeralbind' subdirectories
481+ will be unmounted.
482+
483+ The directories corresponding to the lxc_lp_dir_pattern will
484+ be unmounted and removed. No subdirectories will need
485+ unmounting.
486+ """
487+ def __init__(self, user, lxc_name,
488+ lp_test_dir_pattern=LXC_LP_TEST_DIR_PATTERN,
489+ lxc_lp_dir_pattern=LXC_LP_DIR_PATTERN,
490+ ignored_containers=None):
491+ if ignored_containers is None:
492+ self.ignored_containers = []
493+ else:
494+ self.ignored_containers = list(ignored_containers)
495+ self.lp_test_dir_pattern = lp_test_dir_pattern.format(
496+ lxc_name=lxc_name)
497+ self.lxc_lp_dir_pattern = lxc_lp_dir_pattern
498+ self.user = user
499+
500+ def umount(self, dir_):
501+ if os.path.ismount(dir_):
502+ run("umount", dir_)
503+
504+ def scrubdir(self, dir_, extra):
505+ dirs = [dir_]
506+ if extra is not None:
507+ dirs.insert(0, os.path.join(dir_, extra))
508+ for d in dirs:
509+ self.umount(d)
510+ shutil.rmtree(dir_)
511+
512+ def scrub(self, pattern, extra=None):
513+ for dir_ in glob.glob(pattern):
514+ if os.path.isdir(dir_):
515+ self.scrubdir(dir_, extra)
516+
517+ def get_pid(self, container):
518+ info = run("lxc-info", "-n", container)
519+ # lxc-info returns a string containing 'RUNNING' for those
520+ # containers that are running followed by 'pid: <pid>', so
521+ # that must be parsed.
522+ if 'RUNNING' in info:
523+ match = PID_RE.search(info)
524+ if match:
525+ return match.group(1)
526+ return None
527+
528+ def get_running_containers(self):
529+ """Get the running containers.
530+
531+ Returns a list of (name, pid) tuples.
532+ """
533+ output = run("lxc-ls")
534+ containers = set(output.split()).difference(self.ignored_containers)
535+ pidlist = [(c, self.get_pid(c)) for c in containers]
536+ return [(c,p) for c,p in pidlist if p is not None]
537+
538+ def killer(self):
539+ """Kill all running ephemeral containers."""
540+ pids = self.get_running_containers()
541+ if len(pids) > 0:
542+ # We can do this the easy way...
543+ for name, pid in pids:
544+ run("/usr/bin/lxc-stop", "-n", name)
545+ time.sleep(2)
546+ pids = self.get_running_containers()
547+ # ...or, the hard way.
548+ for name, pid in pids:
549+ run("kill", "-9", pid)
550+
551+ def run(self):
552+ self.killer()
553+ self.scrub(self.lp_test_dir_pattern, "ephemeralbind")
554+ self.scrub(self.lxc_lp_dir_pattern)
555+
556+
557 def this_command(directory, args):
558 """Return a command line to re-launch this script using given `args`.
559

Subscribers

People subscribed via source and target branches

to all changes: