Left: | ||
Right: |
LEFT | RIGHT |
---|---|
1 // | 1 // |
2 // golxc - Go package to interact with Linux Containers (LXC). | 2 // golxc - Go package to interact with Linux Containers (LXC). |
3 // | 3 // |
4 // https://launchpad.net/golxc/ | 4 // https://launchpad.net/golxc/ |
5 // | 5 // |
6 // Copyright (c) 2012 Canonical Ltd. | 6 // Copyright (c) 2012 Canonical Ltd. |
7 // | 7 // |
8 | 8 |
9 package golxc | 9 package golxc |
10 | 10 |
11 import ( | 11 import ( |
12 "bytes" | |
13 "fmt" | 12 "fmt" |
14 "os" | 13 "os" |
15 "os/exec" | 14 "os/exec" |
16 "strconv" | 15 "strconv" |
17 "strings" | 16 "strings" |
18 ) | 17 ) |
18 | |
19 // Error reports the failure of a LXC command. | |
20 type Error struct { | |
21 Name string | |
22 Err error | |
23 Output []string | |
24 } | |
25 | |
26 func (e Error) Error() string { | |
27 if e.Output == nil { | |
28 return fmt.Sprintf("error executing %q: %v", e.Name, e.Err) | |
29 } | |
30 if len(e.Output) == 1 { | |
31 return fmt.Sprintf("error executing %q: %v", e.Name, e.Output[0] ) | |
32 } | |
33 return fmt.Sprintf("error executing %q: %s", e.Name, strings.Join(e.Outp ut, "; ")) | |
34 } | |
19 | 35 |
20 // State represents a container state. | 36 // State represents a container state. |
21 type State string | 37 type State string |
22 | 38 |
23 const ( | 39 const ( |
24 StateUnknown State = "UNKNOWN" | 40 StateUnknown State = "UNKNOWN" |
25 StateStopped State = "STOPPED" | 41 StateStopped State = "STOPPED" |
26 StateStarting State = "STARTING" | 42 StateStarting State = "STARTING" |
27 StateRunning State = "RUNNING" | 43 StateRunning State = "RUNNING" |
28 StateAborting State = "ABORTING" | 44 StateAborting State = "ABORTING" |
(...skipping 24 matching lines...) Expand all Loading... | |
53 // New returns a container instance which can then be used for operations | 69 // New returns a container instance which can then be used for operations |
54 // like Create(), Start(), Stop() or Destroy(). | 70 // like Create(), Start(), Stop() or Destroy(). |
55 func New(name string) *Container { | 71 func New(name string) *Container { |
56 return &Container{ | 72 return &Container{ |
57 name: name, | 73 name: name, |
58 } | 74 } |
59 } | 75 } |
60 | 76 |
61 // List lists the existing containers on the system. | 77 // List lists the existing containers on the system. |
62 func List() ([]*Container, error) { | 78 func List() ([]*Container, error) { |
63 » outbuf, _, err := run("lxc-ls", "-1") | 79 » stdout, err := run("lxc-ls", "-1") |
64 if err != nil { | 80 if err != nil { |
65 return nil, err | 81 return nil, err |
66 } | 82 } |
67 » names := nameSet(outbuf) | 83 » names := nameSet(stdout) |
68 containers := make([]*Container, len(names)) | 84 containers := make([]*Container, len(names)) |
69 for i, name := range names { | 85 for i, name := range names { |
70 containers[i] = New(name) | 86 containers[i] = New(name) |
71 } | 87 } |
72 return containers, nil | 88 return containers, nil |
73 } | 89 } |
74 | 90 |
75 // Name returns the name of the container. | 91 // Name returns the name of the container. |
76 func (c *Container) Name() string { | 92 func (c *Container) Name() string { |
77 return c.name | 93 return c.name |
78 } | 94 } |
79 | 95 |
80 // Create creates a new container based on the given template. | 96 // Create creates a new container based on the given template. |
81 func (c *Container) Create(template string, tmplArgs ...string) error { | 97 func (c *Container) Create(template string, tmplArgs ...string) error { |
82 if c.IsConstructed() { | 98 if c.IsConstructed() { |
83 » » return nil | 99 » » return fmt.Errorf("container %q is already created", c.Name()) |
84 } | 100 } |
85 args := []string{ | 101 args := []string{ |
86 "-n", c.name, | 102 "-n", c.name, |
87 "-t", template, | 103 "-t", template, |
88 "--", | 104 "--", |
89 } | 105 } |
90 if len(tmplArgs) != 0 { | 106 if len(tmplArgs) != 0 { |
91 args = append(args, tmplArgs...) | 107 args = append(args, tmplArgs...) |
92 } | 108 } |
93 » _, _, err := run("lxc-create", args...) | 109 » _, err := run("lxc-create", args...) |
94 if err != nil { | 110 if err != nil { |
95 return err | 111 return err |
96 } | 112 } |
97 return nil | 113 return nil |
98 } | 114 } |
99 | 115 |
100 // Start runs the container as a daemon. | 116 // Start runs the container as a daemon. |
101 func (c *Container) Start(configFile, consoleFile string) error { | 117 func (c *Container) Start(configFile, consoleFile string) error { |
118 if !c.IsConstructed() { | |
119 return fmt.Errorf("container %q is not yet created", c.name) | |
120 } | |
102 args := []string{ | 121 args := []string{ |
103 "--daemon", | 122 "--daemon", |
104 "-n", c.name, | 123 "-n", c.name, |
105 } | 124 } |
106 if configFile != "" { | 125 if configFile != "" { |
107 args = append(args, "-f", configFile) | 126 args = append(args, "-f", configFile) |
108 } | 127 } |
109 if consoleFile != "" { | 128 if consoleFile != "" { |
110 args = append(args, "-c", consoleFile) | 129 args = append(args, "-c", consoleFile) |
111 } | 130 } |
112 if c.LogFile != "" { | 131 if c.LogFile != "" { |
113 args = append(args, "-o", c.LogFile, "-l", string(c.LogLevel)) | 132 args = append(args, "-o", c.LogFile, "-l", string(c.LogLevel)) |
114 } | 133 } |
115 » _, _, err := run("lxc-start", args...) | 134 » _, err := run("lxc-start", args...) |
116 if err != nil { | 135 if err != nil { |
117 return err | 136 return err |
118 } | 137 } |
119 return c.Wait(StateRunning) | 138 return c.Wait(StateRunning) |
120 } | 139 } |
121 | 140 |
122 // Stop terminates the running container. | 141 // Stop terminates the running container. |
123 func (c *Container) Stop() error { | 142 func (c *Container) Stop() error { |
143 if !c.IsConstructed() { | |
144 return fmt.Errorf("container %q is not yet created", c.name) | |
145 } | |
124 args := []string{ | 146 args := []string{ |
125 "-n", c.name, | 147 "-n", c.name, |
126 } | 148 } |
127 if c.LogFile != "" { | 149 if c.LogFile != "" { |
128 args = append(args, "-o", c.LogFile, "-l", string(c.LogLevel)) | 150 args = append(args, "-o", c.LogFile, "-l", string(c.LogLevel)) |
129 } | 151 } |
130 » _, _, err := run("lxc-stop", args...) | 152 » _, err := run("lxc-stop", args...) |
131 if err != nil { | 153 if err != nil { |
132 return err | 154 return err |
133 } | 155 } |
134 return c.Wait(StateStopped) | 156 return c.Wait(StateStopped) |
135 } | 157 } |
136 | 158 |
137 // Clone creates a copy of the container, it gets the given name. | 159 // Clone creates a copy of the container, it gets the given name. |
138 func (c *Container) Clone(name string) (*Container, error) { | 160 func (c *Container) Clone(name string) (*Container, error) { |
139 if !c.IsConstructed() { | 161 if !c.IsConstructed() { |
140 return nil, fmt.Errorf("container %q is not yet created", c.name ) | 162 return nil, fmt.Errorf("container %q is not yet created", c.name ) |
141 } | 163 } |
142 cc := &Container{ | 164 cc := &Container{ |
143 name: name, | 165 name: name, |
144 } | 166 } |
145 if cc.IsConstructed() { | 167 if cc.IsConstructed() { |
146 return cc, nil | 168 return cc, nil |
147 } | 169 } |
148 args := []string{ | 170 args := []string{ |
149 "-o", c.name, | 171 "-o", c.name, |
150 "-n", name, | 172 "-n", name, |
151 } | 173 } |
152 » _, _, err := run("lxc-clone", args...) | 174 » _, err := run("lxc-clone", args...) |
153 if err != nil { | 175 if err != nil { |
154 return nil, err | 176 return nil, err |
155 } | 177 } |
156 return cc, nil | 178 return cc, nil |
157 } | 179 } |
158 | 180 |
159 // Freeze freezes all the container'ss processes. | 181 // Freeze freezes all the container's processes. |
160 func (c *Container) Freeze() error { | 182 func (c *Container) Freeze() error { |
161 if !c.IsConstructed() { | 183 if !c.IsConstructed() { |
162 return fmt.Errorf("container %q is not yet created", c.name) | 184 return fmt.Errorf("container %q is not yet created", c.name) |
185 } | |
186 if !c.IsRunning() { | |
187 return fmt.Errorf("container %q is not running", c.name) | |
163 } | 188 } |
164 args := []string{ | 189 args := []string{ |
165 "-n", c.name, | 190 "-n", c.name, |
166 } | 191 } |
167 if c.LogFile != "" { | 192 if c.LogFile != "" { |
168 args = append(args, "-o", c.LogFile, "-l", string(c.LogLevel)) | 193 args = append(args, "-o", c.LogFile, "-l", string(c.LogLevel)) |
169 } | 194 } |
170 » _, _, err := run("lxc-freeze", args...) | 195 » _, err := run("lxc-freeze", args...) |
171 » if err != nil { | 196 » if err != nil { |
172 » » return err | 197 » » return err |
173 » } | 198 » } |
174 » return nil | 199 » return nil |
175 } | 200 } |
176 | 201 |
177 // Unfreeze thaws all a froozen container's processes. | 202 // Unfreeze thaws all frozen container's processes. |
178 func (c *Container) Unfreeze() error { | 203 func (c *Container) Unfreeze() error { |
179 if !c.IsConstructed() { | 204 if !c.IsConstructed() { |
180 return fmt.Errorf("container %q is not yet created", c.name) | 205 return fmt.Errorf("container %q is not yet created", c.name) |
206 } | |
207 if c.IsRunning() { | |
208 return fmt.Errorf("container %q is not frozen", c.name) | |
181 } | 209 } |
182 args := []string{ | 210 args := []string{ |
183 "-n", c.name, | 211 "-n", c.name, |
184 } | 212 } |
185 if c.LogFile != "" { | 213 if c.LogFile != "" { |
186 args = append(args, "-o", c.LogFile, "-l", string(c.LogLevel)) | 214 args = append(args, "-o", c.LogFile, "-l", string(c.LogLevel)) |
187 } | 215 } |
188 » _, _, err := run("lxc-unfreeze", args...) | 216 » _, err := run("lxc-unfreeze", args...) |
189 if err != nil { | 217 if err != nil { |
190 return err | 218 return err |
191 } | 219 } |
192 return nil | 220 return nil |
193 } | 221 } |
194 | 222 |
195 // Destroy stops and removes the container. | 223 // Destroy stops and removes the container. |
196 func (c *Container) Destroy() error { | 224 func (c *Container) Destroy() error { |
197 if !c.IsConstructed() { | 225 if !c.IsConstructed() { |
198 return fmt.Errorf("container %q is not yet created", c.name) | 226 return fmt.Errorf("container %q is not yet created", c.name) |
199 } | 227 } |
200 if err := c.Stop(); err != nil { | 228 if err := c.Stop(); err != nil { |
201 return err | 229 return err |
202 } | 230 } |
203 » _, _, err := run("lxc-destroy", "-n", c.name) | 231 » _, err := run("lxc-destroy", "-n", c.name) |
204 » if err != nil { | 232 » if err != nil { |
205 » » return err | 233 » » return err |
206 » } | 234 » } |
207 » return nil | 235 » return nil |
208 } | 236 } |
209 | 237 |
210 // Wait waits for the passed container states. | 238 // Wait waits for one of the specified container states. |
211 func (c *Container) Wait(states ...State) error { | 239 func (c *Container) Wait(states ...State) error { |
240 if len(states) == 0 { | |
241 return fmt.Errorf("no states specified") | |
242 } | |
212 stateStrs := make([]string, len(states)) | 243 stateStrs := make([]string, len(states)) |
213 for i, state := range states { | 244 for i, state := range states { |
214 stateStrs[i] = string(state) | 245 stateStrs[i] = string(state) |
215 } | 246 } |
216 » combinedStates := strings.Join(stateStrs, "|") | 247 » waitStates := strings.Join(stateStrs, "|") |
217 » _, _, err := run("lxc-wait", "-n", c.name, "-s", combinedStates) | 248 » _, err := run("lxc-wait", "-n", c.name, "-s", waitStates) |
fwereade
2012/11/21 17:32:53
I guess LXC will complain about impossible state c
TheMue
2012/11/22 12:53:23
To LXC there are no impossible combinations. It's
| |
218 if err != nil { | 249 if err != nil { |
219 return err | 250 return err |
220 } | 251 } |
221 return nil | 252 return nil |
222 } | 253 } |
223 | 254 |
224 // Info returns the status and the process id of the container. | 255 // Info returns the status and the process id of the container. |
225 func (c *Container) Info() (State, int, error) { | 256 func (c *Container) Info() (State, int, error) { |
226 » stdout, _, err := run("lxc-info", "-n", c.name) | 257 » stdout, err := run("lxc-info", "-n", c.name) |
227 if err != nil { | 258 if err != nil { |
228 return StateUnknown, -1, err | 259 return StateUnknown, -1, err |
229 } | 260 } |
230 kv := keyValues(stdout, ": ") | 261 kv := keyValues(stdout, ": ") |
231 state := State(kv["state"]) | 262 state := State(kv["state"]) |
232 pid, err := strconv.Atoi(kv["pid"]) | 263 pid, err := strconv.Atoi(kv["pid"]) |
233 if err != nil { | 264 if err != nil { |
234 return StateUnknown, -1, fmt.Errorf("cannot read the pid: %v", e rr) | 265 return StateUnknown, -1, fmt.Errorf("cannot read the pid: %v", e rr) |
235 } | 266 } |
236 return state, pid, nil | 267 return state, pid, nil |
(...skipping 10 matching lines...) Expand all Loading... | |
247 | 278 |
248 // IsRunning checks if the state of the container is 'RUNNING'. | 279 // IsRunning checks if the state of the container is 'RUNNING'. |
249 func (c *Container) IsRunning() bool { | 280 func (c *Container) IsRunning() bool { |
250 state, _, err := c.Info() | 281 state, _, err := c.Info() |
251 if err != nil { | 282 if err != nil { |
252 return false | 283 return false |
253 } | 284 } |
254 return state == StateRunning | 285 return state == StateRunning |
255 } | 286 } |
256 | 287 |
257 // String returns a short information about the container. | 288 // String returns information about the container. |
fwereade
2012/11/21 17:32:53
s/a short/brief/
perhaps?
TheMue
2012/11/22 12:53:23
Done.
| |
258 func (c *Container) String() string { | 289 func (c *Container) String() string { |
259 state, pid, err := c.Info() | 290 state, pid, err := c.Info() |
260 if err != nil { | 291 if err != nil { |
261 return fmt.Sprintf("cannot retrieve container info for %q: %v", c.name, err) | 292 return fmt.Sprintf("cannot retrieve container info for %q: %v", c.name, err) |
262 } | 293 } |
263 » return fmt.Sprintf("container %q has state %q and pid %d", c.name, state , pid) | 294 » return fmt.Sprintf("container %q (%s, pid %d)", c.name, state, pid) |
264 } | 295 } |
265 | 296 |
266 // containerHome returns the name of the container directory. | 297 // containerHome returns the name of the container directory. |
267 func (c *Container) containerHome() string { | 298 func (c *Container) containerHome() string { |
268 return "/var/lib/lxc/" + c.name | 299 return "/var/lib/lxc/" + c.name |
269 } | 300 } |
270 | 301 |
271 // rootfs returns the name of the directory containing the | 302 // rootfs returns the name of the directory containing the |
272 // root filesystem of the container. | 303 // root filesystem of the container. |
273 func (c *Container) rootfs() string { | 304 func (c *Container) rootfs() string { |
274 return c.containerHome() + "/rootfs/" | 305 return c.containerHome() + "/rootfs/" |
275 } | 306 } |
276 | 307 |
277 // run executes the passed command and returns stdout and stderr. | 308 // run executes the passed command and returns the output. |
278 func run(c string, args ...string) (stdout, stderr string, err error) { | 309 func run(name string, args ...string) (string, error) { |
279 » var outbuf bytes.Buffer | 310 » cmd := exec.Command(name, args...) |
280 » var errbuf bytes.Buffer | 311 » output, err := cmd.CombinedOutput() |
281 » cmd := exec.Command(c, args...) | 312 » if err != nil { |
282 » cmd.Stdout = &outbuf | 313 » » return "", runError(name, err, output) |
283 » cmd.Stderr = &errbuf | 314 » } |
284 » err = cmd.Run() | 315 » return string(output), nil |
285 » if err != nil { | 316 } |
286 » » if _, ok := err.(*exec.ExitError); !ok { | 317 |
287 » » » return "", "", err | 318 // runError creates an error if run fails. |
319 func runError(name string, err error, output []byte) error { | |
320 » e := &Error{name, err, nil} | |
321 » for _, l := range strings.Split(string(output), "\n") { | |
322 » » if strings.HasPrefix(l, name+": ") { | |
323 » » » l = l[len(name)+2:] | |
288 } | 324 } |
289 » } | 325 » » if l != "" { |
290 » return outbuf.String(), errbuf.String(), err | 326 » » » e.Output = append(e.Output, l) |
327 » » } | |
328 » } | |
329 » return e | |
291 } | 330 } |
292 | 331 |
293 // keyValues retrieves key/value pairs out of a command output. | 332 // keyValues retrieves key/value pairs out of a command output. |
294 func keyValues(raw string, sep string) map[string]string { | 333 func keyValues(raw string, sep string) map[string]string { |
295 kv := map[string]string{} | 334 kv := map[string]string{} |
296 lines := strings.Split(raw, "\n") | 335 lines := strings.Split(raw, "\n") |
297 for _, line := range lines { | 336 for _, line := range lines { |
298 parts := strings.SplitN(line, sep, 2) | 337 parts := strings.SplitN(line, sep, 2) |
299 if len(parts) == 2 { | 338 if len(parts) == 2 { |
300 kv[parts[0]] = strings.TrimSpace(parts[1]) | 339 kv[parts[0]] = strings.TrimSpace(parts[1]) |
(...skipping 11 matching lines...) Expand all Loading... | |
312 name := strings.TrimSpace(line) | 351 name := strings.TrimSpace(line) |
313 if name != "" { | 352 if name != "" { |
314 collector[name] = struct{}{} | 353 collector[name] = struct{}{} |
315 } | 354 } |
316 } | 355 } |
317 for name := range collector { | 356 for name := range collector { |
318 set = append(set, name) | 357 set = append(set, name) |
319 } | 358 } |
320 return set | 359 return set |
321 } | 360 } |
LEFT | RIGHT |