Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(1001)

Side by Side Diff: golxc.go

Issue 6852065: golxc: add first container tests (Closed)
Patch Set: golxc: add first container tests Created 11 years, 4 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « export_test.go ('k') | golxc_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // 1 //
2 // goamz - Go package to interact with Linux Containers (LXC). 2 // golxc - Go package to interact with Linux Containers (LXC).
3 // 3 //
4 // https://wiki.ubuntu.com/golxc 4 // https://launchpad.net/golxc/
5 // 5 //
6 // Copyright (c) 2012 Canonical Ltd. 6 // Copyright (c) 2012 Canonical Ltd.
7 // 7 //
8 // Written by Aram Hăvărneanu <aram.havarneanu@canonical.com> 8 // Written by Aram Hăvărneanu <aram.havarneanu@canonical.com>
9 // and Frank Mueller <frank.mueller@canonical.com> 9 // Frank Mueller <frank.mueller@canonical.com>
10 // 10 //
11 11
12 package golxc 12 package golxc
13 13
14 import ( 14 import (
15 "bytes" 15 "bytes"
16 "fmt" 16 "fmt"
17 "log"
18 "os" 17 "os"
19 "os/exec" 18 "os/exec"
20 "strconv" 19 "strconv"
21 "strings" 20 "strings"
22 ) 21 )
23 22
23 // State represents a container state.
24 type State string
25
24 const ( 26 const (
25 » // ContainerStopped represents a stopped container. 27 » StateUnknown State = "UNKNOWN"
26 » ContainerStopped = 1 << iota 28 » StateStopped State = "STOPPED"
27 29 » StateStarting State = "STARTING"
28 » // ContainerStarting represents a starting container. 30 » StateRunning State = "RUNNING"
29 » ContainerStarting = 1 << iota 31 » StateAborting State = "ABORTING"
30 32 » StateStopping State = "STOPPING"
31 » // ContainerRunning represents a running container.
32 » ContainerRunning = 1 << iota
33
34 » // ContainerAborting represents a starting container after
35 » // an error occured.
36 » ContainerAborting = 1 << iota
37
38 » // ContainerStopping represents a stopping container.
39 » ContainerStopping = 1 << iota
40 ) 33 )
41 34
42 // state2go maps lxc state strings to the according go constants. 35 // LogLevel represents a containers log level.
rog 2012/11/20 16:49:53 s/containers/container's/
TheMue 2012/11/21 08:36:56 Done.
43 var state2go = map[string]int{ 36 type LogLevel string
44 » "STOPPED": ContainerStopped,
45 » "STARTING": ContainerStarting,
46 » "RUNNING": ContainerRunning,
47 » "ABORTING": ContainerAborting,
48 » "STOPPING": ContainerStopping,
49 }
50
51 // go2state maps go state constants to the according lxc strings.
52 var go2state = map[int]string{
53 » ContainerStopped: "STOPPED",
54 » ContainerStarting: "STARTING",
55 » ContainerRunning: "RUNNING",
56 » ContainerAborting: "ABORTING",
57 » ContainerStopping: "STOPPING",
58 }
59 37
60 const ( 38 const (
61 » LogDebug = iota 39 » LogDebug LogLevel = "DEBUG"
62 » LogInfo 40 » LogInfo LogLevel = "INFO"
63 » LogNotice 41 » LogNotice LogLevel = "NOTICE"
64 » LogWarning 42 » LogWarning LogLevel = "WARN"
65 » LogError 43 » LogError LogLevel = "ERROR"
66 » LogCritical 44 » LogCritical LogLevel = "CRIT"
67 » LogFatal 45 » LogFatal LogLevel = "FATAL"
68 ) 46 )
69 47
70 // log2go maps the lxc log strings to the according go constants.
71 var log2go = map[string]int{
72 "DEBUG": LogDebug,
73 "INFO": LogInfo,
74 "NOTICE": LogNotice,
75 "WARN": LogWarning,
76 "ERROR": LogError,
77 "CRIT": LogCritical,
78 "FATAL": LogFatal,
79 }
80
81 // go2log maps the go log constants to the according lxc strings.
82 var go2log = map[int]string{
83 LogDebug: "DEBUG",
84 LogInfo: "INFO",
85 LogNotice: "NOTICE",
86 LogWarning: "WARN",
87 LogError: "ERROR",
88 LogCritical: "CRIT",
89 LogFatal: "FATAL",
90 }
91
92 // Container represents a linux container instance and provides 48 // Container represents a linux container instance and provides
93 // operations to create, maintain and destroy the container.· 49 // operations to create, maintain and destroy the container.·
94 type Container struct { 50 type Container struct {
95 name string 51 name string
96 logFile string 52 logFile string
97 » logLevel string 53 » logLevel LogLevel
98 } 54 }
99 55
100 // New returns a container instance which can then be used for operations 56 // New returns a container instance which can then be used for operations
101 // like Create(), Start(), Stop() or Destroy(). 57 // like Create(), Start(), Stop() or Destroy().
102 func New(name string) *Container { 58 func New(name string) *Container {
103 » c := &Container{ 59 » return &Container{
104 name: name, 60 name: name,
105 } 61 }
106 // Debugging infos.
107 state, pid, err := c.Info()
108 if err != nil {
109 log.Fatalf("cannot retrieve container info for %q: %v", c.name, err)
110 return nil
111 }
112 log.Printf("container %q has state %q and pid %d", c.name, go2state[stat e], pid)
113 return c
114 } 62 }
115 63
116 // List lists the existing containers on the system. 64 // List lists the existing containers on the system.
117 func List() ([]*Container, error) { 65 func List() ([]*Container, error) {
118 » args := []string{"-1"} 66 » outbuf, _, err := run("lxc-ls", "-1")
119 » cmd := exec.Command("lxc-ls", args...)
120 » outbuf, _, err := run(cmd)
121 if err != nil { 67 if err != nil {
122 log.Fatalf("cannot list containers: %v", err)
123 return nil, err 68 return nil, err
124 } 69 }
125 names := nameSet(outbuf) 70 names := nameSet(outbuf)
126 » containers := []*Container{} 71 » containers := make([]*Container, len(names))
127 » for _, name := range names { 72 » for i, name := range names {
128 » » containers = append(containers, New(name)) 73 » » containers[i] = New(name)
129 } 74 }
130 return containers, nil 75 return containers, nil
131 } 76 }
132 77
78 // Name returns the name of the container.
79 func (c *Container) Name() string {
80 return c.name
81 }
82
133 // SetLog configures the logging for the lxc commands. 83 // SetLog configures the logging for the lxc commands.
134 func (c *Container) SetLog(logFile string, logLevel int) { 84 func (c *Container) SetLog(logFile string, logLevel LogLevel) {
135 c.logFile = logFile 85 c.logFile = logFile
136 » c.logLevel = go2log[logLevel] 86 » c.logLevel = logLevel
rog 2012/11/20 16:49:53 is it really worth having the setter method here?
TheMue 2012/11/21 08:36:56 Done.
137 } 87 }
138 88
139 // Create creates a new container based on the given template. 89 // Create creates a new container based on the given template.
140 func (c *Container) Create(template string, tmplArgs ...string) error { 90 func (c *Container) Create(template string, tmplArgs ...string) error {
141 if c.IsConstructed() { 91 if c.IsConstructed() {
142 return nil 92 return nil
143 } 93 }
144 args := []string{ 94 args := []string{
145 "-n", c.name, 95 "-n", c.name,
146 "-t", template, 96 "-t", template,
97 "--",
147 } 98 }
148 if len(tmplArgs) != 0 { 99 if len(tmplArgs) != 0 {
149 » » // Arguments after -- will be passed to the template. 100 » » args = append(args, tmplArgs...)
150 » » args = append(append(args, "--"), tmplArgs...)
151 } 101 }
152 » cmd := exec.Command("lxc-create", args...) 102 » _, _, err := run("lxc-create", args...)
153 » _, _, err := run(cmd)
154 if err != nil { 103 if err != nil {
155 log.Fatalf("cannot create container %q: %v", c.name, err)
156 return err 104 return err
157 } 105 }
158 return nil 106 return nil
159 } 107 }
160 108
161 // Start runs the container as a daemon. 109 // Start runs the container as a daemon.
162 func (c *Container) Start(configFile, consoleFile string) error { 110 func (c *Container) Start(configFile, consoleFile string) error {
163 args := []string{ 111 args := []string{
164 "--daemon", 112 "--daemon",
165 "-n", c.name, 113 "-n", c.name,
166 } 114 }
167 if configFile != "" { 115 if configFile != "" {
168 args = append(args, "-f", configFile) 116 args = append(args, "-f", configFile)
169 } 117 }
170 if consoleFile != "" { 118 if consoleFile != "" {
171 args = append(args, "-c", consoleFile) 119 args = append(args, "-c", consoleFile)
172 } 120 }
173 if c.logFile != "" { 121 if c.logFile != "" {
174 » » args = append(args, "-o", c.logFile, "-l", c.logLevel) 122 » » args = append(args, "-o", c.logFile, "-l", string(c.logLevel))
175 } 123 }
176 » cmd := exec.Command("lxc-start", args...) 124 » _, _, err := run("lxc-start", args...)
177 » _, _, err := run(cmd)
178 if err != nil { 125 if err != nil {
179 log.Fatalf("cannot start container %q: %v", c.name, err)
180 return err 126 return err
181 } 127 }
182 » return c.wait(ContainerRunning) 128 » return c.wait(StateRunning)
183 } 129 }
184 130
185 // Stop terminates the running container. 131 // Stop terminates the running container.
186 func (c *Container) Stop() error { 132 func (c *Container) Stop() error {
187 args := []string{ 133 args := []string{
188 "-n", c.name, 134 "-n", c.name,
189 } 135 }
190 if c.logFile != "" { 136 if c.logFile != "" {
191 » » args = append(args, "-o", c.logFile, "-l", c.logLevel) 137 » » args = append(args, "-o", c.logFile, "-l", string(c.logLevel))
192 } 138 }
193 » cmd := exec.Command("lxc-stop", args...) 139 » _, _, err := run("lxc-stop", args...)
194 » _, _, err := run(cmd)
195 if err != nil { 140 if err != nil {
196 log.Fatalf("cannot stop container %q: %v", c.name, err)
197 return err 141 return err
198 } 142 }
199 » return c.wait(ContainerStopped) 143 » return c.wait(StateStopped)
200 } 144 }
201 145
202 // Clone creates a copy of the container, it gets the given name. 146 // Clone creates a copy of the container, it gets the given name.
203 func (c *Container) Clone(name string) (*Container, error) { 147 func (c *Container) Clone(name string) (*Container, error) {
204 if !c.IsConstructed() { 148 if !c.IsConstructed() {
205 return nil, fmt.Errorf("container %q is not yet created", c.name ) 149 return nil, fmt.Errorf("container %q is not yet created", c.name )
206 } 150 }
207 cc := &Container{ 151 cc := &Container{
208 name: name, 152 name: name,
209 } 153 }
210 if cc.IsConstructed() { 154 if cc.IsConstructed() {
211 return cc, nil 155 return cc, nil
212 } 156 }
213 args := []string{ 157 args := []string{
214 "-o", c.name, 158 "-o", c.name,
215 "-n", name, 159 "-n", name,
216 } 160 }
217 » cmd := exec.Command("lxc-clone", args...) 161 » _, _, err := run("lxc-clone", args...)
218 » _, _, err := run(cmd)
219 if err != nil { 162 if err != nil {
220 log.Fatalf("cannot clone container %q: %v", c.name, err)
221 return nil, err 163 return nil, err
222 } 164 }
223 return cc, nil 165 return cc, nil
224 } 166 }
225 167
226 // Freeze freezes all the container'ss processes. 168 // Freeze freezes all the container'ss processes.
227 func (c *Container) Freeze() error { 169 func (c *Container) Freeze() error {
228 if !c.IsConstructed() { 170 if !c.IsConstructed() {
229 return fmt.Errorf("container %q is not yet created", c.name) 171 return fmt.Errorf("container %q is not yet created", c.name)
230 } 172 }
231 args := []string{ 173 args := []string{
232 "-n", c.name, 174 "-n", c.name,
233 } 175 }
234 if c.logFile != "" { 176 if c.logFile != "" {
235 » » args = append(args, "-o", c.logFile, "-l", c.logLevel) 177 » » args = append(args, "-o", c.logFile, "-l", string(c.logLevel))
236 } 178 }
237 » cmd := exec.Command("lxc-freeze", args...) 179 » _, _, err := run("lxc-freeze", args...)
238 » _, _, err := run(cmd)
239 if err != nil { 180 if err != nil {
240 log.Fatalf("cannot freeze container %q: %v", c.name, err)
241 return err 181 return err
242 } 182 }
243 return nil 183 return nil
244 } 184 }
245 185
246 // Unfreeze thaws all a froozen container's processes. 186 // Unfreeze thaws all a froozen container's processes.
247 func (c *Container) Unfreeze() error { 187 func (c *Container) Unfreeze() error {
248 if !c.IsConstructed() { 188 if !c.IsConstructed() {
249 return fmt.Errorf("container %q is not yet created", c.name) 189 return fmt.Errorf("container %q is not yet created", c.name)
250 } 190 }
251 args := []string{ 191 args := []string{
252 "-n", c.name, 192 "-n", c.name,
253 } 193 }
254 if c.logFile != "" { 194 if c.logFile != "" {
255 » » args = append(args, "-o", c.logFile, "-l", c.logLevel) 195 » » args = append(args, "-o", c.logFile, "-l", string(c.logLevel))
256 } 196 }
257 » cmd := exec.Command("lxc-unfreeze", args...) 197 » _, _, err := run("lxc-unfreeze", args...)
258 » _, _, err := run(cmd)
259 if err != nil { 198 if err != nil {
260 log.Fatalf("cannot unfreeze container %q: %v", c.name, err)
261 return err 199 return err
262 } 200 }
263 return nil 201 return nil
264 } 202 }
265 203
266 // Destroy stops and removes the container. 204 // Destroy stops and removes the container.
267 func (c *Container) Destroy() error { 205 func (c *Container) Destroy() error {
268 if !c.IsConstructed() { 206 if !c.IsConstructed() {
269 return fmt.Errorf("container %q is not yet created", c.name) 207 return fmt.Errorf("container %q is not yet created", c.name)
270 } 208 }
271 if err := c.Stop(); err != nil { 209 if err := c.Stop(); err != nil {
272 return err 210 return err
273 } 211 }
274 » args := []string{ 212 » _, _, err := run("lxc-destroy", "-n", c.name)
275 » » "-n", c.name,
276 » }
277 » cmd := exec.Command("lxc-destroy", args...)
278 » _, _, err := run(cmd)
279 if err != nil { 213 if err != nil {
280 log.Fatalf("cannot destroy container %q: %v", c.name, err)
281 return err 214 return err
282 } 215 }
283 return nil 216 return nil
284 } 217 }
285 218
286 // Info returns the status and the process id of the container. 219 // Info returns the status and the process id of the container.
287 func (c *Container) Info() (int, int, error) { 220 func (c *Container) Info() (State, int, error) {
288 » args := []string{ 221 » stdout, _, err := run("lxc-info", "-n", c.name)
289 » » "-n", c.name, 222 » if err != nil {
223 » » return StateUnknown, -1, err
290 } 224 }
291 » cmd := exec.Command("lxc-info", args...) 225 » kv := keyValues(stdout, ": ")
292 » outbuf, _, err := run(cmd) 226 » state := State(kv["state"])
293 » if err != nil {
294 » » log.Fatalf("cannot retrieve container info for %q: %v", c.name, err)
295 » » return -1, -1, err
296 » }
297 » kv := keyValues(outbuf, ": ")
298 » state := state2go[kv["state"]]
299 pid, err := strconv.Atoi(kv["pid"]) 227 pid, err := strconv.Atoi(kv["pid"])
300 if err != nil { 228 if err != nil {
301 » » return -1, -1, err 229 » » return StateUnknown, -1, fmt.Errorf("cannot read the pid: %v", e rr)
302 } 230 }
303 return state, pid, nil 231 return state, pid, nil
304 } 232 }
305 233
306 // IsConstructed checks if the container image exists. 234 // IsConstructed checks if the container image exists.
307 func (c *Container) IsConstructed() bool { 235 func (c *Container) IsConstructed() bool {
308 fi, err := os.Stat(c.rootfs()) 236 fi, err := os.Stat(c.rootfs())
309 if err != nil { 237 if err != nil {
310 return false 238 return false
311 } 239 }
312 return fi.IsDir() 240 return fi.IsDir()
313 } 241 }
314 242
315 // IsRunning checks if the state of the container is 'RUNNING'. 243 // IsRunning checks if the state of the container is 'RUNNING'.
316 func (c *Container) IsRunning() bool { 244 func (c *Container) IsRunning() bool {
317 state, _, err := c.Info() 245 state, _, err := c.Info()
318 if err != nil { 246 if err != nil {
319 return false 247 return false
320 } 248 }
321 » return state == ContainerRunning 249 » return state == StateRunning
250 }
251
252 // String returns a short information about the container.
253 func (c *Container) String() string {
254 » state, pid, err := c.Info()
255 » if err != nil {
256 » » return fmt.Sprintf("cannot retrieve container info for %q: %v", c.name, err)
257 » }
258 » return fmt.Sprintf("container %q has state %q and pid %d", c.name, state , pid)
322 } 259 }
323 260
324 // containerHome returns the name of the container directory. 261 // containerHome returns the name of the container directory.
325 func (c *Container) containerHome() string { 262 func (c *Container) containerHome() string {
326 return "/var/lib/lxc/" + c.name 263 return "/var/lib/lxc/" + c.name
327 } 264 }
328 265
329 // rootfs returns the name of the directory containing the 266 // rootfs returns the name of the directory containing the
330 // root filesystem of the container. 267 // root filesystem of the container.
331 func (c *Container) rootfs() string { 268 func (c *Container) rootfs() string {
332 return c.containerHome() + "/rootfs/" 269 return c.containerHome() + "/rootfs/"
333 } 270 }
334 271
335 // wait waits for the passed container status. 272 // wait waits for the passed container states.
336 func (c *Container) wait(status int) error { 273 func (c *Container) wait(states ...State) error {
rog 2012/11/20 16:49:53 given that nothing is actually making use of the f
TheMue 2012/11/21 08:36:56 This package is intended to be a generic one also
337 » lxcStatus := []string{} 274 » stateStrs := make([]string, len(states))
338 » if status&ContainerStarting != 0 { 275 » for i, state := range states {
339 » » lxcStatus = append(lxcStatus, "STARTING") 276 » » stateStrs[i] = string(state)
340 } 277 }
341 » if status&ContainerRunning != 0 { 278 » combinedStates := strings.Join(stateStrs, "|")
342 » » lxcStatus = append(lxcStatus, "RUNNING") 279 » _, _, err := run("lxc-wait", "-n", c.name, "-s", combinedStates)
343 » }
344 » if status&ContainerAborting != 0 {
345 » » lxcStatus = append(lxcStatus, "ABORTING")
346 » }
347 » if status&ContainerStopping != 0 {
348 » » lxcStatus = append(lxcStatus, "STOPPING")
349 » }
350 » if status&ContainerStopped != 0 {
351 » » lxcStatus = append(lxcStatus, "STOPPED")
352 » }
353 » lxcStatusStr := strings.Join(lxcStatus, "|")
354 » args := []string{
355 » » "-n", c.name,
356 » » "-s", lxcStatusStr,
357 » }
358 » cmd := exec.Command("lxc-wait", args...)
359 » _, _, err := run(cmd)
360 if err != nil { 280 if err != nil {
361 log.Fatalf("cannot wait for container %q: %v", c.name, err)
362 return err 281 return err
363 } 282 }
364 return nil 283 return nil
365 } 284 }
366 285
367 // run executes the passed command and returns stdout and stderr. 286 // run executes the passed command and returns stdout and stderr.
368 func run(cmd *exec.Cmd) (outbuf, errbuf bytes.Buffer, err error) { 287 func run(c string, args ...string) (stdout, stderr string, err error) {
288 » var outbuf bytes.Buffer
289 » var errbuf bytes.Buffer
290 » cmd := exec.Command(c, args...)
369 cmd.Stdout = &outbuf 291 cmd.Stdout = &outbuf
370 cmd.Stderr = &errbuf 292 cmd.Stderr = &errbuf
371 err = cmd.Run() 293 err = cmd.Run()
372 if err != nil { 294 if err != nil {
373 » » log.Fatalf("error executing %q: %v", strings.Join(cmd.Args, " ") , err) 295 » » return "", "", err
374 » » return
375 } 296 }
376 » return 297 » return outbuf.String(), errbuf.String(), nil
377 } 298 }
378 299
379 // keyValues retrieves key/value pairs out of a command output. 300 // keyValues retrieves key/value pairs out of a command output.
380 func keyValues(raw bytes.Buffer, sep string) map[string]string { 301 func keyValues(raw string, sep string) map[string]string {
381 kv := map[string]string{} 302 kv := map[string]string{}
382 » lines := strings.Split(raw.String(), "\n") 303 » lines := strings.Split(raw, "\n")
383 for _, line := range lines { 304 for _, line := range lines {
384 parts := strings.SplitN(line, sep, 2) 305 parts := strings.SplitN(line, sep, 2)
385 if len(parts) == 2 { 306 if len(parts) == 2 {
386 kv[parts[0]] = strings.TrimSpace(parts[1]) 307 kv[parts[0]] = strings.TrimSpace(parts[1])
387 } 308 }
388 } 309 }
389 return kv 310 return kv
390 } 311 }
391 312
392 // nameSet retrieves a set of names out of a command output. 313 // nameSet retrieves a set of names out of a command output.
393 func nameSet(raw bytes.Buffer) []string { 314 func nameSet(raw string) []string {
394 collector := map[string]struct{}{} 315 collector := map[string]struct{}{}
395 set := []string{} 316 set := []string{}
396 » lines := strings.Split(raw.String(), "\n") 317 » lines := strings.Split(raw, "\n")
397 for _, line := range lines { 318 for _, line := range lines {
398 name := strings.TrimSpace(line) 319 name := strings.TrimSpace(line)
399 if name != "" { 320 if name != "" {
400 collector[name] = struct{}{} 321 collector[name] = struct{}{}
401 } 322 }
402 } 323 }
403 for name := range collector { 324 for name := range collector {
404 set = append(set, name) 325 set = append(set, name)
405 } 326 }
406 return set 327 return set
407 } 328 }
OLDNEW
« no previous file with comments | « export_test.go ('k') | golxc_test.go » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b