Merge lp:~bcsaller/pyjuju/lxc-lib into lp:pyjuju

Proposed by Benjamin Saller
Status: Merged
Approved by: Kapil Thangavelu
Approved revision: 315
Merged at revision: 345
Proposed branch: lp:~bcsaller/pyjuju/lxc-lib
Merge into: lp:pyjuju
Diff against target: 518 lines (+456/-7)
7 files modified
ensemble/control/add_unit.py (+1/-3)
ensemble/control/deploy.py (+1/-3)
ensemble/environment/environment.py (+6/-1)
ensemble/lib/lxc/__init__.py (+231/-0)
ensemble/lib/lxc/data/ensemble-create (+63/-0)
ensemble/lib/lxc/data/lxc.conf (+3/-0)
ensemble/lib/lxc/tests/test_lxc.py (+151/-0)
To merge this branch: bzr merge lp:~bcsaller/pyjuju/lxc-lib
Reviewer Review Type Date Requested Status
Kapil Thangavelu (community) Approve
Gustavo Niemeyer Approve
Review via email: mp+71396@code.launchpad.net

Description of the change

This branch creates a LXCContainer which wraps the lxc cli tools in a way that easy to integrate with twisted.

The associated tests are skipped by default unless

TEST_LXC=1

is in the shell environment.

Other items of note:
- `ensemble/lib/lxc/data/lxc-ensemble` must be copied to `/usr/lib/lxc/templates/`
- container creation requires root. The tests will ask for `sudo` permissions to function.

To post a comment you must log in.
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

I'm lacking a bit of context on the content of the branch. We debated this in Austin,
and I was told that we were reusing existing tools for maintaining containers, rather
than rolling our own. This branch includes a 600+ line shell script to maintain
containers. There's some misunderstanding we should catch up on.

review: Needs Information
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

This no longer needs a fork of the upstream lxc template, i talked to ben and it looks like its good for another review.

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :
Download full text (6.3 KiB)

Quite a few details, but it looks very nice overall.V

+1 with these sorted.

[1]

=== added file 'ensemble/lib/lxc/data/ensemble-create'

Nicely organized script, thanks.

[2]

+ ENSEMBLE_SOURCE="ppa:ensemble/ppa"
+ elif [ $ENSEMBLE_ORIGIN = "lp" ]; then
+ echo "Using Ensemble Branch $ensemble_source"

s/$ensemble_source/$ENSEMBLE_SOURCE/

Environment variables are case sensitive.

Also, let's s/lp/branch/ please. Seems more clear and PPAs
are also in Launchpad.

As a minor, s/Branch/branch/

[3]

+ elif [ $ENSEMBLE_ORIGIN = "distro" ]; then
+ echo "Using Ensemble distribution packages"
+ fi

Should error if $ENSEMBLE_ORIGIN contains garbage.

[4]

+ ENSEMBLE_SOURCE="ppa:ensemble/ppa"
(...)
+ apt-add-repository $ENSEMBLE_SOURCE

Seems unnecessary to use the variable if the script is
hardcoding the behavior. Just use the value itself in
the second line.

[5]

- env_defaults = environment.get_serialization_data()
- options.placement = env_defaults[environment.name].get(
- "placement", UNASSIGNED_POLICY)
+ options.placement = environment.placement

Nice.

[6]

+BASE_PATH = os.path.normpath(
+ os.path.abspath(
+ os.path.join(os.path.dirname(__file__), "data")))

s/BASE_PATH/DATA_PATH/?

Also, abspath calls normpath internally.

[7]

+class LXCError(EnsembleError):
+ """Indicates a low level error with an LXC container"""
+ pass

/pass/d

[8]

+ p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ env=dict(os.environ))
+ r = p.wait()
+ if r != 0:
+ # read the stdout/err streams and show the user
+ print >>sys.stderr, p.stderr.read()
+ raise LXCError(p.stdout.read())

This logic is suboptimal in a few ways:

- The stderr and stdout will be separated out, which means depending on
  the output it will be hard to interpret what the error was about.

- In case stdout or stderr get enough data to fill the kernel buffer,
  the p.wait() will hang completely and never return.

- The argument of env provided is the default behavior if not provided.

- The stderr printed has no context.

To fix all of these, use something along these lines:

    p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    output = p.communicate()[0]
    if p.wait() != 0:
        raise LXCError(output)

Note the argument of stderr (STDOUT, not PIPE). This will make them share
the same file descriptor and intersperse the output as it happens, fixing
the first point. communicate() avoids the second problem.

[9]

+ "-f", config_file,
+ "--",
+ "-r", release]

Weird.. what's the -- about for lxc-create? Can you please add a comment
in the code?

[10]

+ return _cmd(args)
+
+
+def _lxc_start(container_name, console_log=None):

As a suggestion only, I'd remove the two spaces between functions. I know
about PEP8, but this is one of those cases where it feels detrimental to
readability.

[11]

+ subprocess.Popen(["sudo", "lxc-stop", "-n",
+ container_name],
(...)
+ output = subprocess.Popen(["lxc-ls"],
+ ...

Read more...

review: Approve
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

Gustavo has done a great review on details, some additional high level things...

[0]

While building on top of lxc-lib, i ended up needing two additional functionalities, listing all containers with a given prefix, and finding out if a given container is running. The current usage of the running attribute is broken for usages where the container was not started by the same instance of an lxccontainer class.

i used this implementation to satisfy both
http://bazaar.launchpad.net/~hazmat/ensemble/local-provider/view/head:/ensemble/providers/lxc/container.py

I'm fine with this branch being merged without, i can incorporate this fix directly into lib/lxc this in a subsequent local-dev branch.

[1] Pep8 nitpicks

http://paste.ubuntu.com/682624/

[2] Re gustavo's _cmd comments, it would be nice if this was made public or pushed into a separate process module inside of ensemble/lib.. I'm currently using subprocess.check_output/subprocess.check_call very regularly, which are bit suboptimal in light of gustavo's comments.

cheers,

Kapil

review: Approve
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

also the script should be using ENSEMBLE_ZOOKEEPER instead of ZOOKEEPER_ADDRESS

lp:~bcsaller/pyjuju/lxc-lib updated
316. By Benjamin Saller

review changes and test fixes

317. By Benjamin Saller

allow _cmd to return (returncode, output) for debugging

318. By Benjamin Saller

use _cmd everywhere

319. By Benjamin Saller

pep8

320. By Benjamin Saller

s/ZOOKEEPER_ADDRESS/ENSEMBLE_ZOOKEEPER/

Revision history for this message
Benjamin Saller (bcsaller) wrote :
Download full text (7.2 KiB)

Responded to these point and Kapils later one. All changes have been
addressed. Re [22] the tests are not run as root, they ask for
permissions the first time they need them. When not run as root they
require interactivity but they don't run by default in the test suite
(as they can be quite expensive timewise and do need perms).

On Sat, Sep 3, 2011 at 9:25 AM, Gustavo Niemeyer <email address hidden> wrote:
> Review: Approve
> Quite a few details, but it looks very nice overall.V
>
> +1 with these sorted.
>
>
> [1]
>
> === added file 'ensemble/lib/lxc/data/ensemble-create'
>
> Nicely organized script, thanks.
>
> [2]
>
> +        ENSEMBLE_SOURCE="ppa:ensemble/ppa"
> +    elif [ $ENSEMBLE_ORIGIN = "lp" ]; then
> +        echo "Using Ensemble Branch $ensemble_source"
>
> s/$ensemble_source/$ENSEMBLE_SOURCE/
>
> Environment variables are case sensitive.
>
> Also, let's s/lp/branch/ please. Seems more clear and PPAs
> are also in Launchpad.
>
> As a minor, s/Branch/branch/
>
> [3]
>
> +    elif [ $ENSEMBLE_ORIGIN = "distro" ]; then
> +        echo "Using Ensemble distribution packages"
> +    fi
>
> Should error if $ENSEMBLE_ORIGIN contains garbage.
>
> [4]
>
> +        ENSEMBLE_SOURCE="ppa:ensemble/ppa"
> (...)
> +        apt-add-repository $ENSEMBLE_SOURCE
>
> Seems unnecessary to use the variable if the script is
> hardcoding the behavior.  Just use the value itself in
> the second line.
>
> [5]
>
> -        env_defaults = environment.get_serialization_data()
> -        options.placement = env_defaults[environment.name].get(
> -            "placement", UNASSIGNED_POLICY)
> +        options.placement = environment.placement
>
> Nice.
>
> [6]
>
> +BASE_PATH = os.path.normpath(
> +    os.path.abspath(
> +        os.path.join(os.path.dirname(__file__), "data")))
>
> s/BASE_PATH/DATA_PATH/?
>
> Also, abspath calls normpath internally.
>
> [7]
>
> +class LXCError(EnsembleError):
> +    """Indicates a low level error with an LXC container"""
> +    pass
>
> /pass/d
>
> [8]
>
> +    p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
> +                         env=dict(os.environ))
> +    r = p.wait()
> +    if  r != 0:
> +        # read the stdout/err streams and show the user
> +        print >>sys.stderr, p.stderr.read()
> +        raise LXCError(p.stdout.read())
>
> This logic is suboptimal in a few ways:
>
> - The stderr and stdout will be separated out, which means depending on
>  the output it will be hard to interpret what the error was about.
>
> - In case stdout or stderr get enough data to fill the kernel buffer,
>  the p.wait() will hang completely and never return.
>
> - The argument of env provided is the default behavior if not provided.
>
> - The stderr printed has no context.
>
> To fix all of these, use something along these lines:
>
>    p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
>    output = p.communicate()[0]
>    if p.wait() != 0:
>        raise LXCError(output)
>
> Note the argument of stderr (STDOUT, not PIPE). This will make them share
> the same file descriptor and intersperse the output as it happens, fixing
> the first point. communicate() avoids the second proble...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ensemble/control/add_unit.py'
2--- ensemble/control/add_unit.py 2011-08-08 17:25:27 +0000
3+++ ensemble/control/add_unit.py 2011-09-08 23:54:45 +0000
4@@ -28,9 +28,7 @@
5 """Add a new service unit."""
6 environment = get_environment(options)
7 if not options.placement:
8- env_defaults = environment.get_serialization_data()
9- options.placement = env_defaults[environment.name].get(
10- "placement", UNASSIGNED_POLICY)
11+ options.placement = environment.placement
12
13 return add_unit(
14 options.environments,
15
16=== modified file 'ensemble/control/deploy.py'
17--- ensemble/control/deploy.py 2011-08-17 18:42:35 +0000
18+++ ensemble/control/deploy.py 2011-09-08 23:54:45 +0000
19@@ -56,9 +56,7 @@
20 environment = get_environment(options)
21
22 if not options.placement:
23- env_defaults = environment.get_serialization_data()
24- options.placement = env_defaults[environment.name].get(
25- "placement", UNASSIGNED_POLICY)
26+ options.placement = environment.placement
27
28 return deploy(
29 options.environments,
30
31=== modified file 'ensemble/environment/environment.py'
32--- ensemble/environment/environment.py 2011-08-09 01:41:35 +0000
33+++ ensemble/environment/environment.py 2011-09-08 23:54:45 +0000
34@@ -40,5 +40,10 @@
35
36 @property
37 def placement(self):
38- """Unit placement policy name or None"""
39+ """The name of the default placement policy.
40+
41+ If the environment doesn't have a default unit placement
42+ policy None is returned
43+ """
44 return self._environment_config.get("placement")
45+
46
47=== added directory 'ensemble/lib/lxc'
48=== added file 'ensemble/lib/lxc/__init__.py'
49--- ensemble/lib/lxc/__init__.py 1970-01-01 00:00:00 +0000
50+++ ensemble/lib/lxc/__init__.py 2011-09-08 23:54:45 +0000
51@@ -0,0 +1,231 @@
52+import os
53+import pipes
54+import subprocess
55+import sys
56+import tempfile
57+
58+from twisted.internet.defer import inlineCallbacks, returnValue
59+from twisted.internet.threads import deferToThread
60+
61+from ensemble.errors import EnsembleError
62+
63+DATA_PATH = os.path.abspath(
64+ os.path.join(os.path.dirname(__file__), "data"))
65+
66+
67+class LXCError(EnsembleError):
68+ """Indicates a low level error with an LXC container"""
69+
70+
71+def _cmd(args):
72+ p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
73+ stdout_data, _ = p.communicate()
74+ r = p.returncode
75+ if r != 0:
76+ # read the stdout/err streams and show the user
77+ print >>sys.stderr, stdout_data
78+ raise LXCError(stdout_data)
79+ return (r, stdout_data)
80+
81+
82+# Wrapped lxc cli primitives
83+def _lxc_create(
84+ container_name, template="ubuntu", config_file=None, release="oneiric"):
85+ # the -- argument indicates the last parameters are passed
86+ # to the template and not handled by lxc-create
87+ args = ["sudo", "lxc-create",
88+ "-n", container_name,
89+ "-t", template,
90+ "-f", config_file,
91+ "--",
92+ "-r", release]
93+ return _cmd(args)
94+
95+
96+def _lxc_start(container_name, console_log=None):
97+ args = ["sudo", "lxc-start", "--daemon", "-n", container_name]
98+ if console_log:
99+ args.append("-c")
100+ args.append(console_log)
101+ return _cmd(args)
102+
103+
104+def _lxc_stop(container_name):
105+ _cmd(["sudo", "lxc-stop", "-n", container_name])
106+
107+
108+def _lxc_destroy(container_name):
109+ return _cmd(["sudo", "lxc-destroy", "-n", container_name])
110+
111+
112+def _lxc_ls():
113+ _, output = _cmd(["lxc-ls"])
114+ output = output.replace("\n", " ")
115+ return set([c for c in output.split(" ") if c])
116+
117+
118+def _lxc_wait(container_name, state="RUNNING"):
119+ """Wait for container to be in a given state RUNNING|STOPPED."""
120+
121+ def wait(container_name):
122+ rc, _ = _cmd(["sudo", "lxc-wait",
123+ "-n", container_name,
124+ "-s", state])
125+ return rc == 0
126+
127+ return deferToThread(wait, container_name)
128+
129+
130+def _customize_container(customize_script, container_root):
131+ if not os.path.isdir(container_root):
132+ raise LXCError("Expect container root directory: %s" %
133+ container_root)
134+ # write the container scripts into the container
135+ fd, in_path = tempfile.mkstemp(prefix=os.path.basename(customize_script),
136+ dir=os.path.join(container_root, "tmp"))
137+
138+ os.write(fd, open(customize_script, "r").read())
139+ os.close(fd)
140+ os.chmod(in_path, 0755)
141+
142+ args = ["sudo", "chroot", container_root,
143+ os.path.join("/tmp", os.path.basename(in_path))]
144+ return _cmd(args)
145+
146+
147+def validate_path(pathname):
148+ if not os.access(pathname, os.R_OK):
149+ raise LXCError("Invalid or unreadable file: %s" % pathname)
150+
151+
152+@inlineCallbacks
153+def get_containers(prefix):
154+ """Return a dictionary of containers key names to runtime boolean value.
155+
156+ :param prefix: Optionally specify a prefix that the container should
157+ match any returned containers.
158+ """
159+ _, output = yield deferToThread(_cmd, ["lxc-ls"])
160+
161+ containers = {}
162+ for i in filter(None, output.split("\n")):
163+ if i in containers:
164+ containers[i] = True
165+ else:
166+ containers[i] = False
167+
168+ if prefix:
169+ remove = [k for k in containers.keys() if not
170+ k.startswith(prefix)]
171+ map(containers.pop, remove)
172+
173+ returnValue(containers)
174+
175+
176+class LXCContainer(object):
177+ def __init__(self,
178+ container_name, configuration=None, network_name="virbr0",
179+ customize_script=None):
180+ """Create an LXCContainer
181+
182+ :param container_name: should be unique within the system
183+
184+ :param configuration: dict of values which are written into
185+ the containers /etc/ensemble/ensemble.conf file and expected
186+ to be consumed by the customize script
187+
188+ :param network_name: name of network link
189+
190+ :param customize_script: script used inside container to make
191+ it ensemble ready
192+ """
193+
194+ self.container_name = container_name
195+
196+ if customize_script is None:
197+ customize_script = os.path.join(DATA_PATH,
198+ "ensemble-create")
199+ self.customize_script = customize_script
200+ validate_path(self.customize_script)
201+
202+ self.configuration = configuration
203+ self.network_name = network_name
204+ self.running = False
205+
206+ @property
207+ def rootfs(self):
208+ return "/var/lib/lxc/%s/rootfs/" % self.container_name
209+
210+ def _p(self, path):
211+ if path[0] == "/":
212+ path = path[1:]
213+ return os.path.join(self.rootfs, path)
214+
215+ def _make_lxc_config(self, network_name):
216+ lxc_config = os.path.join(DATA_PATH, "lxc.conf")
217+ with open(lxc_config, "r") as fh:
218+ template = fh.read()
219+ fd, output_fn = tempfile.mkstemp(suffix=".conf")
220+ output_config = open(output_fn, "w")
221+ output_config.write(template % {"network_name": network_name})
222+ output_config.close()
223+
224+ validate_path(output_fn)
225+ return output_fn
226+
227+ @inlineCallbacks
228+ def _customize_container(self):
229+ config_dir = self._p("etc/ensemble")
230+ if not os.path.exists(config_dir):
231+ _cmd(["sudo", "mkdir", config_dir])
232+
233+ if not self.configuration:
234+ return
235+
236+ _, fn = tempfile.mkstemp()
237+ with open(fn, "w") as fp:
238+ for name, value in self.configuration.items():
239+ fp.write("%s=%s\n" % (name.upper(), pipes.quote(value)))
240+
241+ _cmd(["sudo", "mv", fn,
242+ self._p("etc/ensemble/ensemble.conf")])
243+
244+ yield deferToThread(
245+ _customize_container, self.customize_script, self.rootfs)
246+
247+ @inlineCallbacks
248+ def execute(self, args):
249+ if not isinstance(args, (list, tuple)):
250+ args = [args, ]
251+
252+ args = ["sudo", "chroot", self.rootfs] + args
253+ yield deferToThread(_cmd, args)
254+
255+ @inlineCallbacks
256+ def create(self):
257+ # open the template file and create a new temp processed
258+ # version
259+ lxc_config = self._make_lxc_config(self.network_name)
260+ yield deferToThread(_lxc_create, self.container_name,
261+ config_file=lxc_config)
262+ yield self._customize_container()
263+ os.unlink(lxc_config)
264+
265+ @inlineCallbacks
266+ def run(self):
267+ if not os.path.exists(self.rootfs):
268+ yield self.create()
269+
270+ yield deferToThread(_lxc_start, self.container_name)
271+ self.running = yield _lxc_wait(self.container_name, "RUNNING")
272+
273+ @inlineCallbacks
274+ def stop(self):
275+ yield deferToThread(_lxc_stop, self.container_name)
276+ yield _lxc_wait(self.container_name, "STOPPED")
277+ self.running = False
278+
279+ @inlineCallbacks
280+ def destroy(self):
281+ yield self.stop()
282+ yield deferToThread(_lxc_destroy, self.container_name)
283
284=== added directory 'ensemble/lib/lxc/data'
285=== added file 'ensemble/lib/lxc/data/ensemble-create'
286--- ensemble/lib/lxc/data/ensemble-create 1970-01-01 00:00:00 +0000
287+++ ensemble/lib/lxc/data/ensemble-create 2011-09-08 23:54:45 +0000
288@@ -0,0 +1,63 @@
289+#!bin/bash
290+set -x
291+
292+setup_ensemble()
293+{
294+ if [ ! -e /var/lib/ensemble ]; then
295+ mkdir /var/lib/ensemble
296+ fi
297+
298+ if [ ! -e /var/log/ensemble ]; then
299+ mkdir /var/log/ensemble
300+ fi
301+
302+ if [ $ENSEMBLE_ORIGIN = "ppa" ]; then
303+ echo "Using Ensemble PPA for container"
304+ elif [ $ENSEMBLE_ORIGIN = "branch" ]; then
305+ echo "Using Ensemble Branch $ENSEMBLE_SOURCE"
306+ elif [ $ENSEMBLE_ORIGIN = "distro" ]; then
307+ echo "Using Ensemble distribution packages"
308+ else
309+ echo "Unknown Ensemble origin policy $ENSEMBLE_ORIGIN: expected [ppa|branch|disto]"
310+ exit 1
311+ fi
312+
313+ echo "Setting up ensemble in container"
314+ apt-get install --force-yes -y bzr tmux
315+
316+ if [ $ENSEMBLE_ORIGIN = "ppa" ]; then
317+ # the echo forces an enter to get around the interactive
318+ # prompt in apt-add-repository
319+ echo y | apt-add-repository ppa:ensemble/ppa
320+ apt-get update
321+ apt-get install --force-yes -y ensemble python-txzookeeper
322+ elif [ $ENSEMBLE_ORIGIN = "branch" ]; then
323+ apt-get install --force-yes -y python-txzookeeper
324+ mkdir /usr/lib/ensemble
325+ bzr branch $ENSEMBLE_SOURCE /usr/lib/ensemble/ensemble
326+ bash -c "cd /usr/lib/ensemble/ensemble && sudo python setup.py develop"
327+ elif [ $ENSEMBLE_ORIGIN = "distro" ]; then
328+ apt-get install --force-yes -y ensemble python-txzookeeper
329+ fi
330+
331+}
332+
333+# load configuration
334+source /etc/ensemble/ensemble.conf
335+
336+if [ -z "$ENSEMBLE_ORIGIN" ]; then
337+ ENSEMBLE_ORIGIN=distro
338+fi
339+
340+if [ -z "$ENSEMBLE_ZOOKEEPER" ]; then
341+ echo "'ENSEMBLE_ZOOKEEPER' is required"
342+ exit 1
343+fi
344+
345+if [ "$(id -u)" != "0" ]; then
346+ echo "This script should be run as 'root'"
347+ exit 1
348+fi
349+
350+
351+setup_ensemble
352\ No newline at end of file
353
354=== added file 'ensemble/lib/lxc/data/lxc.conf'
355--- ensemble/lib/lxc/data/lxc.conf 1970-01-01 00:00:00 +0000
356+++ ensemble/lib/lxc/data/lxc.conf 2011-09-08 23:54:45 +0000
357@@ -0,0 +1,3 @@
358+lxc.network.type=veth
359+lxc.network.link=%(network_name)s
360+lxc.network.flags=up
361
362=== added directory 'ensemble/lib/lxc/tests'
363=== added file 'ensemble/lib/lxc/tests/__init__.py'
364=== added file 'ensemble/lib/lxc/tests/test_lxc.py'
365--- ensemble/lib/lxc/tests/test_lxc.py 1970-01-01 00:00:00 +0000
366+++ ensemble/lib/lxc/tests/test_lxc.py 2011-09-08 23:54:45 +0000
367@@ -0,0 +1,151 @@
368+import os
369+import tempfile
370+
371+from twisted.internet.defer import inlineCallbacks
372+from twisted.internet.threads import deferToThread
373+
374+from ensemble.lib.lxc import (_lxc_start, _lxc_stop, _lxc_create,
375+ _lxc_wait, _lxc_ls, _lxc_destroy,
376+ LXCContainer, get_containers)
377+from ensemble.lib.testing import TestCase, get_test_zookeeper_address
378+
379+
380+def run_lxc_tests():
381+ if os.environ.get("TEST_LXC"):
382+ return None
383+ return "TEST_LXC=1 to include lxc tests"
384+
385+
386+DATA_PATH = os.path.abspath(
387+ os.path.join(os.path.dirname(__file__), "..", "data"))
388+
389+
390+DEFAULT_CONTAINER = "lxc_test"
391+
392+
393+class LXCTest(TestCase):
394+ timeout = 240
395+ skip = run_lxc_tests()
396+
397+ def setUp(self):
398+ self.config = self.make_config()
399+
400+ @self.addCleanup
401+ def remove_config():
402+ if os.path.exists(self.config):
403+ os.unlink(self.config)
404+
405+ def make_config(self, network_name="virbr0"):
406+ lxc_config = os.path.join(DATA_PATH, "lxc.conf")
407+ template = open(lxc_config, "r").read()
408+
409+ fd, output_fn = tempfile.mkstemp(suffix=".conf")
410+ output_config = open(output_fn, "w")
411+ output_config.write(template % {"network_name": network_name})
412+ output_config.close()
413+
414+ return output_fn
415+
416+ def cleanContainer(self, container_name):
417+ if os.path.exists("/var/lib/lxc/%s" % container_name):
418+ _lxc_stop(container_name)
419+ _lxc_destroy(container_name)
420+
421+ def test_lxc_create(self):
422+ self.addCleanup(self.cleanContainer, DEFAULT_CONTAINER)
423+
424+ _lxc_create(DEFAULT_CONTAINER, config_file=self.config)
425+
426+ # verify we can find the container
427+ output = _lxc_ls()
428+ self.assertIn(DEFAULT_CONTAINER, output)
429+
430+ # remove and verify the container was removed
431+ _lxc_destroy(DEFAULT_CONTAINER)
432+ output = _lxc_ls()
433+ self.assertNotIn(DEFAULT_CONTAINER, output)
434+
435+ def test_lxc_start(self):
436+ self.addCleanup(self.cleanContainer, DEFAULT_CONTAINER)
437+
438+ _lxc_create(DEFAULT_CONTAINER, config_file=self.config)
439+
440+ _lxc_start(DEFAULT_CONTAINER)
441+ _lxc_stop(DEFAULT_CONTAINER)
442+
443+ @inlineCallbacks
444+ def test_lxc_deferred(self):
445+ self.addCleanup(self.cleanContainer, DEFAULT_CONTAINER)
446+ yield deferToThread(_lxc_create, DEFAULT_CONTAINER, config_file=self.config)
447+ yield deferToThread(_lxc_start, DEFAULT_CONTAINER)
448+
449+ @inlineCallbacks
450+ def test_lxc_container(self):
451+ self.addCleanup(self.cleanContainer, DEFAULT_CONTAINER)
452+
453+ c = LXCContainer(DEFAULT_CONTAINER,
454+ dict(ensemble_zookeeper=get_test_zookeeper_address(),
455+ ensemble_origin="distro"))
456+ self.assertFalse(c.running)
457+ yield c.run()
458+ self.assertTrue(c.running)
459+
460+ output = _lxc_ls()
461+ self.assertIn(DEFAULT_CONTAINER, output)
462+
463+ # verify we have a path into the container
464+ self.assertTrue(os.path.exists(c.rootfs))
465+
466+ # expect a processed config file above the rootfs
467+ # -- this requires root to read
468+ #network = open(os.path.join(c.rootfs, "..", "config"), "r").read()
469+ #self.assertIn("lxc.network.link=virbr0", network)
470+
471+ # check that some of the customize modifications were made
472+ self.assertTrue(os.path.exists(os.path.join(
473+ c.rootfs, "etc", "ensemble")))
474+ self.assertTrue(os.path.exists(os.path.join(
475+ c.rootfs, "usr", "bin", "ensemble")))
476+
477+ # verify that we have shell variables in rootfs/etc/ensemble.conf
478+ config = open(os.path.join(c.rootfs, "etc", "ensemble", "ensemble.conf"), "r").read()
479+ self.assertIn("ENSEMBLE_ZOOKEEPER=%s" % get_test_zookeeper_address(), config)
480+
481+ # verify that we are in containers
482+ containers = yield get_containers(None)
483+ self.assertEqual(containers[DEFAULT_CONTAINER], True)
484+
485+ # tear it down
486+ yield c.destroy()
487+ self.assertFalse(c.running)
488+
489+ containers = yield get_containers(None)
490+ self.assertNotIn(DEFAULT_CONTAINER, containers)
491+
492+ # and its gone
493+ output = _lxc_ls()
494+ self.assertNotIn(DEFAULT_CONTAINER, output)
495+
496+ @inlineCallbacks
497+ def test_lxc_wait(self):
498+ self.addCleanup(self.cleanContainer, DEFAULT_CONTAINER)
499+
500+ _lxc_create(DEFAULT_CONTAINER, config_file=self.config)
501+
502+ _lxc_start(DEFAULT_CONTAINER)
503+
504+ def waitForState(result):
505+ self.assertEqual(result, True)
506+
507+ d = _lxc_wait(DEFAULT_CONTAINER, "RUNNING")
508+ d.addCallback(waitForState)
509+ yield d
510+
511+ _lxc_stop(DEFAULT_CONTAINER)
512+ yield _lxc_wait(DEFAULT_CONTAINER, "STOPPED")
513+ _lxc_destroy(DEFAULT_CONTAINER)
514+
515+
516+
517+
518+

Subscribers

People subscribed via source and target branches

to status/vote changes: