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

Delta Between Two Patch Sets: golxc.go

Issue 6852065: golxc: add first container tests (Closed)
Left Patch Set: golxc: add first container tests Created 12 years, 4 months ago
Right Patch Set: golxc: add first container tests Created 12 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:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « export_test.go ('k') | golxc_test.go » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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
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
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
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 }
LEFTRIGHT

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