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
=== modified file 'ensemble/control/add_unit.py'
--- ensemble/control/add_unit.py 2011-08-08 17:25:27 +0000
+++ ensemble/control/add_unit.py 2011-09-08 23:54:45 +0000
@@ -28,9 +28,7 @@
28 """Add a new service unit."""28 """Add a new service unit."""
29 environment = get_environment(options)29 environment = get_environment(options)
30 if not options.placement:30 if not options.placement:
31 env_defaults = environment.get_serialization_data()31 options.placement = environment.placement
32 options.placement = env_defaults[environment.name].get(
33 "placement", UNASSIGNED_POLICY)
3432
35 return add_unit(33 return add_unit(
36 options.environments,34 options.environments,
3735
=== modified file 'ensemble/control/deploy.py'
--- ensemble/control/deploy.py 2011-08-17 18:42:35 +0000
+++ ensemble/control/deploy.py 2011-09-08 23:54:45 +0000
@@ -56,9 +56,7 @@
56 environment = get_environment(options)56 environment = get_environment(options)
5757
58 if not options.placement:58 if not options.placement:
59 env_defaults = environment.get_serialization_data()59 options.placement = environment.placement
60 options.placement = env_defaults[environment.name].get(
61 "placement", UNASSIGNED_POLICY)
6260
63 return deploy(61 return deploy(
64 options.environments,62 options.environments,
6563
=== modified file 'ensemble/environment/environment.py'
--- ensemble/environment/environment.py 2011-08-09 01:41:35 +0000
+++ ensemble/environment/environment.py 2011-09-08 23:54:45 +0000
@@ -40,5 +40,10 @@
4040
41 @property41 @property
42 def placement(self):42 def placement(self):
43 """Unit placement policy name or None"""43 """The name of the default placement policy.
44
45 If the environment doesn't have a default unit placement
46 policy None is returned
47 """
44 return self._environment_config.get("placement")48 return self._environment_config.get("placement")
49
4550
=== added directory 'ensemble/lib/lxc'
=== added file 'ensemble/lib/lxc/__init__.py'
--- ensemble/lib/lxc/__init__.py 1970-01-01 00:00:00 +0000
+++ ensemble/lib/lxc/__init__.py 2011-09-08 23:54:45 +0000
@@ -0,0 +1,231 @@
1import os
2import pipes
3import subprocess
4import sys
5import tempfile
6
7from twisted.internet.defer import inlineCallbacks, returnValue
8from twisted.internet.threads import deferToThread
9
10from ensemble.errors import EnsembleError
11
12DATA_PATH = os.path.abspath(
13 os.path.join(os.path.dirname(__file__), "data"))
14
15
16class LXCError(EnsembleError):
17 """Indicates a low level error with an LXC container"""
18
19
20def _cmd(args):
21 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
22 stdout_data, _ = p.communicate()
23 r = p.returncode
24 if r != 0:
25 # read the stdout/err streams and show the user
26 print >>sys.stderr, stdout_data
27 raise LXCError(stdout_data)
28 return (r, stdout_data)
29
30
31# Wrapped lxc cli primitives
32def _lxc_create(
33 container_name, template="ubuntu", config_file=None, release="oneiric"):
34 # the -- argument indicates the last parameters are passed
35 # to the template and not handled by lxc-create
36 args = ["sudo", "lxc-create",
37 "-n", container_name,
38 "-t", template,
39 "-f", config_file,
40 "--",
41 "-r", release]
42 return _cmd(args)
43
44
45def _lxc_start(container_name, console_log=None):
46 args = ["sudo", "lxc-start", "--daemon", "-n", container_name]
47 if console_log:
48 args.append("-c")
49 args.append(console_log)
50 return _cmd(args)
51
52
53def _lxc_stop(container_name):
54 _cmd(["sudo", "lxc-stop", "-n", container_name])
55
56
57def _lxc_destroy(container_name):
58 return _cmd(["sudo", "lxc-destroy", "-n", container_name])
59
60
61def _lxc_ls():
62 _, output = _cmd(["lxc-ls"])
63 output = output.replace("\n", " ")
64 return set([c for c in output.split(" ") if c])
65
66
67def _lxc_wait(container_name, state="RUNNING"):
68 """Wait for container to be in a given state RUNNING|STOPPED."""
69
70 def wait(container_name):
71 rc, _ = _cmd(["sudo", "lxc-wait",
72 "-n", container_name,
73 "-s", state])
74 return rc == 0
75
76 return deferToThread(wait, container_name)
77
78
79def _customize_container(customize_script, container_root):
80 if not os.path.isdir(container_root):
81 raise LXCError("Expect container root directory: %s" %
82 container_root)
83 # write the container scripts into the container
84 fd, in_path = tempfile.mkstemp(prefix=os.path.basename(customize_script),
85 dir=os.path.join(container_root, "tmp"))
86
87 os.write(fd, open(customize_script, "r").read())
88 os.close(fd)
89 os.chmod(in_path, 0755)
90
91 args = ["sudo", "chroot", container_root,
92 os.path.join("/tmp", os.path.basename(in_path))]
93 return _cmd(args)
94
95
96def validate_path(pathname):
97 if not os.access(pathname, os.R_OK):
98 raise LXCError("Invalid or unreadable file: %s" % pathname)
99
100
101@inlineCallbacks
102def get_containers(prefix):
103 """Return a dictionary of containers key names to runtime boolean value.
104
105 :param prefix: Optionally specify a prefix that the container should
106 match any returned containers.
107 """
108 _, output = yield deferToThread(_cmd, ["lxc-ls"])
109
110 containers = {}
111 for i in filter(None, output.split("\n")):
112 if i in containers:
113 containers[i] = True
114 else:
115 containers[i] = False
116
117 if prefix:
118 remove = [k for k in containers.keys() if not
119 k.startswith(prefix)]
120 map(containers.pop, remove)
121
122 returnValue(containers)
123
124
125class LXCContainer(object):
126 def __init__(self,
127 container_name, configuration=None, network_name="virbr0",
128 customize_script=None):
129 """Create an LXCContainer
130
131 :param container_name: should be unique within the system
132
133 :param configuration: dict of values which are written into
134 the containers /etc/ensemble/ensemble.conf file and expected
135 to be consumed by the customize script
136
137 :param network_name: name of network link
138
139 :param customize_script: script used inside container to make
140 it ensemble ready
141 """
142
143 self.container_name = container_name
144
145 if customize_script is None:
146 customize_script = os.path.join(DATA_PATH,
147 "ensemble-create")
148 self.customize_script = customize_script
149 validate_path(self.customize_script)
150
151 self.configuration = configuration
152 self.network_name = network_name
153 self.running = False
154
155 @property
156 def rootfs(self):
157 return "/var/lib/lxc/%s/rootfs/" % self.container_name
158
159 def _p(self, path):
160 if path[0] == "/":
161 path = path[1:]
162 return os.path.join(self.rootfs, path)
163
164 def _make_lxc_config(self, network_name):
165 lxc_config = os.path.join(DATA_PATH, "lxc.conf")
166 with open(lxc_config, "r") as fh:
167 template = fh.read()
168 fd, output_fn = tempfile.mkstemp(suffix=".conf")
169 output_config = open(output_fn, "w")
170 output_config.write(template % {"network_name": network_name})
171 output_config.close()
172
173 validate_path(output_fn)
174 return output_fn
175
176 @inlineCallbacks
177 def _customize_container(self):
178 config_dir = self._p("etc/ensemble")
179 if not os.path.exists(config_dir):
180 _cmd(["sudo", "mkdir", config_dir])
181
182 if not self.configuration:
183 return
184
185 _, fn = tempfile.mkstemp()
186 with open(fn, "w") as fp:
187 for name, value in self.configuration.items():
188 fp.write("%s=%s\n" % (name.upper(), pipes.quote(value)))
189
190 _cmd(["sudo", "mv", fn,
191 self._p("etc/ensemble/ensemble.conf")])
192
193 yield deferToThread(
194 _customize_container, self.customize_script, self.rootfs)
195
196 @inlineCallbacks
197 def execute(self, args):
198 if not isinstance(args, (list, tuple)):
199 args = [args, ]
200
201 args = ["sudo", "chroot", self.rootfs] + args
202 yield deferToThread(_cmd, args)
203
204 @inlineCallbacks
205 def create(self):
206 # open the template file and create a new temp processed
207 # version
208 lxc_config = self._make_lxc_config(self.network_name)
209 yield deferToThread(_lxc_create, self.container_name,
210 config_file=lxc_config)
211 yield self._customize_container()
212 os.unlink(lxc_config)
213
214 @inlineCallbacks
215 def run(self):
216 if not os.path.exists(self.rootfs):
217 yield self.create()
218
219 yield deferToThread(_lxc_start, self.container_name)
220 self.running = yield _lxc_wait(self.container_name, "RUNNING")
221
222 @inlineCallbacks
223 def stop(self):
224 yield deferToThread(_lxc_stop, self.container_name)
225 yield _lxc_wait(self.container_name, "STOPPED")
226 self.running = False
227
228 @inlineCallbacks
229 def destroy(self):
230 yield self.stop()
231 yield deferToThread(_lxc_destroy, self.container_name)
0232
=== added directory 'ensemble/lib/lxc/data'
=== added file 'ensemble/lib/lxc/data/ensemble-create'
--- ensemble/lib/lxc/data/ensemble-create 1970-01-01 00:00:00 +0000
+++ ensemble/lib/lxc/data/ensemble-create 2011-09-08 23:54:45 +0000
@@ -0,0 +1,63 @@
1#!bin/bash
2set -x
3
4setup_ensemble()
5{
6 if [ ! -e /var/lib/ensemble ]; then
7 mkdir /var/lib/ensemble
8 fi
9
10 if [ ! -e /var/log/ensemble ]; then
11 mkdir /var/log/ensemble
12 fi
13
14 if [ $ENSEMBLE_ORIGIN = "ppa" ]; then
15 echo "Using Ensemble PPA for container"
16 elif [ $ENSEMBLE_ORIGIN = "branch" ]; then
17 echo "Using Ensemble Branch $ENSEMBLE_SOURCE"
18 elif [ $ENSEMBLE_ORIGIN = "distro" ]; then
19 echo "Using Ensemble distribution packages"
20 else
21 echo "Unknown Ensemble origin policy $ENSEMBLE_ORIGIN: expected [ppa|branch|disto]"
22 exit 1
23 fi
24
25 echo "Setting up ensemble in container"
26 apt-get install --force-yes -y bzr tmux
27
28 if [ $ENSEMBLE_ORIGIN = "ppa" ]; then
29 # the echo forces an enter to get around the interactive
30 # prompt in apt-add-repository
31 echo y | apt-add-repository ppa:ensemble/ppa
32 apt-get update
33 apt-get install --force-yes -y ensemble python-txzookeeper
34 elif [ $ENSEMBLE_ORIGIN = "branch" ]; then
35 apt-get install --force-yes -y python-txzookeeper
36 mkdir /usr/lib/ensemble
37 bzr branch $ENSEMBLE_SOURCE /usr/lib/ensemble/ensemble
38 bash -c "cd /usr/lib/ensemble/ensemble && sudo python setup.py develop"
39 elif [ $ENSEMBLE_ORIGIN = "distro" ]; then
40 apt-get install --force-yes -y ensemble python-txzookeeper
41 fi
42
43}
44
45# load configuration
46source /etc/ensemble/ensemble.conf
47
48if [ -z "$ENSEMBLE_ORIGIN" ]; then
49 ENSEMBLE_ORIGIN=distro
50fi
51
52if [ -z "$ENSEMBLE_ZOOKEEPER" ]; then
53 echo "'ENSEMBLE_ZOOKEEPER' is required"
54 exit 1
55fi
56
57if [ "$(id -u)" != "0" ]; then
58 echo "This script should be run as 'root'"
59 exit 1
60fi
61
62
63setup_ensemble
0\ No newline at end of file64\ No newline at end of file
165
=== added file 'ensemble/lib/lxc/data/lxc.conf'
--- ensemble/lib/lxc/data/lxc.conf 1970-01-01 00:00:00 +0000
+++ ensemble/lib/lxc/data/lxc.conf 2011-09-08 23:54:45 +0000
@@ -0,0 +1,3 @@
1lxc.network.type=veth
2lxc.network.link=%(network_name)s
3lxc.network.flags=up
04
=== added directory 'ensemble/lib/lxc/tests'
=== added file 'ensemble/lib/lxc/tests/__init__.py'
=== added file 'ensemble/lib/lxc/tests/test_lxc.py'
--- ensemble/lib/lxc/tests/test_lxc.py 1970-01-01 00:00:00 +0000
+++ ensemble/lib/lxc/tests/test_lxc.py 2011-09-08 23:54:45 +0000
@@ -0,0 +1,151 @@
1import os
2import tempfile
3
4from twisted.internet.defer import inlineCallbacks
5from twisted.internet.threads import deferToThread
6
7from ensemble.lib.lxc import (_lxc_start, _lxc_stop, _lxc_create,
8 _lxc_wait, _lxc_ls, _lxc_destroy,
9 LXCContainer, get_containers)
10from ensemble.lib.testing import TestCase, get_test_zookeeper_address
11
12
13def run_lxc_tests():
14 if os.environ.get("TEST_LXC"):
15 return None
16 return "TEST_LXC=1 to include lxc tests"
17
18
19DATA_PATH = os.path.abspath(
20 os.path.join(os.path.dirname(__file__), "..", "data"))
21
22
23DEFAULT_CONTAINER = "lxc_test"
24
25
26class LXCTest(TestCase):
27 timeout = 240
28 skip = run_lxc_tests()
29
30 def setUp(self):
31 self.config = self.make_config()
32
33 @self.addCleanup
34 def remove_config():
35 if os.path.exists(self.config):
36 os.unlink(self.config)
37
38 def make_config(self, network_name="virbr0"):
39 lxc_config = os.path.join(DATA_PATH, "lxc.conf")
40 template = open(lxc_config, "r").read()
41
42 fd, output_fn = tempfile.mkstemp(suffix=".conf")
43 output_config = open(output_fn, "w")
44 output_config.write(template % {"network_name": network_name})
45 output_config.close()
46
47 return output_fn
48
49 def cleanContainer(self, container_name):
50 if os.path.exists("/var/lib/lxc/%s" % container_name):
51 _lxc_stop(container_name)
52 _lxc_destroy(container_name)
53
54 def test_lxc_create(self):
55 self.addCleanup(self.cleanContainer, DEFAULT_CONTAINER)
56
57 _lxc_create(DEFAULT_CONTAINER, config_file=self.config)
58
59 # verify we can find the container
60 output = _lxc_ls()
61 self.assertIn(DEFAULT_CONTAINER, output)
62
63 # remove and verify the container was removed
64 _lxc_destroy(DEFAULT_CONTAINER)
65 output = _lxc_ls()
66 self.assertNotIn(DEFAULT_CONTAINER, output)
67
68 def test_lxc_start(self):
69 self.addCleanup(self.cleanContainer, DEFAULT_CONTAINER)
70
71 _lxc_create(DEFAULT_CONTAINER, config_file=self.config)
72
73 _lxc_start(DEFAULT_CONTAINER)
74 _lxc_stop(DEFAULT_CONTAINER)
75
76 @inlineCallbacks
77 def test_lxc_deferred(self):
78 self.addCleanup(self.cleanContainer, DEFAULT_CONTAINER)
79 yield deferToThread(_lxc_create, DEFAULT_CONTAINER, config_file=self.config)
80 yield deferToThread(_lxc_start, DEFAULT_CONTAINER)
81
82 @inlineCallbacks
83 def test_lxc_container(self):
84 self.addCleanup(self.cleanContainer, DEFAULT_CONTAINER)
85
86 c = LXCContainer(DEFAULT_CONTAINER,
87 dict(ensemble_zookeeper=get_test_zookeeper_address(),
88 ensemble_origin="distro"))
89 self.assertFalse(c.running)
90 yield c.run()
91 self.assertTrue(c.running)
92
93 output = _lxc_ls()
94 self.assertIn(DEFAULT_CONTAINER, output)
95
96 # verify we have a path into the container
97 self.assertTrue(os.path.exists(c.rootfs))
98
99 # expect a processed config file above the rootfs
100 # -- this requires root to read
101 #network = open(os.path.join(c.rootfs, "..", "config"), "r").read()
102 #self.assertIn("lxc.network.link=virbr0", network)
103
104 # check that some of the customize modifications were made
105 self.assertTrue(os.path.exists(os.path.join(
106 c.rootfs, "etc", "ensemble")))
107 self.assertTrue(os.path.exists(os.path.join(
108 c.rootfs, "usr", "bin", "ensemble")))
109
110 # verify that we have shell variables in rootfs/etc/ensemble.conf
111 config = open(os.path.join(c.rootfs, "etc", "ensemble", "ensemble.conf"), "r").read()
112 self.assertIn("ENSEMBLE_ZOOKEEPER=%s" % get_test_zookeeper_address(), config)
113
114 # verify that we are in containers
115 containers = yield get_containers(None)
116 self.assertEqual(containers[DEFAULT_CONTAINER], True)
117
118 # tear it down
119 yield c.destroy()
120 self.assertFalse(c.running)
121
122 containers = yield get_containers(None)
123 self.assertNotIn(DEFAULT_CONTAINER, containers)
124
125 # and its gone
126 output = _lxc_ls()
127 self.assertNotIn(DEFAULT_CONTAINER, output)
128
129 @inlineCallbacks
130 def test_lxc_wait(self):
131 self.addCleanup(self.cleanContainer, DEFAULT_CONTAINER)
132
133 _lxc_create(DEFAULT_CONTAINER, config_file=self.config)
134
135 _lxc_start(DEFAULT_CONTAINER)
136
137 def waitForState(result):
138 self.assertEqual(result, True)
139
140 d = _lxc_wait(DEFAULT_CONTAINER, "RUNNING")
141 d.addCallback(waitForState)
142 yield d
143
144 _lxc_stop(DEFAULT_CONTAINER)
145 yield _lxc_wait(DEFAULT_CONTAINER, "STOPPED")
146 _lxc_destroy(DEFAULT_CONTAINER)
147
148
149
150
151

Subscribers

People subscribed via source and target branches

to status/vote changes: