Skip to content

Commit a27b4b8

Browse files
committed
Initial commit
0 parents  commit a27b4b8

10 files changed

+1146
-0
lines changed

container.go

+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
package docker
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"io"
7+
"io/ioutil"
8+
"os"
9+
"os/exec"
10+
"path"
11+
"syscall"
12+
)
13+
14+
type Container struct {
15+
Name string
16+
Root string
17+
Path string
18+
Args []string
19+
20+
*Config
21+
*Filesystem
22+
*State
23+
24+
lxcConfigPath string
25+
cmd *exec.Cmd
26+
stdout *writeBroadcaster
27+
stderr *writeBroadcaster
28+
}
29+
30+
type Config struct {
31+
Hostname string
32+
Ram int64
33+
}
34+
35+
func createContainer(name string, root string, command string, args []string, layers []string, config *Config) (*Container, error) {
36+
container := &Container{
37+
Name: name,
38+
Root: root,
39+
Path: command,
40+
Args: args,
41+
Config: config,
42+
Filesystem: newFilesystem(path.Join(root, "rootfs"), path.Join(root, "rw"), layers),
43+
State: newState(),
44+
45+
lxcConfigPath: path.Join(root, "config.lxc"),
46+
stdout: newWriteBroadcaster(),
47+
stderr: newWriteBroadcaster(),
48+
}
49+
50+
if err := os.Mkdir(root, 0700); err != nil {
51+
return nil, err
52+
}
53+
54+
if err := container.save(); err != nil {
55+
return nil, err
56+
}
57+
if err := container.generateLXCConfig(); err != nil {
58+
return nil, err
59+
}
60+
return container, nil
61+
}
62+
63+
func loadContainer(containerPath string) (*Container, error) {
64+
configPath := path.Join(containerPath, "config.json")
65+
fi, err := os.Open(configPath)
66+
if err != nil {
67+
return nil, err
68+
}
69+
defer fi.Close()
70+
enc := json.NewDecoder(fi)
71+
container := &Container{}
72+
if err := enc.Decode(container); err != nil {
73+
return nil, err
74+
}
75+
return container, nil
76+
}
77+
78+
func (container *Container) save() error {
79+
configPath := path.Join(container.Root, "config.json")
80+
fo, err := os.Create(configPath)
81+
if err != nil {
82+
return err
83+
}
84+
defer fo.Close()
85+
enc := json.NewEncoder(fo)
86+
if err := enc.Encode(container); err != nil {
87+
return err
88+
}
89+
return nil
90+
}
91+
92+
func (container *Container) generateLXCConfig() error {
93+
fo, err := os.Create(container.lxcConfigPath)
94+
if err != nil {
95+
return err
96+
}
97+
defer fo.Close()
98+
99+
if err := LxcTemplateCompiled.Execute(fo, container); err != nil {
100+
return err
101+
}
102+
return nil
103+
}
104+
105+
func (container *Container) Start() error {
106+
if err := container.Filesystem.Mount(); err != nil {
107+
return err
108+
}
109+
110+
params := []string{
111+
"-n", container.Name,
112+
"-f", container.lxcConfigPath,
113+
"--",
114+
container.Path,
115+
}
116+
params = append(params, container.Args...)
117+
118+
container.cmd = exec.Command("/usr/bin/lxc-start", params...)
119+
container.cmd.Stdout = container.stdout
120+
container.cmd.Stderr = container.stderr
121+
122+
if err := container.cmd.Start(); err != nil {
123+
return err
124+
}
125+
container.State.setRunning(container.cmd.Process.Pid)
126+
go container.monitor()
127+
128+
// Wait until we are out of the STARTING state before returning
129+
//
130+
// Even though lxc-wait blocks until the container reaches a given state,
131+
// sometimes it returns an error code, which is why we have to retry.
132+
//
133+
// This is a rare race condition that happens for short lived programs
134+
for retries := 0; retries < 3; retries++ {
135+
err := exec.Command("/usr/bin/lxc-wait", "-n", container.Name, "-s", "RUNNING|STOPPED").Run()
136+
if err == nil {
137+
return nil
138+
}
139+
}
140+
return errors.New("Container failed to start")
141+
}
142+
143+
func (container *Container) Run() error {
144+
if err := container.Start(); err != nil {
145+
return err
146+
}
147+
container.Wait()
148+
return nil
149+
}
150+
151+
func (container *Container) Output() (output []byte, err error) {
152+
pipe, err := container.StdoutPipe()
153+
if err != nil {
154+
return nil, err
155+
}
156+
defer pipe.Close()
157+
if err := container.Start(); err != nil {
158+
return nil, err
159+
}
160+
output, err = ioutil.ReadAll(pipe)
161+
container.Wait()
162+
return output, err
163+
}
164+
165+
func (container *Container) StdoutPipe() (io.ReadCloser, error) {
166+
reader, writer := io.Pipe()
167+
container.stdout.AddWriter(writer)
168+
return newBufReader(reader), nil
169+
}
170+
171+
func (container *Container) StderrPipe() (io.ReadCloser, error) {
172+
reader, writer := io.Pipe()
173+
container.stderr.AddWriter(writer)
174+
return newBufReader(reader), nil
175+
}
176+
177+
func (container *Container) monitor() {
178+
container.cmd.Wait()
179+
container.stdout.Close()
180+
container.stderr.Close()
181+
container.State.setStopped(container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus())
182+
}
183+
184+
func (container *Container) Stop() error {
185+
if container.State.Running {
186+
if err := exec.Command("/usr/bin/lxc-stop", "-n", container.Name).Run(); err != nil {
187+
return err
188+
}
189+
//FIXME: We should lxc-wait for the container to stop
190+
}
191+
192+
if err := container.Filesystem.Umount(); err != nil {
193+
// FIXME: Do not abort, probably already umounted?
194+
return nil
195+
}
196+
return nil
197+
}
198+
199+
func (container *Container) Wait() {
200+
for container.State.Running {
201+
container.State.wait()
202+
}
203+
}

container_test.go

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package docker
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestStart(t *testing.T) {
8+
docker, err := newTestDocker()
9+
if err != nil {
10+
t.Fatal(err)
11+
}
12+
container, err := docker.Create(
13+
"start_test",
14+
"ls",
15+
[]string{"-al"},
16+
[]string{"/var/lib/docker/images/ubuntu"},
17+
&Config{
18+
Ram: 33554432,
19+
},
20+
)
21+
if err != nil {
22+
t.Fatal(err)
23+
}
24+
defer docker.Destroy(container)
25+
26+
if container.State.Running {
27+
t.Errorf("Container shouldn't be running")
28+
}
29+
if err := container.Start(); err != nil {
30+
t.Fatal(err)
31+
}
32+
container.Wait()
33+
if container.State.Running {
34+
t.Errorf("Container shouldn't be running")
35+
}
36+
// We should be able to call Wait again
37+
container.Wait()
38+
if container.State.Running {
39+
t.Errorf("Container shouldn't be running")
40+
}
41+
}
42+
43+
func TestRun(t *testing.T) {
44+
docker, err := newTestDocker()
45+
if err != nil {
46+
t.Fatal(err)
47+
}
48+
container, err := docker.Create(
49+
"run_test",
50+
"ls",
51+
[]string{"-al"},
52+
[]string{"/var/lib/docker/images/ubuntu"},
53+
&Config{
54+
Ram: 33554432,
55+
},
56+
)
57+
if err != nil {
58+
t.Fatal(err)
59+
}
60+
defer docker.Destroy(container)
61+
62+
if container.State.Running {
63+
t.Errorf("Container shouldn't be running")
64+
}
65+
if err := container.Run(); err != nil {
66+
t.Fatal(err)
67+
}
68+
if container.State.Running {
69+
t.Errorf("Container shouldn't be running")
70+
}
71+
}
72+
73+
func TestOutput(t *testing.T) {
74+
docker, err := newTestDocker()
75+
if err != nil {
76+
t.Fatal(err)
77+
}
78+
container, err := docker.Create(
79+
"output_test",
80+
"echo",
81+
[]string{"-n", "foobar"},
82+
[]string{"/var/lib/docker/images/ubuntu"},
83+
&Config{},
84+
)
85+
if err != nil {
86+
t.Fatal(err)
87+
}
88+
defer docker.Destroy(container)
89+
90+
pipe, err := container.StdoutPipe()
91+
defer pipe.Close()
92+
output, err := container.Output()
93+
if err != nil {
94+
t.Fatal(err)
95+
}
96+
if string(output) != "foobar" {
97+
t.Error(string(output))
98+
}
99+
}
100+
101+
func TestStop(t *testing.T) {
102+
docker, err := newTestDocker()
103+
if err != nil {
104+
t.Fatal(err)
105+
}
106+
container, err := docker.Create(
107+
"stop_test",
108+
"sleep",
109+
[]string{"300"},
110+
[]string{"/var/lib/docker/images/ubuntu"},
111+
&Config{},
112+
)
113+
if err != nil {
114+
t.Fatal(err)
115+
}
116+
defer docker.Destroy(container)
117+
118+
if container.State.Running {
119+
t.Errorf("Container shouldn't be running")
120+
}
121+
if err := container.Start(); err != nil {
122+
t.Fatal(err)
123+
}
124+
if !container.State.Running {
125+
t.Errorf("Container should be running")
126+
}
127+
if err := container.Stop(); err != nil {
128+
t.Fatal(err)
129+
}
130+
if container.State.Running {
131+
t.Errorf("Container shouldn't be running")
132+
}
133+
container.Wait()
134+
if container.State.Running {
135+
t.Errorf("Container shouldn't be running")
136+
}
137+
// Try stopping twice
138+
if err := container.Stop(); err != nil {
139+
t.Fatal(err)
140+
}
141+
}
142+
143+
func TestExitCode(t *testing.T) {
144+
docker, err := newTestDocker()
145+
if err != nil {
146+
t.Fatal(err)
147+
}
148+
149+
trueContainer, err := docker.Create(
150+
"exit_test_1",
151+
"/bin/true",
152+
[]string{""},
153+
[]string{"/var/lib/docker/images/ubuntu"},
154+
&Config{},
155+
)
156+
if err != nil {
157+
t.Fatal(err)
158+
}
159+
defer docker.Destroy(trueContainer)
160+
if err := trueContainer.Run(); err != nil {
161+
t.Fatal(err)
162+
}
163+
164+
falseContainer, err := docker.Create(
165+
"exit_test_2",
166+
"/bin/false",
167+
[]string{""},
168+
[]string{"/var/lib/docker/images/ubuntu"},
169+
&Config{},
170+
)
171+
if err != nil {
172+
t.Fatal(err)
173+
}
174+
defer docker.Destroy(falseContainer)
175+
if err := falseContainer.Run(); err != nil {
176+
t.Fatal(err)
177+
}
178+
179+
if trueContainer.State.ExitCode != 0 {
180+
t.Errorf("Unexpected exit code %v", trueContainer.State.ExitCode)
181+
}
182+
183+
if falseContainer.State.ExitCode != 1 {
184+
t.Errorf("Unexpected exit code %v", falseContainer.State.ExitCode)
185+
}
186+
}

0 commit comments

Comments
 (0)