Merge lp:~themue/golxc/001-added-sudo-and-first-test into lp:golxc
- 001-added-sudo-and-first-test
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 2 |
Proposed branch: | lp:~themue/golxc/001-added-sudo-and-first-test |
Merge into: | lp:golxc |
Diff against target: |
797 lines (+417/-191) 4 files modified
export_test.go (+6/-0) golxc.go (+156/-191) golxc_test.go (+247/-0) golxc_test.sh (+8/-0) |
To merge this branch: | bzr merge lp:~themue/golxc/001-added-sudo-and-first-test |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Frank Mueller | Pending | ||
Review via email: mp+134956@code.launchpad.net |
Commit message
Description of the change
golxc: add first container tests
The package now has the first functions for the
testing of the container. Two external scripts
help to perform the tests as a root.
- 6. By Frank Mueller
-
golxc: added test for running container
- 7. By Frank Mueller
-
golxc: added more tests
Roger Peppe (rogpeppe) wrote : | # |
- 8. By Frank Mueller
-
golxc: simplification after review
Roger Peppe (rogpeppe) wrote : | # |
LGTM apart from the error messages. i think we don't want to discard the
important information printed to stderr.
https:/
File golxc.go (right):
https:/
golxc.go:9: // Frank Mueller <email address hidden>
On 2012/11/20 16:34:04, TheMue wrote:
> On 2012/11/20 15:20:11, rog wrote:
> > do we need individual attributions?
> Got it from packages like goamz. But personally don't need it.
i think goamz is different because it really is almost all gustavo's
work. YMMV, whatever.
https:/
golxc.go:370: log.Fatalf("error executing %q: %v",
strings.
On 2012/11/20 16:34:04, TheMue wrote:
> On 2012/11/20 15:20:11, rog wrote:
> > d
> > but i wonder whether the error message here should actually
incorporate the
> > error printed to stderr. nothing that calls run actually uses the
stderr
> > returned, and the error returned by cmd.Run is, in most cases,
almost
> > meaningless.
> The err here is an error of Command executing the command, not the
stderr of the
> executed command. I changed it to mode it more clear. So far we have
no usage
> for stderr, but it's also not everything implemented. If there stays
no need it
> will be removed.
my point is that if a command fails, the only message we'll get will be
"exit status 1" or whatever, and the useful error message that the
command has printed to stderr will be discarded.
i think we should put at least some of that information into the err
return.
https:/
File golxc.go (right):
https:/
golxc.go:35: // LogLevel represents a containers log level.
s/containers/
https:/
golxc.go:86: c.logLevel = logLevel
is it really worth having the setter method here?
could we just export the relevant fields?
https:/
golxc.go:273: func (c *Container) wait(states ...State) error {
given that nothing is actually making use of the functionality yet, we
could just have wait(state State) and not bother with combining states
at all. just a then this function is only 5 lines. just a thought.
- 9. By Frank Mueller
-
golxc: more changes after review
William Reade (fwereade) wrote : | # |
Not sure about the tests -- it looks like there's plenty of opportunity
from pollution from failures. Is there some motivation for this
behaviour that I've missed?
Also, it kinda looks as though the tests will fail if your system is
already running a container. Can you see any way around this?
https:/
File golxc.go (right):
https:/
golxc.go:217: _, _, err := run("lxc-wait", "-n", c.name, "-s",
combinedStates)
I guess LXC will complain about impossible state combinations?
https:/
golxc.go:257: // String returns a short information about the container.
s/a short/brief/
perhaps?
https:/
File golxc_test.go (right):
https:/
golxc_test.go:20: lc.Destroy()
Shouldn't these be destroyed inline via defer?
- 10. By Frank Mueller
-
golxc: hardened package and added more tests after review
William Reade (fwereade) wrote : | # |
A few more comments...
https:/
File golxc.go (right):
https:/
golxc.go:180: // Unfreeze thaws all a froozen container's processes.
s/rooz/roz/
https:/
golxc.go:186: return fmt.Errorf(
ditto
https:/
golxc.go:219: switch len(states) {
I'm not sure the switch is a big win over a simple err return when no
states are supplied -- the one-state case may be more efficient but I'm
not sure it really aids clarity. Up to you though.
https:/
golxc.go:271: // String returns a brief information about the container.
s/a //
?
https:/
File golxc_test.go (right):
https:/
golxc_test.go:25: c.Assert(
Shouldn't we defer the Destroy at this point?
https:/
golxc_test.go:40: defer lc1.Destroy()
This should be either 1 or 2 lines down, shouldn't it? And surely we
should be asserting no error?
https:/
golxc_test.go:74: defer lc.Destroy()
Handle errors? (after checking no error on preceding Create)
https:/
golxc_test.go:87: defer lc1.Destroy()
ditto
https:/
golxc_test.go:91: defer lc2.Destroy()
ditto
https:/
golxc_test.go:116: defer lc.Destroy()
ditto
https:/
golxc_test.go:137: defer lc.Destroy()
ditto
https:/
golxc_test.go:138: go lc.Start("", "")
Destroy works even if the container is running?
https:/
golxc_test.go:161: // Test that a not running container can't be
froozen.
s/rooz/roz/
https:/
golxc_test.go:171: // Test that a non-existing container can't be
froozen.
ditto
https:/
golxc_test.go:179: // Test that a non-existing container can't be
unfroozen.
ditto
https:/
golxc_test.go:186: func (l *LXCSuite) TestUnfreezeNot
ditto
https:/
golxc_test.go:187: // Test that a running container can't be unfroozen.
ditto
https:/
golxc_test.go:193: defer lc.Stop()
error checking
https:/
golxc_test.go:195: c.Assert(err, ErrorMatches, "container .* is not
froozen...
- 11. By Frank Mueller
-
golxc: better error checking after review
William Reade (fwereade) wrote : | # |
Roger Peppe (rogpeppe) wrote : | # |
looks good to me apart from (still) the error messages.
a few minor suggestions in addition to that.
https:/
File golxc.go (right):
https:/
golxc.go:219: switch len(states) {
On 2012/11/22 14:35:57, fwereade wrote:
> I'm not sure the switch is a big win over a simple err return when no
states are
> supplied -- the one-state case may be more efficient but I'm not sure
it really
> aids clarity. Up to you though.
i think i agree here. we don't mind about a single non-escaping
allocation. i'd lose the "case 1" and make the "case 0" into an if that
returns the error.
https:/
golxc.go:271: // String returns a brief information about the container.
On 2012/11/22 14:35:57, fwereade wrote:
> s/a //
> ?
s/a brief // ?
https:/
golxc.go:277: return fmt.Sprintf(
c.name, state, pid)
i'd be tempted to go a little more terse than this.
something like this, as an example, perhaps?
container "foo" (STARTING, pid 2353)
https:/
golxc.go:304: return outbuf.String(), errbuf.String(), err
i'm still not happy with this. if we call Container.Start and get the
error "exit status 1", that's an extremely unhelpful error. for an
example of what might be a reasonable approach, see how lbox deals with
error returns from running bzr. (search for bzrError in the lbox
source). you might want to tailor that for the way that lxc tends to
print error messages.
https:/
File golxc_test.go (right):
https:/
golxc_test.go:12: type LXCSuite struct{}
i wonder if it might be worth Skipping the suite (with a warning,
perhaps) if we're not running as root.
- 12. By Frank Mueller
-
golxc: added error type and test skipping if not root
Roger Peppe (rogpeppe) wrote : | # |
much better, but still not *quite* there.
i'd like to see at least one test that verifies the new error logic.
https:/
File golxc.go (right):
https:/
golxc.go:29: if strings.
perhaps slightly more robust to do: HasPrefix(e.StdErr, e.Name + ": ")
and more obvious to the reader what the extra 2 chars are too.
also, what if the error output is more than one line? i'm not sure if
it's best to discard all but the first line in that case, or remove the
prefix from all lines. you could do the latter and join the lines with
semicolons, i suppose. might be worth discussing with gustavo, as he has
had definite opinions on this subject in the past.
- 13. By Frank Mueller
-
golxc: improved error handling after review
Roger Peppe (rogpeppe) wrote : | # |
LGTM with one suggestion below.
i'm worried i might have led you up the garden path with this one though
- please check with niemeyer that this logic is acceptable.
thanks for bearing with me!
https:/
File golxc.go (right):
https:/
golxc.go:326: if len(output) == 0 {
i think this might be better as something like:
e := &Error{name, err, nil}
for _, l := range strings.
if l != "" {
}
}
return e
i think that's equivalent, except it also removes all blank lines, which
could be considered a good thing.
if you wanted, this would also be a good place to strip the command
prefix from all the lines.
Preview Diff
1 | === added file 'export_test.go' |
2 | --- export_test.go 1970-01-01 00:00:00 +0000 |
3 | +++ export_test.go 2012-11-23 13:49:20 +0000 |
4 | @@ -0,0 +1,6 @@ |
5 | +package golxc |
6 | + |
7 | +// ContainerHome returns the name of the container directory. |
8 | +func ContainerHome(c *Container) string { |
9 | + return c.containerHome() |
10 | +} |
11 | \ No newline at end of file |
12 | |
13 | === modified file 'golxc.go' |
14 | --- golxc.go 2012-11-16 12:34:50 +0000 |
15 | +++ golxc.go 2012-11-23 13:49:20 +0000 |
16 | @@ -1,158 +1,119 @@ |
17 | // |
18 | -// goamz - Go package to interact with Linux Containers (LXC). |
19 | +// golxc - Go package to interact with Linux Containers (LXC). |
20 | // |
21 | -// https://wiki.ubuntu.com/golxc |
22 | +// https://launchpad.net/golxc/ |
23 | // |
24 | // Copyright (c) 2012 Canonical Ltd. |
25 | // |
26 | -// Written by Aram Hăvărneanu <aram.havarneanu@canonical.com> |
27 | -// and Frank Mueller <frank.mueller@canonical.com> |
28 | -// |
29 | |
30 | package golxc |
31 | |
32 | import ( |
33 | - "bytes" |
34 | "fmt" |
35 | - "log" |
36 | "os" |
37 | "os/exec" |
38 | "strconv" |
39 | "strings" |
40 | ) |
41 | |
42 | -const ( |
43 | - // ContainerStopped represents a stopped container. |
44 | - ContainerStopped = 1 << iota |
45 | - |
46 | - // ContainerStarting represents a starting container. |
47 | - ContainerStarting = 1 << iota |
48 | - |
49 | - // ContainerRunning represents a running container. |
50 | - ContainerRunning = 1 << iota |
51 | - |
52 | - // ContainerAborting represents a starting container after |
53 | - // an error occured. |
54 | - ContainerAborting = 1 << iota |
55 | - |
56 | - // ContainerStopping represents a stopping container. |
57 | - ContainerStopping = 1 << iota |
58 | -) |
59 | - |
60 | -// state2go maps lxc state strings to the according go constants. |
61 | -var state2go = map[string]int{ |
62 | - "STOPPED": ContainerStopped, |
63 | - "STARTING": ContainerStarting, |
64 | - "RUNNING": ContainerRunning, |
65 | - "ABORTING": ContainerAborting, |
66 | - "STOPPING": ContainerStopping, |
67 | -} |
68 | - |
69 | -// go2state maps go state constants to the according lxc strings. |
70 | -var go2state = map[int]string{ |
71 | - ContainerStopped: "STOPPED", |
72 | - ContainerStarting: "STARTING", |
73 | - ContainerRunning: "RUNNING", |
74 | - ContainerAborting: "ABORTING", |
75 | - ContainerStopping: "STOPPING", |
76 | -} |
77 | - |
78 | -const ( |
79 | - LogDebug = iota |
80 | - LogInfo |
81 | - LogNotice |
82 | - LogWarning |
83 | - LogError |
84 | - LogCritical |
85 | - LogFatal |
86 | -) |
87 | - |
88 | -// log2go maps the lxc log strings to the according go constants. |
89 | -var log2go = map[string]int{ |
90 | - "DEBUG": LogDebug, |
91 | - "INFO": LogInfo, |
92 | - "NOTICE": LogNotice, |
93 | - "WARN": LogWarning, |
94 | - "ERROR": LogError, |
95 | - "CRIT": LogCritical, |
96 | - "FATAL": LogFatal, |
97 | -} |
98 | - |
99 | -// go2log maps the go log constants to the according lxc strings. |
100 | -var go2log = map[int]string{ |
101 | - LogDebug: "DEBUG", |
102 | - LogInfo: "INFO", |
103 | - LogNotice: "NOTICE", |
104 | - LogWarning: "WARN", |
105 | - LogError: "ERROR", |
106 | - LogCritical: "CRIT", |
107 | - LogFatal: "FATAL", |
108 | -} |
109 | +// Error reports the failure of a LXC command. |
110 | +type Error struct { |
111 | + Name string |
112 | + Err error |
113 | + Output []string |
114 | +} |
115 | + |
116 | +func (e Error) Error() string { |
117 | + if e.Output == nil { |
118 | + return fmt.Sprintf("error executing %q: %v", e.Name, e.Err) |
119 | + } |
120 | + if len(e.Output) == 1 { |
121 | + var info string |
122 | + if strings.HasPrefix(e.Output[0], e.Name+": ") { |
123 | + info = e.Output[0][len(e.Name)+2:] |
124 | + } else { |
125 | + info = e.Output[0] |
126 | + } |
127 | + return fmt.Sprintf("error executing %q: %v", e.Name, info) |
128 | + } |
129 | + return fmt.Sprintf("error executing %q: %s", e.Name, strings.Join(e.Output, "; ")) |
130 | +} |
131 | + |
132 | +// State represents a container state. |
133 | +type State string |
134 | + |
135 | +const ( |
136 | + StateUnknown State = "UNKNOWN" |
137 | + StateStopped State = "STOPPED" |
138 | + StateStarting State = "STARTING" |
139 | + StateRunning State = "RUNNING" |
140 | + StateAborting State = "ABORTING" |
141 | + StateStopping State = "STOPPING" |
142 | +) |
143 | + |
144 | +// LogLevel represents a container's log level. |
145 | +type LogLevel string |
146 | + |
147 | +const ( |
148 | + LogDebug LogLevel = "DEBUG" |
149 | + LogInfo LogLevel = "INFO" |
150 | + LogNotice LogLevel = "NOTICE" |
151 | + LogWarning LogLevel = "WARN" |
152 | + LogError LogLevel = "ERROR" |
153 | + LogCritical LogLevel = "CRIT" |
154 | + LogFatal LogLevel = "FATAL" |
155 | +) |
156 | |
157 | // Container represents a linux container instance and provides |
158 | // operations to create, maintain and destroy the container. |
159 | type Container struct { |
160 | name string |
161 | - logFile string |
162 | - logLevel string |
163 | + LogFile string |
164 | + LogLevel LogLevel |
165 | } |
166 | |
167 | // New returns a container instance which can then be used for operations |
168 | // like Create(), Start(), Stop() or Destroy(). |
169 | func New(name string) *Container { |
170 | - c := &Container{ |
171 | + return &Container{ |
172 | name: name, |
173 | } |
174 | - // Debugging infos. |
175 | - state, pid, err := c.Info() |
176 | - if err != nil { |
177 | - log.Fatalf("cannot retrieve container info for %q: %v", c.name, err) |
178 | - return nil |
179 | - } |
180 | - log.Printf("container %q has state %q and pid %d", c.name, go2state[state], pid) |
181 | - return c |
182 | } |
183 | |
184 | // List lists the existing containers on the system. |
185 | func List() ([]*Container, error) { |
186 | - args := []string{"-1"} |
187 | - cmd := exec.Command("lxc-ls", args...) |
188 | - outbuf, _, err := run(cmd) |
189 | + stdout, err := run("lxc-ls", "-1") |
190 | if err != nil { |
191 | - log.Fatalf("cannot list containers: %v", err) |
192 | return nil, err |
193 | } |
194 | - names := nameSet(outbuf) |
195 | - containers := []*Container{} |
196 | - for _, name := range names { |
197 | - containers = append(containers, New(name)) |
198 | + names := nameSet(stdout) |
199 | + containers := make([]*Container, len(names)) |
200 | + for i, name := range names { |
201 | + containers[i] = New(name) |
202 | } |
203 | return containers, nil |
204 | } |
205 | |
206 | -// SetLog configures the logging for the lxc commands. |
207 | -func (c *Container) SetLog(logFile string, logLevel int) { |
208 | - c.logFile = logFile |
209 | - c.logLevel = go2log[logLevel] |
210 | +// Name returns the name of the container. |
211 | +func (c *Container) Name() string { |
212 | + return c.name |
213 | } |
214 | |
215 | // Create creates a new container based on the given template. |
216 | func (c *Container) Create(template string, tmplArgs ...string) error { |
217 | if c.IsConstructed() { |
218 | - return nil |
219 | + return fmt.Errorf("container %q is already created", c.Name()) |
220 | } |
221 | args := []string{ |
222 | "-n", c.name, |
223 | "-t", template, |
224 | + "--", |
225 | } |
226 | if len(tmplArgs) != 0 { |
227 | - // Arguments after -- will be passed to the template. |
228 | - args = append(append(args, "--"), tmplArgs...) |
229 | + args = append(args, tmplArgs...) |
230 | } |
231 | - cmd := exec.Command("lxc-create", args...) |
232 | - _, _, err := run(cmd) |
233 | + _, err := run("lxc-create", args...) |
234 | if err != nil { |
235 | - log.Fatalf("cannot create container %q: %v", c.name, err) |
236 | return err |
237 | } |
238 | return nil |
239 | @@ -160,6 +121,9 @@ |
240 | |
241 | // Start runs the container as a daemon. |
242 | func (c *Container) Start(configFile, consoleFile string) error { |
243 | + if !c.IsConstructed() { |
244 | + return fmt.Errorf("container %q is not yet created", c.name) |
245 | + } |
246 | args := []string{ |
247 | "--daemon", |
248 | "-n", c.name, |
249 | @@ -170,33 +134,32 @@ |
250 | if consoleFile != "" { |
251 | args = append(args, "-c", consoleFile) |
252 | } |
253 | - if c.logFile != "" { |
254 | - args = append(args, "-o", c.logFile, "-l", c.logLevel) |
255 | + if c.LogFile != "" { |
256 | + args = append(args, "-o", c.LogFile, "-l", string(c.LogLevel)) |
257 | } |
258 | - cmd := exec.Command("lxc-start", args...) |
259 | - _, _, err := run(cmd) |
260 | + _, err := run("lxc-start", args...) |
261 | if err != nil { |
262 | - log.Fatalf("cannot start container %q: %v", c.name, err) |
263 | return err |
264 | } |
265 | - return c.wait(ContainerRunning) |
266 | + return c.Wait(StateRunning) |
267 | } |
268 | |
269 | // Stop terminates the running container. |
270 | func (c *Container) Stop() error { |
271 | + if !c.IsConstructed() { |
272 | + return fmt.Errorf("container %q is not yet created", c.name) |
273 | + } |
274 | args := []string{ |
275 | "-n", c.name, |
276 | } |
277 | - if c.logFile != "" { |
278 | - args = append(args, "-o", c.logFile, "-l", c.logLevel) |
279 | + if c.LogFile != "" { |
280 | + args = append(args, "-o", c.LogFile, "-l", string(c.LogLevel)) |
281 | } |
282 | - cmd := exec.Command("lxc-stop", args...) |
283 | - _, _, err := run(cmd) |
284 | + _, err := run("lxc-stop", args...) |
285 | if err != nil { |
286 | - log.Fatalf("cannot stop container %q: %v", c.name, err) |
287 | return err |
288 | } |
289 | - return c.wait(ContainerStopped) |
290 | + return c.Wait(StateStopped) |
291 | } |
292 | |
293 | // Clone creates a copy of the container, it gets the given name. |
294 | @@ -214,50 +177,50 @@ |
295 | "-o", c.name, |
296 | "-n", name, |
297 | } |
298 | - cmd := exec.Command("lxc-clone", args...) |
299 | - _, _, err := run(cmd) |
300 | + _, err := run("lxc-clone", args...) |
301 | if err != nil { |
302 | - log.Fatalf("cannot clone container %q: %v", c.name, err) |
303 | return nil, err |
304 | } |
305 | return cc, nil |
306 | } |
307 | |
308 | -// Freeze freezes all the container'ss processes. |
309 | +// Freeze freezes all the container's processes. |
310 | func (c *Container) Freeze() error { |
311 | if !c.IsConstructed() { |
312 | return fmt.Errorf("container %q is not yet created", c.name) |
313 | } |
314 | + if !c.IsRunning() { |
315 | + return fmt.Errorf("container %q is not running", c.name) |
316 | + } |
317 | args := []string{ |
318 | "-n", c.name, |
319 | } |
320 | - if c.logFile != "" { |
321 | - args = append(args, "-o", c.logFile, "-l", c.logLevel) |
322 | + if c.LogFile != "" { |
323 | + args = append(args, "-o", c.LogFile, "-l", string(c.LogLevel)) |
324 | } |
325 | - cmd := exec.Command("lxc-freeze", args...) |
326 | - _, _, err := run(cmd) |
327 | + _, err := run("lxc-freeze", args...) |
328 | if err != nil { |
329 | - log.Fatalf("cannot freeze container %q: %v", c.name, err) |
330 | return err |
331 | } |
332 | return nil |
333 | } |
334 | |
335 | -// Unfreeze thaws all a froozen container's processes. |
336 | +// Unfreeze thaws all frozen container's processes. |
337 | func (c *Container) Unfreeze() error { |
338 | if !c.IsConstructed() { |
339 | return fmt.Errorf("container %q is not yet created", c.name) |
340 | } |
341 | + if c.IsRunning() { |
342 | + return fmt.Errorf("container %q is not frozen", c.name) |
343 | + } |
344 | args := []string{ |
345 | "-n", c.name, |
346 | } |
347 | - if c.logFile != "" { |
348 | - args = append(args, "-o", c.logFile, "-l", c.logLevel) |
349 | + if c.LogFile != "" { |
350 | + args = append(args, "-o", c.LogFile, "-l", string(c.LogLevel)) |
351 | } |
352 | - cmd := exec.Command("lxc-unfreeze", args...) |
353 | - _, _, err := run(cmd) |
354 | + _, err := run("lxc-unfreeze", args...) |
355 | if err != nil { |
356 | - log.Fatalf("cannot unfreeze container %q: %v", c.name, err) |
357 | return err |
358 | } |
359 | return nil |
360 | @@ -271,34 +234,41 @@ |
361 | if err := c.Stop(); err != nil { |
362 | return err |
363 | } |
364 | - args := []string{ |
365 | - "-n", c.name, |
366 | - } |
367 | - cmd := exec.Command("lxc-destroy", args...) |
368 | - _, _, err := run(cmd) |
369 | - if err != nil { |
370 | - log.Fatalf("cannot destroy container %q: %v", c.name, err) |
371 | + _, err := run("lxc-destroy", "-n", c.name) |
372 | + if err != nil { |
373 | + return err |
374 | + } |
375 | + return nil |
376 | +} |
377 | + |
378 | +// Wait waits for one of the specified container states. |
379 | +func (c *Container) Wait(states ...State) error { |
380 | + if len(states) == 0 { |
381 | + return fmt.Errorf("no states specified") |
382 | + } |
383 | + stateStrs := make([]string, len(states)) |
384 | + for i, state := range states { |
385 | + stateStrs[i] = string(state) |
386 | + } |
387 | + waitStates := strings.Join(stateStrs, "|") |
388 | + _, err := run("lxc-wait", "-n", c.name, "-s", waitStates) |
389 | + if err != nil { |
390 | return err |
391 | } |
392 | return nil |
393 | } |
394 | |
395 | // Info returns the status and the process id of the container. |
396 | -func (c *Container) Info() (int, int, error) { |
397 | - args := []string{ |
398 | - "-n", c.name, |
399 | - } |
400 | - cmd := exec.Command("lxc-info", args...) |
401 | - outbuf, _, err := run(cmd) |
402 | +func (c *Container) Info() (State, int, error) { |
403 | + stdout, err := run("lxc-info", "-n", c.name) |
404 | if err != nil { |
405 | - log.Fatalf("cannot retrieve container info for %q: %v", c.name, err) |
406 | - return -1, -1, err |
407 | + return StateUnknown, -1, err |
408 | } |
409 | - kv := keyValues(outbuf, ": ") |
410 | - state := state2go[kv["state"]] |
411 | + kv := keyValues(stdout, ": ") |
412 | + state := State(kv["state"]) |
413 | pid, err := strconv.Atoi(kv["pid"]) |
414 | if err != nil { |
415 | - return -1, -1, err |
416 | + return StateUnknown, -1, fmt.Errorf("cannot read the pid: %v", err) |
417 | } |
418 | return state, pid, nil |
419 | } |
420 | @@ -318,7 +288,16 @@ |
421 | if err != nil { |
422 | return false |
423 | } |
424 | - return state == ContainerRunning |
425 | + return state == StateRunning |
426 | +} |
427 | + |
428 | +// String returns information about the container. |
429 | +func (c *Container) String() string { |
430 | + state, pid, err := c.Info() |
431 | + if err != nil { |
432 | + return fmt.Sprintf("cannot retrieve container info for %q: %v", c.name, err) |
433 | + } |
434 | + return fmt.Sprintf("container %q (%s, pid %d)", c.name, state, pid) |
435 | } |
436 | |
437 | // containerHome returns the name of the container directory. |
438 | @@ -332,54 +311,40 @@ |
439 | return c.containerHome() + "/rootfs/" |
440 | } |
441 | |
442 | -// wait waits for the passed container status. |
443 | -func (c *Container) wait(status int) error { |
444 | - lxcStatus := []string{} |
445 | - if status&ContainerStarting != 0 { |
446 | - lxcStatus = append(lxcStatus, "STARTING") |
447 | - } |
448 | - if status&ContainerRunning != 0 { |
449 | - lxcStatus = append(lxcStatus, "RUNNING") |
450 | - } |
451 | - if status&ContainerAborting != 0 { |
452 | - lxcStatus = append(lxcStatus, "ABORTING") |
453 | - } |
454 | - if status&ContainerStopping != 0 { |
455 | - lxcStatus = append(lxcStatus, "STOPPING") |
456 | - } |
457 | - if status&ContainerStopped != 0 { |
458 | - lxcStatus = append(lxcStatus, "STOPPED") |
459 | - } |
460 | - lxcStatusStr := strings.Join(lxcStatus, "|") |
461 | - args := []string{ |
462 | - "-n", c.name, |
463 | - "-s", lxcStatusStr, |
464 | - } |
465 | - cmd := exec.Command("lxc-wait", args...) |
466 | - _, _, err := run(cmd) |
467 | +// run executes the passed command and returns the output. |
468 | +func run(name string, args ...string) (string, error) { |
469 | + cmd := exec.Command(name, args...) |
470 | + output, err := cmd.CombinedOutput() |
471 | if err != nil { |
472 | - log.Fatalf("cannot wait for container %q: %v", c.name, err) |
473 | - return err |
474 | + return "", runError(name, err, output) |
475 | } |
476 | - return nil |
477 | + return string(output), nil |
478 | } |
479 | |
480 | -// run executes the passed command and returns stdout and stderr. |
481 | -func run(cmd *exec.Cmd) (outbuf, errbuf bytes.Buffer, err error) { |
482 | - cmd.Stdout = &outbuf |
483 | - cmd.Stderr = &errbuf |
484 | - err = cmd.Run() |
485 | - if err != nil { |
486 | - log.Fatalf("error executing %q: %v", strings.Join(cmd.Args, " "), err) |
487 | - return |
488 | - } |
489 | - return |
490 | +// runError creates an error if run fails. |
491 | +func runError(name string, err error, output []byte) error { |
492 | + if len(output) == 0 { |
493 | + return &Error{name, err, nil} |
494 | + } |
495 | + // Remove leading and/or trailing blank lines some |
496 | + // of the LXC outputs contain. |
497 | + lines := strings.Split(string(output), "\n") |
498 | + if len(lines) > 0 && lines[0] == "" { |
499 | + lines = lines[1:] |
500 | + } |
501 | + if len(lines) > 0 && lines[len(lines)-1] == "" { |
502 | + lines = lines[:len(lines)-1] |
503 | + } |
504 | + if len(lines) == 0 { |
505 | + return &Error{name, err, nil} |
506 | + } |
507 | + return &Error{name, err, lines} |
508 | } |
509 | |
510 | // keyValues retrieves key/value pairs out of a command output. |
511 | -func keyValues(raw bytes.Buffer, sep string) map[string]string { |
512 | +func keyValues(raw string, sep string) map[string]string { |
513 | kv := map[string]string{} |
514 | - lines := strings.Split(raw.String(), "\n") |
515 | + lines := strings.Split(raw, "\n") |
516 | for _, line := range lines { |
517 | parts := strings.SplitN(line, sep, 2) |
518 | if len(parts) == 2 { |
519 | @@ -390,10 +355,10 @@ |
520 | } |
521 | |
522 | // nameSet retrieves a set of names out of a command output. |
523 | -func nameSet(raw bytes.Buffer) []string { |
524 | +func nameSet(raw string) []string { |
525 | collector := map[string]struct{}{} |
526 | set := []string{} |
527 | - lines := strings.Split(raw.String(), "\n") |
528 | + lines := strings.Split(raw, "\n") |
529 | for _, line := range lines { |
530 | name := strings.TrimSpace(line) |
531 | if name != "" { |
532 | |
533 | === added file 'golxc_test.go' |
534 | --- golxc_test.go 1970-01-01 00:00:00 +0000 |
535 | +++ golxc_test.go 2012-11-23 13:49:20 +0000 |
536 | @@ -0,0 +1,247 @@ |
537 | +package golxc_test |
538 | + |
539 | +import ( |
540 | + . "launchpad.net/gocheck" |
541 | + "launchpad.net/golxc" |
542 | + "os" |
543 | + "os/user" |
544 | + "testing" |
545 | +) |
546 | + |
547 | +func Test(t *testing.T) { TestingT(t) } |
548 | + |
549 | +type LXCSuite struct{} |
550 | + |
551 | +var _ = Suite(&LXCSuite{}) |
552 | + |
553 | +func (s *LXCSuite) SetUpSuite(c *C) { |
554 | + u, err := user.Current() |
555 | + c.Assert(err, IsNil) |
556 | + if u.Uid != "0" { |
557 | + // Has to be run as root! |
558 | + c.Skip("tests must run as root") |
559 | + } |
560 | +} |
561 | + |
562 | +func (s *LXCSuite) TestCreateDestroy(c *C) { |
563 | + // Test clean creation and destroying of a container. |
564 | + lc := golxc.New("golxc") |
565 | + c.Assert(lc.IsConstructed(), Equals, false) |
566 | + home := golxc.ContainerHome(lc) |
567 | + _, err := os.Stat(home) |
568 | + c.Assert(err, ErrorMatches, "stat .*: no such file or directory") |
569 | + err = lc.Create("ubuntu") |
570 | + c.Assert(err, IsNil) |
571 | + c.Assert(lc.IsConstructed(), Equals, true) |
572 | + defer func() { |
573 | + err = lc.Destroy() |
574 | + c.Assert(err, IsNil) |
575 | + _, err = os.Stat(home) |
576 | + c.Assert(err, ErrorMatches, "stat .*: no such file or directory") |
577 | + }() |
578 | + fi, err := os.Stat(golxc.ContainerHome(lc)) |
579 | + c.Assert(err, IsNil) |
580 | + c.Assert(fi.IsDir(), Equals, true) |
581 | +} |
582 | + |
583 | +func (s *LXCSuite) TestCreateTwice(c *C) { |
584 | + // Test that a container cannot be created twice. |
585 | + lc1 := golxc.New("golxc") |
586 | + c.Assert(lc1.IsConstructed(), Equals, false) |
587 | + err := lc1.Create("ubuntu") |
588 | + c.Assert(err, IsNil) |
589 | + c.Assert(lc1.IsConstructed(), Equals, true) |
590 | + defer func() { |
591 | + c.Assert(lc1.Destroy(), IsNil) |
592 | + }() |
593 | + lc2 := golxc.New("golxc") |
594 | + err = lc2.Create("ubuntu") |
595 | + c.Assert(err, ErrorMatches, "container .* is already created") |
596 | +} |
597 | + |
598 | +func (s *LXCSuite) TestCreateIllegalTemplate(c *C) { |
599 | + // Test that a container creation fails correctly in |
600 | + // case of an illegal template. |
601 | + lc := golxc.New("golxc") |
602 | + c.Assert(lc.IsConstructed(), Equals, false) |
603 | + err := lc.Create("name-of-a-not-existing-template-for-golxc") |
604 | + c.Assert(err, ErrorMatches, `error executing "lxc-create": No config file specified, .*`) |
605 | + c.Assert(lc.IsConstructed(), Equals, false) |
606 | +} |
607 | + |
608 | +func (l *LXCSuite) TestDestroyNotCreated(c *C) { |
609 | + // Test that a non-existing container can't be destroyed. |
610 | + lc := golxc.New("golxc") |
611 | + c.Assert(lc.IsConstructed(), Equals, false) |
612 | + err := lc.Destroy() |
613 | + c.Assert(err, ErrorMatches, "container .* is not yet created") |
614 | +} |
615 | + |
616 | +func contains(lcs []*golxc.Container, lc *golxc.Container) bool { |
617 | + for _, clc := range lcs { |
618 | + if clc.Name() == lc.Name() { |
619 | + return true |
620 | + } |
621 | + } |
622 | + return false |
623 | +} |
624 | + |
625 | +func (s *LXCSuite) TestList(c *C) { |
626 | + // Test the listing of created containers. |
627 | + lcs, err := golxc.List() |
628 | + oldLen := len(lcs) |
629 | + c.Assert(err, IsNil) |
630 | + c.Assert(oldLen >= 0, Equals, true) |
631 | + lc := golxc.New("golxc") |
632 | + c.Assert(lc.IsConstructed(), Equals, false) |
633 | + c.Assert(lc.Create("ubuntu"), IsNil) |
634 | + c.Assert(lc.IsConstructed(), Equals, true) |
635 | + defer func() { |
636 | + c.Assert(lc.Destroy(), IsNil) |
637 | + }() |
638 | + lcs, _ = golxc.List() |
639 | + newLen := len(lcs) |
640 | + c.Assert(newLen == oldLen+1, Equals, true) |
641 | + c.Assert(contains(lcs, lc), Equals, true) |
642 | +} |
643 | + |
644 | +func (s *LXCSuite) TestClone(c *C) { |
645 | + // Test the cloning of an existing container. |
646 | + lc1 := golxc.New("golxc") |
647 | + c.Assert(lc1.IsConstructed(), Equals, false) |
648 | + c.Assert(lc1.Create("ubuntu"), IsNil) |
649 | + c.Assert(lc1.IsConstructed(), Equals, true) |
650 | + defer func() { |
651 | + c.Assert(lc1.Destroy(), IsNil) |
652 | + }() |
653 | + lcs, _ := golxc.List() |
654 | + oldLen := len(lcs) |
655 | + lc2, err := lc1.Clone("golxcclone") |
656 | + c.Assert(err, IsNil) |
657 | + c.Assert(lc2.IsConstructed(), Equals, true) |
658 | + defer func() { |
659 | + c.Assert(lc2.Destroy(), IsNil) |
660 | + }() |
661 | + lcs, _ = golxc.List() |
662 | + newLen := len(lcs) |
663 | + c.Assert(newLen == oldLen+1, Equals, true) |
664 | + c.Assert(contains(lcs, lc1), Equals, true) |
665 | + c.Assert(contains(lcs, lc2), Equals, true) |
666 | +} |
667 | + |
668 | +func (s *LXCSuite) TestCloneNotCreated(c *C) { |
669 | + // Test the cloning of a non-existing container. |
670 | + lc := golxc.New("golxc") |
671 | + c.Assert(lc.IsConstructed(), Equals, false) |
672 | + _, err := lc.Clone("golxcclone") |
673 | + c.Assert(err, ErrorMatches, "container .* is not yet created") |
674 | +} |
675 | + |
676 | +func (s *LXCSuite) TestStartStop(c *C) { |
677 | + // Test starting and stopping a container. |
678 | + lc := golxc.New("golxc") |
679 | + c.Assert(lc.IsConstructed(), Equals, false) |
680 | + c.Assert(lc.Create("ubuntu"), IsNil) |
681 | + defer func() { |
682 | + c.Assert(lc.Destroy(), IsNil) |
683 | + }() |
684 | + c.Assert(lc.Start("", ""), IsNil) |
685 | + c.Assert(lc.IsRunning(), Equals, true) |
686 | + c.Assert(lc.Stop(), IsNil) |
687 | + c.Assert(lc.IsRunning(), Equals, false) |
688 | +} |
689 | + |
690 | +func (l *LXCSuite) TestStartNotCreated(c *C) { |
691 | + // Test that a non-existing container can't be started. |
692 | + lc := golxc.New("golxc") |
693 | + c.Assert(lc.IsConstructed(), Equals, false) |
694 | + c.Assert(lc.Start("", ""), ErrorMatches, "container .* is not yet created") |
695 | +} |
696 | + |
697 | +func (l *LXCSuite) TestStopNotRunning(c *C) { |
698 | + // Test that a not running container can't be stopped. |
699 | + lc := golxc.New("golxc") |
700 | + c.Assert(lc.IsConstructed(), Equals, false) |
701 | + c.Assert(lc.Create("ubuntu"), IsNil) |
702 | + defer func() { |
703 | + c.Assert(lc.Destroy(), IsNil) |
704 | + }() |
705 | + c.Assert(lc.Stop(), IsNil) |
706 | +} |
707 | + |
708 | +func (s *LXCSuite) TestWait(c *C) { |
709 | + // Test waiting for one of a number of states of a container. |
710 | + // ATTN: Using a not reached state blocks the test until timeout! |
711 | + lc := golxc.New("golxc") |
712 | + c.Assert(lc.IsConstructed(), Equals, false) |
713 | + c.Assert(lc.Wait(), ErrorMatches, "no states specified") |
714 | + c.Assert(lc.Wait(golxc.StateStopped), IsNil) |
715 | + c.Assert(lc.Wait(golxc.StateStopped, golxc.StateRunning), IsNil) |
716 | + c.Assert(lc.Create("ubuntu"), IsNil) |
717 | + defer func() { |
718 | + c.Assert(lc.Destroy(), IsNil) |
719 | + }() |
720 | + go func() { |
721 | + c.Assert(lc.Start("", ""), IsNil) |
722 | + }() |
723 | + c.Assert(lc.Wait(golxc.StateRunning), IsNil) |
724 | +} |
725 | + |
726 | +func (l *LXCSuite) TestFreezeUnfreeze(c *C) { |
727 | + // Test the freezing and unfreezing of a started container. |
728 | + lc := golxc.New("golxc") |
729 | + c.Assert(lc.IsConstructed(), Equals, false) |
730 | + c.Assert(lc.Create("ubuntu"), IsNil) |
731 | + defer func() { |
732 | + c.Assert(lc.Destroy(), IsNil) |
733 | + }() |
734 | + c.Assert(lc.Start("", ""), IsNil) |
735 | + defer func() { |
736 | + c.Assert(lc.Stop(), IsNil) |
737 | + }() |
738 | + c.Assert(lc.IsRunning(), Equals, true) |
739 | + c.Assert(lc.Freeze(), IsNil) |
740 | + c.Assert(lc.IsRunning(), Equals, false) |
741 | + c.Assert(lc.Unfreeze(), IsNil) |
742 | + c.Assert(lc.IsRunning(), Equals, true) |
743 | +} |
744 | + |
745 | +func (l *LXCSuite) TestFreezeNotStarted(c *C) { |
746 | + // Test that a not running container can't be frozen. |
747 | + lc := golxc.New("golxc") |
748 | + c.Assert(lc.IsConstructed(), Equals, false) |
749 | + c.Assert(lc.Create("ubuntu"), IsNil) |
750 | + defer func() { |
751 | + c.Assert(lc.Destroy(), IsNil) |
752 | + }() |
753 | + c.Assert(lc.Freeze(), ErrorMatches, "container .* is not running") |
754 | +} |
755 | + |
756 | +func (l *LXCSuite) TestFreezeNotCreated(c *C) { |
757 | + // Test that a non-existing container can't be frozen. |
758 | + lc := golxc.New("golxc") |
759 | + c.Assert(lc.IsConstructed(), Equals, false) |
760 | + c.Assert(lc.Freeze(), ErrorMatches, "container .* is not yet created") |
761 | +} |
762 | + |
763 | +func (l *LXCSuite) TestUnfreezeNotCreated(c *C) { |
764 | + // Test that a non-existing container can't be unfrozen. |
765 | + lc := golxc.New("golxc") |
766 | + c.Assert(lc.IsConstructed(), Equals, false) |
767 | + c.Assert(lc.Unfreeze(), ErrorMatches, "container .* is not yet created") |
768 | +} |
769 | + |
770 | +func (l *LXCSuite) TestUnfreezeNotFrozen(c *C) { |
771 | + // Test that a running container can't be unfrozen. |
772 | + lc := golxc.New("golxc") |
773 | + c.Assert(lc.IsConstructed(), Equals, false) |
774 | + c.Assert(lc.Create("ubuntu"), IsNil) |
775 | + defer func() { |
776 | + c.Assert(lc.Destroy(), IsNil) |
777 | + }() |
778 | + c.Assert(lc.Start("", ""), IsNil) |
779 | + defer func() { |
780 | + c.Assert(lc.Stop(), IsNil) |
781 | + }() |
782 | + c.Assert(lc.Unfreeze(), ErrorMatches, "container .* is not frozen") |
783 | +} |
784 | |
785 | === added file 'golxc_test.sh' |
786 | --- golxc_test.sh 1970-01-01 00:00:00 +0000 |
787 | +++ golxc_test.sh 2012-11-23 13:49:20 +0000 |
788 | @@ -0,0 +1,8 @@ |
789 | +#!/bin/sh |
790 | +sudo sh -c " |
791 | + export GOMAXPROCS=\"$GOMAXPROCS\" |
792 | + export GOPATH=\"$GOPATH\" |
793 | + export GOROOT=\"$GOROOT\" |
794 | + export PATH=\"$PATH\" |
795 | + go test $* |
796 | +" |
797 | \ No newline at end of file |
lots of comments but nothing major.
i'm not familiar with lxc, so all superficial, i'm afraid.
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go
File golxc.go (right):
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode9
golxc.go:9: // Frank Mueller <email address hidden>
do we need individual attributions?
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode29
golxc.go:29: ContainerStarting = 1 << iota
you can omit the "1 << iota" from here and the following constants.
however, ISTM that there's no particular reason for these to be bit
masks. the only place that we make use of their bitmask nature is in the
unexported Container.wait, and it would be just as easy (actually
easier, i think) to create a bitmask explicitly there.
also, how about:
type State uint
and using that as the state type, perhaps renaming the constants to
StateRunning, etc.
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode52
golxc.go:52: var go2state = map[int]string{
if the states weren't bitmasks, this could just be []string and you'd
get the same result.
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode82
golxc.go:82: var go2log = map[int]string{
[]string
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode111
golxc.go:111: outbuf, _, err := run("lxc-ls", args...)
run("lxc-ls", "-1")
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode113
golxc.go:113: log.Fatalf("cannot list containers: %v", err)
d
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode117
golxc.go:117: containers := []*Container{}
containers := make([]*Container, len(names)) ?
and then containers[i] = New(name) below.
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode142
golxc.go:142: "-t", template,
you could add "--" here unconditionally.
then the below lines could be simply:
args = append(args, tmplArgs)
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode150
golxc.go:150: log.Fatalf("cannot create container %q: %v", c.name, err)
d
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode173
golxc.go:173: log.Fatalf("cannot start container %q: %v", c.name, err)
d
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode189
golxc.go:189: log.Fatalf("cannot stop container %q: %v", c.name, err)
d
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode212
golxc.go:212: log.Fatalf("cannot clone container %q: %v", c.name, err)
d
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode231
golxc.go:231: log.Fatalf("cannot freeze container %q: %v", c.name, err)
d
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode250
golxc.go:250: log.Fatalf("cannot unfreeze container %q: %v", c.name,
err)
d
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode269
golxc.go:269: log.Fatalf("cannot destroy container %q: %v", c.name, err)
d
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode282
golxc.go:282: log.Fatalf("cannot retrieve container info for %q: %v",
c.name, err)
d
https:/ /codereview. appspot. com/6852065/ diff/6001/ golxc.go# newcode289
golxc.go:289: return -1, -1, err
a m...