Left: | ||
Right: |
OLD | NEW |
---|---|
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 } |
OLD | NEW |