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