Merge lp:~themue/golxc/001-added-sudo-and-first-test into lp:golxc

Proposed by Frank Mueller
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
Reviewer Review Type Date Requested Status
Frank Mueller Pending
Review via email: mp+134956@code.launchpad.net

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.

https://codereview.appspot.com/6852065/

To post a comment you must log in.
6. By Frank Mueller

golxc: added test for running container

7. By Frank Mueller

golxc: added more tests

Revision history for this message
Roger Peppe (rogpeppe) wrote :
Download full text (4.5 KiB)

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...

Read more...

8. By Frank Mueller

golxc: simplification after review

Revision history for this message
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://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>
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://codereview.appspot.com/6852065/diff/6001/golxc.go#newcode370
golxc.go:370: log.Fatalf("error executing %q: %v",
strings.Join(cmd.Args, " "), err)
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://codereview.appspot.com/6852065/diff/11001/golxc.go
File golxc.go (right):

https://codereview.appspot.com/6852065/diff/11001/golxc.go#newcode35
golxc.go:35: // LogLevel represents a containers log level.
s/containers/container's/

https://codereview.appspot.com/6852065/diff/11001/golxc.go#newcode86
golxc.go:86: c.logLevel = logLevel
is it really worth having the setter method here?
could we just export the relevant fields?

https://codereview.appspot.com/6852065/diff/11001/golxc.go#newcode273
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.

https://codereview.appspot.com/6852065/

9. By Frank Mueller

golxc: more changes after review

Revision history for this message
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://codereview.appspot.com/6852065/diff/7007/golxc.go
File golxc.go (right):

https://codereview.appspot.com/6852065/diff/7007/golxc.go#newcode217
golxc.go:217: _, _, err := run("lxc-wait", "-n", c.name, "-s",
combinedStates)
I guess LXC will complain about impossible state combinations?

https://codereview.appspot.com/6852065/diff/7007/golxc.go#newcode257
golxc.go:257: // String returns a short information about the container.
s/a short/brief/

perhaps?

https://codereview.appspot.com/6852065/diff/7007/golxc_test.go
File golxc_test.go (right):

https://codereview.appspot.com/6852065/diff/7007/golxc_test.go#newcode20
golxc_test.go:20: lc.Destroy()
Shouldn't these be destroyed inline via defer?

https://codereview.appspot.com/6852065/

10. By Frank Mueller

golxc: hardened package and added more tests after review

Revision history for this message
William Reade (fwereade) wrote :
Download full text (3.2 KiB)

A few more comments...

https://codereview.appspot.com/6852065/diff/6002/golxc.go
File golxc.go (right):

https://codereview.appspot.com/6852065/diff/6002/golxc.go#newcode180
golxc.go:180: // Unfreeze thaws all a froozen container's processes.
s/rooz/roz/

https://codereview.appspot.com/6852065/diff/6002/golxc.go#newcode186
golxc.go:186: return fmt.Errorf("container %q is not froozen", c.name)
ditto

https://codereview.appspot.com/6852065/diff/6002/golxc.go#newcode219
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://codereview.appspot.com/6852065/diff/6002/golxc.go#newcode271
golxc.go:271: // String returns a brief information about the container.
s/a //

?

https://codereview.appspot.com/6852065/diff/6002/golxc_test.go
File golxc_test.go (right):

https://codereview.appspot.com/6852065/diff/6002/golxc_test.go#newcode25
golxc_test.go:25: c.Assert(lc.IsConstructed(), Equals, true)
Shouldn't we defer the Destroy at this point?

https://codereview.appspot.com/6852065/diff/6002/golxc_test.go#newcode40
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://codereview.appspot.com/6852065/diff/6002/golxc_test.go#newcode74
golxc_test.go:74: defer lc.Destroy()
Handle errors? (after checking no error on preceding Create)

https://codereview.appspot.com/6852065/diff/6002/golxc_test.go#newcode87
golxc_test.go:87: defer lc1.Destroy()
ditto

https://codereview.appspot.com/6852065/diff/6002/golxc_test.go#newcode91
golxc_test.go:91: defer lc2.Destroy()
ditto

https://codereview.appspot.com/6852065/diff/6002/golxc_test.go#newcode116
golxc_test.go:116: defer lc.Destroy()
ditto

https://codereview.appspot.com/6852065/diff/6002/golxc_test.go#newcode137
golxc_test.go:137: defer lc.Destroy()
ditto

https://codereview.appspot.com/6852065/diff/6002/golxc_test.go#newcode138
golxc_test.go:138: go lc.Start("", "")
Destroy works even if the container is running?

https://codereview.appspot.com/6852065/diff/6002/golxc_test.go#newcode161
golxc_test.go:161: // Test that a not running container can't be
froozen.
s/rooz/roz/

https://codereview.appspot.com/6852065/diff/6002/golxc_test.go#newcode171
golxc_test.go:171: // Test that a non-existing container can't be
froozen.
ditto

https://codereview.appspot.com/6852065/diff/6002/golxc_test.go#newcode179
golxc_test.go:179: // Test that a non-existing container can't be
unfroozen.
ditto

https://codereview.appspot.com/6852065/diff/6002/golxc_test.go#newcode186
golxc_test.go:186: func (l *LXCSuite) TestUnfreezeNotFroozen(c *C) {
ditto

https://codereview.appspot.com/6852065/diff/6002/golxc_test.go#newcode187
golxc_test.go:187: // Test that a running container can't be unfroozen.
ditto

https://codereview.appspot.com/6852065/diff/6002/golxc_test.go#newcode193
golxc_test.go:193: defer lc.Stop()
error checking

https://codereview.appspot.com/6852065/diff/6002/golxc_test.go#newcode195
golxc_test.go:195: c.Assert(err, ErrorMatches, "container .* is not
froozen...

Read more...

11. By Frank Mueller

golxc: better error checking after review

Revision history for this message
William Reade (fwereade) wrote :
Revision history for this message
Roger Peppe (rogpeppe) wrote :

looks good to me apart from (still) the error messages.
a few minor suggestions in addition to that.

https://codereview.appspot.com/6852065/diff/6002/golxc.go
File golxc.go (right):

https://codereview.appspot.com/6852065/diff/6002/golxc.go#newcode219
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://codereview.appspot.com/6852065/diff/6002/golxc.go#newcode271
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://codereview.appspot.com/6852065/diff/6002/golxc.go#newcode277
golxc.go:277: return fmt.Sprintf("container %q has state %q and pid %d",
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://codereview.appspot.com/6852065/diff/6002/golxc.go#newcode304
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://codereview.appspot.com/6852065/diff/9003/golxc_test.go
File golxc_test.go (right):

https://codereview.appspot.com/6852065/diff/9003/golxc_test.go#newcode12
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.

https://codereview.appspot.com/6852065/

12. By Frank Mueller

golxc: added error type and test skipping if not root

Revision history for this message
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://codereview.appspot.com/6852065/diff/5003/golxc.go
File golxc.go (right):

https://codereview.appspot.com/6852065/diff/5003/golxc.go#newcode29
golxc.go:29: if strings.HasPrefix(e.StdErr, e.Name) {
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.

https://codereview.appspot.com/6852065/

13. By Frank Mueller

golxc: improved error handling after review

Revision history for this message
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://codereview.appspot.com/6852065/diff/18/golxc.go
File golxc.go (right):

https://codereview.appspot.com/6852065/diff/18/golxc.go#newcode326
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.Split(string(output), "\n") {
         if l != "" {
             e.Output = append(e.Output, 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.

https://codereview.appspot.com/6852065/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches

to all changes: