Browse code

Initial commit

Andrea Luzzardi authored on 2013/01/19 09:13:39
Showing 10 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,203 @@
0
+package docker
1
+
2
+import (
3
+	"encoding/json"
4
+	"errors"
5
+	"io"
6
+	"io/ioutil"
7
+	"os"
8
+	"os/exec"
9
+	"path"
10
+	"syscall"
11
+)
12
+
13
+type Container struct {
14
+	Name string
15
+	Root string
16
+	Path string
17
+	Args []string
18
+
19
+	*Config
20
+	*Filesystem
21
+	*State
22
+
23
+	lxcConfigPath string
24
+	cmd           *exec.Cmd
25
+	stdout        *writeBroadcaster
26
+	stderr        *writeBroadcaster
27
+}
28
+
29
+type Config struct {
30
+	Hostname string
31
+	Ram      int64
32
+}
33
+
34
+func createContainer(name string, root string, command string, args []string, layers []string, config *Config) (*Container, error) {
35
+	container := &Container{
36
+		Name:       name,
37
+		Root:       root,
38
+		Path:       command,
39
+		Args:       args,
40
+		Config:     config,
41
+		Filesystem: newFilesystem(path.Join(root, "rootfs"), path.Join(root, "rw"), layers),
42
+		State:      newState(),
43
+
44
+		lxcConfigPath: path.Join(root, "config.lxc"),
45
+		stdout:        newWriteBroadcaster(),
46
+		stderr:        newWriteBroadcaster(),
47
+	}
48
+
49
+	if err := os.Mkdir(root, 0700); err != nil {
50
+		return nil, err
51
+	}
52
+
53
+	if err := container.save(); err != nil {
54
+		return nil, err
55
+	}
56
+	if err := container.generateLXCConfig(); err != nil {
57
+		return nil, err
58
+	}
59
+	return container, nil
60
+}
61
+
62
+func loadContainer(containerPath string) (*Container, error) {
63
+	configPath := path.Join(containerPath, "config.json")
64
+	fi, err := os.Open(configPath)
65
+	if err != nil {
66
+		return nil, err
67
+	}
68
+	defer fi.Close()
69
+	enc := json.NewDecoder(fi)
70
+	container := &Container{}
71
+	if err := enc.Decode(container); err != nil {
72
+		return nil, err
73
+	}
74
+	return container, nil
75
+}
76
+
77
+func (container *Container) save() error {
78
+	configPath := path.Join(container.Root, "config.json")
79
+	fo, err := os.Create(configPath)
80
+	if err != nil {
81
+		return err
82
+	}
83
+	defer fo.Close()
84
+	enc := json.NewEncoder(fo)
85
+	if err := enc.Encode(container); err != nil {
86
+		return err
87
+	}
88
+	return nil
89
+}
90
+
91
+func (container *Container) generateLXCConfig() error {
92
+	fo, err := os.Create(container.lxcConfigPath)
93
+	if err != nil {
94
+		return err
95
+	}
96
+	defer fo.Close()
97
+
98
+	if err := LxcTemplateCompiled.Execute(fo, container); err != nil {
99
+		return err
100
+	}
101
+	return nil
102
+}
103
+
104
+func (container *Container) Start() error {
105
+	if err := container.Filesystem.Mount(); err != nil {
106
+		return err
107
+	}
108
+
109
+	params := []string{
110
+		"-n", container.Name,
111
+		"-f", container.lxcConfigPath,
112
+		"--",
113
+		container.Path,
114
+	}
115
+	params = append(params, container.Args...)
116
+
117
+	container.cmd = exec.Command("/usr/bin/lxc-start", params...)
118
+	container.cmd.Stdout = container.stdout
119
+	container.cmd.Stderr = container.stderr
120
+
121
+	if err := container.cmd.Start(); err != nil {
122
+		return err
123
+	}
124
+	container.State.setRunning(container.cmd.Process.Pid)
125
+	go container.monitor()
126
+
127
+	// Wait until we are out of the STARTING state before returning
128
+	//
129
+	// Even though lxc-wait blocks until the container reaches a given state,
130
+	// sometimes it returns an error code, which is why we have to retry.
131
+	//
132
+	// This is a rare race condition that happens for short lived programs
133
+	for retries := 0; retries < 3; retries++ {
134
+		err := exec.Command("/usr/bin/lxc-wait", "-n", container.Name, "-s", "RUNNING|STOPPED").Run()
135
+		if err == nil {
136
+			return nil
137
+		}
138
+	}
139
+	return errors.New("Container failed to start")
140
+}
141
+
142
+func (container *Container) Run() error {
143
+	if err := container.Start(); err != nil {
144
+		return err
145
+	}
146
+	container.Wait()
147
+	return nil
148
+}
149
+
150
+func (container *Container) Output() (output []byte, err error) {
151
+	pipe, err := container.StdoutPipe()
152
+	if err != nil {
153
+		return nil, err
154
+	}
155
+	defer pipe.Close()
156
+	if err := container.Start(); err != nil {
157
+		return nil, err
158
+	}
159
+	output, err = ioutil.ReadAll(pipe)
160
+	container.Wait()
161
+	return output, err
162
+}
163
+
164
+func (container *Container) StdoutPipe() (io.ReadCloser, error) {
165
+	reader, writer := io.Pipe()
166
+	container.stdout.AddWriter(writer)
167
+	return newBufReader(reader), nil
168
+}
169
+
170
+func (container *Container) StderrPipe() (io.ReadCloser, error) {
171
+	reader, writer := io.Pipe()
172
+	container.stderr.AddWriter(writer)
173
+	return newBufReader(reader), nil
174
+}
175
+
176
+func (container *Container) monitor() {
177
+	container.cmd.Wait()
178
+	container.stdout.Close()
179
+	container.stderr.Close()
180
+	container.State.setStopped(container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus())
181
+}
182
+
183
+func (container *Container) Stop() error {
184
+	if container.State.Running {
185
+		if err := exec.Command("/usr/bin/lxc-stop", "-n", container.Name).Run(); err != nil {
186
+			return err
187
+		}
188
+		//FIXME: We should lxc-wait for the container to stop
189
+	}
190
+
191
+	if err := container.Filesystem.Umount(); err != nil {
192
+		// FIXME: Do not abort, probably already umounted?
193
+		return nil
194
+	}
195
+	return nil
196
+}
197
+
198
+func (container *Container) Wait() {
199
+	for container.State.Running {
200
+		container.State.wait()
201
+	}
202
+}
0 203
new file mode 100644
... ...
@@ -0,0 +1,186 @@
0
+package docker
1
+
2
+import (
3
+	"testing"
4
+)
5
+
6
+func TestStart(t *testing.T) {
7
+	docker, err := newTestDocker()
8
+	if err != nil {
9
+		t.Fatal(err)
10
+	}
11
+	container, err := docker.Create(
12
+		"start_test",
13
+		"ls",
14
+		[]string{"-al"},
15
+		[]string{"/var/lib/docker/images/ubuntu"},
16
+		&Config{
17
+			Ram: 33554432,
18
+		},
19
+	)
20
+	if err != nil {
21
+		t.Fatal(err)
22
+	}
23
+	defer docker.Destroy(container)
24
+
25
+	if container.State.Running {
26
+		t.Errorf("Container shouldn't be running")
27
+	}
28
+	if err := container.Start(); err != nil {
29
+		t.Fatal(err)
30
+	}
31
+	container.Wait()
32
+	if container.State.Running {
33
+		t.Errorf("Container shouldn't be running")
34
+	}
35
+	// We should be able to call Wait again
36
+	container.Wait()
37
+	if container.State.Running {
38
+		t.Errorf("Container shouldn't be running")
39
+	}
40
+}
41
+
42
+func TestRun(t *testing.T) {
43
+	docker, err := newTestDocker()
44
+	if err != nil {
45
+		t.Fatal(err)
46
+	}
47
+	container, err := docker.Create(
48
+		"run_test",
49
+		"ls",
50
+		[]string{"-al"},
51
+		[]string{"/var/lib/docker/images/ubuntu"},
52
+		&Config{
53
+			Ram: 33554432,
54
+		},
55
+	)
56
+	if err != nil {
57
+		t.Fatal(err)
58
+	}
59
+	defer docker.Destroy(container)
60
+
61
+	if container.State.Running {
62
+		t.Errorf("Container shouldn't be running")
63
+	}
64
+	if err := container.Run(); err != nil {
65
+		t.Fatal(err)
66
+	}
67
+	if container.State.Running {
68
+		t.Errorf("Container shouldn't be running")
69
+	}
70
+}
71
+
72
+func TestOutput(t *testing.T) {
73
+	docker, err := newTestDocker()
74
+	if err != nil {
75
+		t.Fatal(err)
76
+	}
77
+	container, err := docker.Create(
78
+		"output_test",
79
+		"echo",
80
+		[]string{"-n", "foobar"},
81
+		[]string{"/var/lib/docker/images/ubuntu"},
82
+		&Config{},
83
+	)
84
+	if err != nil {
85
+		t.Fatal(err)
86
+	}
87
+	defer docker.Destroy(container)
88
+
89
+	pipe, err := container.StdoutPipe()
90
+	defer pipe.Close()
91
+	output, err := container.Output()
92
+	if err != nil {
93
+		t.Fatal(err)
94
+	}
95
+	if string(output) != "foobar" {
96
+		t.Error(string(output))
97
+	}
98
+}
99
+
100
+func TestStop(t *testing.T) {
101
+	docker, err := newTestDocker()
102
+	if err != nil {
103
+		t.Fatal(err)
104
+	}
105
+	container, err := docker.Create(
106
+		"stop_test",
107
+		"sleep",
108
+		[]string{"300"},
109
+		[]string{"/var/lib/docker/images/ubuntu"},
110
+		&Config{},
111
+	)
112
+	if err != nil {
113
+		t.Fatal(err)
114
+	}
115
+	defer docker.Destroy(container)
116
+
117
+	if container.State.Running {
118
+		t.Errorf("Container shouldn't be running")
119
+	}
120
+	if err := container.Start(); err != nil {
121
+		t.Fatal(err)
122
+	}
123
+	if !container.State.Running {
124
+		t.Errorf("Container should be running")
125
+	}
126
+	if err := container.Stop(); err != nil {
127
+		t.Fatal(err)
128
+	}
129
+	if container.State.Running {
130
+		t.Errorf("Container shouldn't be running")
131
+	}
132
+	container.Wait()
133
+	if container.State.Running {
134
+		t.Errorf("Container shouldn't be running")
135
+	}
136
+	// Try stopping twice
137
+	if err := container.Stop(); err != nil {
138
+		t.Fatal(err)
139
+	}
140
+}
141
+
142
+func TestExitCode(t *testing.T) {
143
+	docker, err := newTestDocker()
144
+	if err != nil {
145
+		t.Fatal(err)
146
+	}
147
+
148
+	trueContainer, err := docker.Create(
149
+		"exit_test_1",
150
+		"/bin/true",
151
+		[]string{""},
152
+		[]string{"/var/lib/docker/images/ubuntu"},
153
+		&Config{},
154
+	)
155
+	if err != nil {
156
+		t.Fatal(err)
157
+	}
158
+	defer docker.Destroy(trueContainer)
159
+	if err := trueContainer.Run(); err != nil {
160
+		t.Fatal(err)
161
+	}
162
+
163
+	falseContainer, err := docker.Create(
164
+		"exit_test_2",
165
+		"/bin/false",
166
+		[]string{""},
167
+		[]string{"/var/lib/docker/images/ubuntu"},
168
+		&Config{},
169
+	)
170
+	if err != nil {
171
+		t.Fatal(err)
172
+	}
173
+	defer docker.Destroy(falseContainer)
174
+	if err := falseContainer.Run(); err != nil {
175
+		t.Fatal(err)
176
+	}
177
+
178
+	if trueContainer.State.ExitCode != 0 {
179
+		t.Errorf("Unexpected exit code %v", trueContainer.State.ExitCode)
180
+	}
181
+
182
+	if falseContainer.State.ExitCode != 1 {
183
+		t.Errorf("Unexpected exit code %v", falseContainer.State.ExitCode)
184
+	}
185
+}
0 186
new file mode 100644
... ...
@@ -0,0 +1,112 @@
0
+package docker
1
+
2
+import (
3
+	"container/list"
4
+	"fmt"
5
+	"io/ioutil"
6
+	"os"
7
+	"path"
8
+)
9
+
10
+type Docker struct {
11
+	root       string
12
+	repository string
13
+	containers *list.List
14
+}
15
+
16
+func (docker *Docker) List() []*Container {
17
+	containers := []*Container{}
18
+	for e := docker.containers.Front(); e != nil; e = e.Next() {
19
+		containers = append(containers, e.Value.(*Container))
20
+	}
21
+	return containers
22
+}
23
+
24
+func (docker *Docker) getContainerElement(name string) *list.Element {
25
+	for e := docker.containers.Front(); e != nil; e = e.Next() {
26
+		container := e.Value.(*Container)
27
+		if container.Name == name {
28
+			return e
29
+		}
30
+	}
31
+	return nil
32
+}
33
+
34
+func (docker *Docker) Get(name string) *Container {
35
+	e := docker.getContainerElement(name)
36
+	if e == nil {
37
+		return nil
38
+	}
39
+	return e.Value.(*Container)
40
+}
41
+
42
+func (docker *Docker) Exists(name string) bool {
43
+	return docker.Get(name) != nil
44
+}
45
+
46
+func (docker *Docker) Create(name string, command string, args []string, layers []string, config *Config) (*Container, error) {
47
+	if docker.Exists(name) {
48
+		return nil, fmt.Errorf("Container %v already exists", name)
49
+	}
50
+	root := path.Join(docker.repository, name)
51
+	container, err := createContainer(name, root, command, args, layers, config)
52
+	if err != nil {
53
+		return nil, err
54
+	}
55
+	docker.containers.PushBack(container)
56
+	return container, nil
57
+}
58
+
59
+func (docker *Docker) Destroy(container *Container) error {
60
+	element := docker.getContainerElement(container.Name)
61
+	if element == nil {
62
+		return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Name)
63
+	}
64
+
65
+	if err := container.Stop(); err != nil {
66
+		return err
67
+	}
68
+	if err := os.RemoveAll(container.Root); err != nil {
69
+		return err
70
+	}
71
+
72
+	docker.containers.Remove(element)
73
+	return nil
74
+}
75
+
76
+func (docker *Docker) restore() error {
77
+	dir, err := ioutil.ReadDir(docker.repository)
78
+	if err != nil {
79
+		return err
80
+	}
81
+	for _, v := range dir {
82
+		container, err := loadContainer(path.Join(docker.repository, v.Name()))
83
+		if err != nil {
84
+			fmt.Errorf("Failed to load %v: %v", v.Name(), err)
85
+			continue
86
+		}
87
+		docker.containers.PushBack(container)
88
+	}
89
+	return nil
90
+}
91
+
92
+func New() (*Docker, error) {
93
+	return NewFromDirectory("/var/lib/docker")
94
+}
95
+
96
+func NewFromDirectory(root string) (*Docker, error) {
97
+	docker := &Docker{
98
+		root:       root,
99
+		repository: path.Join(root, "containers"),
100
+		containers: list.New(),
101
+	}
102
+
103
+	if err := os.Mkdir(docker.repository, 0700); err != nil && !os.IsExist(err) {
104
+		return nil, err
105
+	}
106
+
107
+	if err := docker.restore(); err != nil {
108
+		return nil, err
109
+	}
110
+	return docker, nil
111
+}
0 112
new file mode 100644
... ...
@@ -0,0 +1,175 @@
0
+package docker
1
+
2
+import (
3
+	"io/ioutil"
4
+	"os"
5
+	"testing"
6
+)
7
+
8
+func newTestDocker() (*Docker, error) {
9
+	root, err := ioutil.TempDir("", "docker-test")
10
+	if err != nil {
11
+		return nil, err
12
+	}
13
+	docker, err := NewFromDirectory(root)
14
+	if err != nil {
15
+		return nil, err
16
+	}
17
+	return docker, nil
18
+}
19
+
20
+func TestCreate(t *testing.T) {
21
+	docker, err := newTestDocker()
22
+	if err != nil {
23
+		t.Fatal(err)
24
+	}
25
+
26
+	// Make sure we start we 0 containers
27
+	if len(docker.List()) != 0 {
28
+		t.Errorf("Expected 0 containers, %v found", len(docker.List()))
29
+	}
30
+	container, err := docker.Create(
31
+		"test_create",
32
+		"ls",
33
+		[]string{"-al"},
34
+		[]string{"/var/lib/docker/images/ubuntu"},
35
+		&Config{},
36
+	)
37
+	if err != nil {
38
+		t.Fatal(err)
39
+	}
40
+
41
+	defer func() {
42
+		if err := docker.Destroy(container); err != nil {
43
+			t.Error(err)
44
+		}
45
+	}()
46
+
47
+	// Make sure we can find the newly created container with List()
48
+	if len(docker.List()) != 1 {
49
+		t.Errorf("Expected 1 container, %v found", len(docker.List()))
50
+	}
51
+
52
+	// Make sure the container List() returns is the right one
53
+	if docker.List()[0].Name != "test_create" {
54
+		t.Errorf("Unexpected container %v returned by List", docker.List()[0])
55
+	}
56
+
57
+	// Make sure we can get the container with Get()
58
+	if docker.Get("test_create") == nil {
59
+		t.Errorf("Unable to get newly created container")
60
+	}
61
+
62
+	// Make sure it is the right container
63
+	if docker.Get("test_create") != container {
64
+		t.Errorf("Get() returned the wrong container")
65
+	}
66
+
67
+	// Make sure Exists returns it as existing
68
+	if !docker.Exists("test_create") {
69
+		t.Errorf("Exists() returned false for a newly created container")
70
+	}
71
+}
72
+
73
+func TestDestroy(t *testing.T) {
74
+	docker, err := newTestDocker()
75
+	if err != nil {
76
+		t.Fatal(err)
77
+	}
78
+	container, err := docker.Create(
79
+		"test_destroy",
80
+		"ls",
81
+		[]string{"-al"},
82
+		[]string{"/var/lib/docker/images/ubuntu"},
83
+		&Config{},
84
+	)
85
+	if err != nil {
86
+		t.Fatal(err)
87
+	}
88
+	// Destroy
89
+	if err := docker.Destroy(container); err != nil {
90
+		t.Error(err)
91
+	}
92
+
93
+	// Make sure docker.Exists() behaves correctly
94
+	if docker.Exists("test_destroy") {
95
+		t.Errorf("Exists() returned true")
96
+	}
97
+
98
+	// Make sure docker.List() doesn't list the destroyed container
99
+	if len(docker.List()) != 0 {
100
+		t.Errorf("Expected 0 container, %v found", len(docker.List()))
101
+	}
102
+
103
+	// Make sure docker.Get() refuses to return the unexisting container
104
+	if docker.Get("test_destroy") != nil {
105
+		t.Errorf("Unable to get newly created container")
106
+	}
107
+
108
+	// Make sure the container root directory does not exist anymore
109
+	_, err = os.Stat(container.Root)
110
+	if err == nil || !os.IsNotExist(err) {
111
+		t.Errorf("Container root directory still exists after destroy")
112
+	}
113
+
114
+	// Test double destroy
115
+	if err := docker.Destroy(container); err == nil {
116
+		// It should have failed
117
+		t.Errorf("Double destroy did not fail")
118
+	}
119
+}
120
+
121
+func TestGet(t *testing.T) {
122
+	docker, err := newTestDocker()
123
+	if err != nil {
124
+		t.Fatal(err)
125
+	}
126
+	container1, err := docker.Create(
127
+		"test1",
128
+		"ls",
129
+		[]string{"-al"},
130
+		[]string{"/var/lib/docker/images/ubuntu"},
131
+		&Config{},
132
+	)
133
+	if err != nil {
134
+		t.Fatal(err)
135
+	}
136
+	defer docker.Destroy(container1)
137
+
138
+	container2, err := docker.Create(
139
+		"test2",
140
+		"ls",
141
+		[]string{"-al"},
142
+		[]string{"/var/lib/docker/images/ubuntu"},
143
+		&Config{},
144
+	)
145
+	if err != nil {
146
+		t.Fatal(err)
147
+	}
148
+	defer docker.Destroy(container2)
149
+
150
+	container3, err := docker.Create(
151
+		"test3",
152
+		"ls",
153
+		[]string{"-al"},
154
+		[]string{"/var/lib/docker/images/ubuntu"},
155
+		&Config{},
156
+	)
157
+	if err != nil {
158
+		t.Fatal(err)
159
+	}
160
+	defer docker.Destroy(container3)
161
+
162
+	if docker.Get("test1") != container1 {
163
+		t.Errorf("Get(test1) returned %v while expecting %v", docker.Get("test1"), container1)
164
+	}
165
+
166
+	if docker.Get("test2") != container2 {
167
+		t.Errorf("Get(test2) returned %v while expecting %v", docker.Get("test2"), container2)
168
+	}
169
+
170
+	if docker.Get("test3") != container3 {
171
+		t.Errorf("Get(test3) returned %v while expecting %v", docker.Get("test3"), container3)
172
+	}
173
+
174
+}
0 175
new file mode 100644
... ...
@@ -0,0 +1,52 @@
0
+package docker
1
+
2
+import (
3
+	"fmt"
4
+	"os"
5
+	"os/exec"
6
+)
7
+
8
+type Filesystem struct {
9
+	RootFS string
10
+	RWPath string
11
+	Layers []string
12
+}
13
+
14
+func (fs *Filesystem) createMountPoints() error {
15
+	if err := os.Mkdir(fs.RootFS, 0700); err != nil && !os.IsExist(err) {
16
+		return err
17
+	}
18
+	if err := os.Mkdir(fs.RWPath, 0700); err != nil && !os.IsExist(err) {
19
+		return err
20
+	}
21
+	return nil
22
+}
23
+
24
+func (fs *Filesystem) Mount() error {
25
+	if err := fs.createMountPoints(); err != nil {
26
+		return err
27
+	}
28
+	rwBranch := fmt.Sprintf("%v=rw", fs.RWPath)
29
+	roBranches := ""
30
+	for _, layer := range fs.Layers {
31
+		roBranches += fmt.Sprintf("%v=ro:", layer)
32
+	}
33
+	branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
34
+	cmd := exec.Command("mount", "-t", "aufs", "-o", branches, "none", fs.RootFS)
35
+	if err := cmd.Run(); err != nil {
36
+		return err
37
+	}
38
+	return nil
39
+}
40
+
41
+func (fs *Filesystem) Umount() error {
42
+	return exec.Command("umount", fs.RootFS).Run()
43
+}
44
+
45
+func newFilesystem(rootfs string, rwpath string, layers []string) *Filesystem {
46
+	return &Filesystem{
47
+		RootFS: rootfs,
48
+		RWPath: rwpath,
49
+		Layers: layers,
50
+	}
51
+}
0 52
new file mode 100644
... ...
@@ -0,0 +1,35 @@
0
+package docker
1
+
2
+import (
3
+	"io/ioutil"
4
+	"testing"
5
+)
6
+
7
+func TestFilesystem(t *testing.T) {
8
+	rootfs, err := ioutil.TempDir("", "docker-test-root")
9
+	if err != nil {
10
+		t.Fatal(err)
11
+	}
12
+	rwpath, err := ioutil.TempDir("", "docker-test-rw")
13
+	if err != nil {
14
+		t.Fatal(err)
15
+	}
16
+
17
+	filesystem := newFilesystem(rootfs, rwpath, []string{"/var/lib/docker/images/ubuntu", "/var/lib/docker/images/test"})
18
+
19
+	if err := filesystem.Umount(); err == nil {
20
+		t.Errorf("Umount succeeded even though the filesystem was not mounted")
21
+	}
22
+
23
+	if err := filesystem.Mount(); err != nil {
24
+		t.Fatal(err)
25
+	}
26
+
27
+	if err := filesystem.Umount(); err != nil {
28
+		t.Fatal(err)
29
+	}
30
+
31
+	if err := filesystem.Umount(); err == nil {
32
+		t.Errorf("Umount succeeded even though the filesystem was already umounted")
33
+	}
34
+}
0 35
new file mode 100644
... ...
@@ -0,0 +1,94 @@
0
+package docker
1
+
2
+import (
3
+	"text/template"
4
+)
5
+
6
+const LxcTemplate = `
7
+# hostname
8
+{{if .Config.Hostname}}
9
+lxc.utsname = {{.Config.Hostname}}
10
+{{else}}
11
+lxc.utsname = {{.Name}}
12
+{{end}}
13
+#lxc.aa_profile = unconfined
14
+
15
+# network configuration
16
+#lxc.network.type = veth
17
+#lxc.network.flags = up
18
+#lxc.network.link = br0
19
+#lxc.network.name = eth0  # Internal container network interface name
20
+#lxc.network.mtu = 1500
21
+#lxc.network.ipv4 = {ip_address}/{ip_prefix_len}
22
+
23
+# root filesystem
24
+lxc.rootfs = {{.Filesystem.RootFS}}
25
+
26
+# use a dedicated pts for the container (and limit the number of pseudo terminal
27
+# available)
28
+lxc.pts = 1024
29
+
30
+# disable the main console
31
+lxc.console = none
32
+
33
+# no controlling tty at all
34
+lxc.tty = 1
35
+
36
+# no implicit access to devices
37
+lxc.cgroup.devices.deny = a
38
+
39
+# /dev/null and zero
40
+lxc.cgroup.devices.allow = c 1:3 rwm
41
+lxc.cgroup.devices.allow = c 1:5 rwm
42
+
43
+# consoles
44
+lxc.cgroup.devices.allow = c 5:1 rwm
45
+lxc.cgroup.devices.allow = c 5:0 rwm
46
+lxc.cgroup.devices.allow = c 4:0 rwm
47
+lxc.cgroup.devices.allow = c 4:1 rwm
48
+
49
+# /dev/urandom,/dev/random
50
+lxc.cgroup.devices.allow = c 1:9 rwm
51
+lxc.cgroup.devices.allow = c 1:8 rwm
52
+
53
+# /dev/pts/* - pts namespaces are "coming soon"
54
+lxc.cgroup.devices.allow = c 136:* rwm
55
+lxc.cgroup.devices.allow = c 5:2 rwm
56
+
57
+# tuntap
58
+lxc.cgroup.devices.allow = c 10:200 rwm
59
+
60
+# fuse
61
+#lxc.cgroup.devices.allow = c 10:229 rwm
62
+
63
+# rtc
64
+#lxc.cgroup.devices.allow = c 254:0 rwm
65
+
66
+
67
+# standard mount point
68
+lxc.mount.entry = proc {{.Filesystem.RootFS}}/proc proc nosuid,nodev,noexec 0 0
69
+lxc.mount.entry = sysfs {{.Filesystem.RootFS}}/sys sysfs nosuid,nodev,noexec 0 0
70
+lxc.mount.entry = devpts {{.Filesystem.RootFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0
71
+#lxc.mount.entry = varrun {{.Filesystem.RootFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0
72
+#lxc.mount.entry = varlock {{.Filesystem.RootFS}}/var/lock tmpfs size=1024k,nosuid,nodev,noexec 0 0
73
+#lxc.mount.entry = shm {{.Filesystem.RootFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0
74
+
75
+
76
+# drop linux capabilities (apply mainly to the user root in the container)
77
+lxc.cap.drop = audit_control audit_write mac_admin mac_override mknod net_raw setfcap setpcap sys_admin sys_boot sys_module sys_nice sys_pacct sys_rawio sys_resource sys_time sys_tty_config
78
+
79
+# limits
80
+{{if .Config.Ram}}
81
+lxc.cgroup.memory.limit_in_bytes = {{.Config.Ram}}
82
+{{end}}
83
+`
84
+
85
+var LxcTemplateCompiled *template.Template
86
+
87
+func init() {
88
+	var err error
89
+	LxcTemplateCompiled, err = template.New("lxc").Parse(LxcTemplate)
90
+	if err != nil {
91
+		panic(err)
92
+	}
93
+}
0 94
new file mode 100644
... ...
@@ -0,0 +1,48 @@
0
+package docker
1
+
2
+import (
3
+	"sync"
4
+)
5
+
6
+type State struct {
7
+	Running  bool
8
+	Pid      int
9
+	ExitCode int
10
+
11
+	stateChangeLock *sync.Mutex
12
+	stateChangeCond *sync.Cond
13
+}
14
+
15
+func newState() *State {
16
+	lock := new(sync.Mutex)
17
+	return &State{
18
+		stateChangeLock: lock,
19
+		stateChangeCond: sync.NewCond(lock),
20
+	}
21
+}
22
+
23
+func (s *State) setRunning(pid int) {
24
+	s.Running = true
25
+	s.ExitCode = 0
26
+	s.Pid = pid
27
+	s.broadcast()
28
+}
29
+
30
+func (s *State) setStopped(exitCode int) {
31
+	s.Running = false
32
+	s.Pid = 0
33
+	s.ExitCode = exitCode
34
+	s.broadcast()
35
+}
36
+
37
+func (s *State) broadcast() {
38
+	s.stateChangeLock.Lock()
39
+	s.stateChangeCond.Broadcast()
40
+	s.stateChangeLock.Unlock()
41
+}
42
+
43
+func (s *State) wait() {
44
+	s.stateChangeLock.Lock()
45
+	s.stateChangeCond.Wait()
46
+	s.stateChangeLock.Unlock()
47
+}
0 48
new file mode 100644
... ...
@@ -0,0 +1,115 @@
0
+package docker
1
+
2
+import (
3
+	"bytes"
4
+	"container/list"
5
+	"io"
6
+	"sync"
7
+)
8
+
9
+type bufReader struct {
10
+	buf    *bytes.Buffer
11
+	reader io.Reader
12
+	err    error
13
+	l      sync.Mutex
14
+	wait   sync.Cond
15
+}
16
+
17
+func newBufReader(r io.Reader) *bufReader {
18
+	reader := &bufReader{
19
+		buf:    &bytes.Buffer{},
20
+		reader: r,
21
+	}
22
+	reader.wait.L = &reader.l
23
+	go reader.drain()
24
+	return reader
25
+}
26
+
27
+func (r *bufReader) drain() {
28
+	buf := make([]byte, 1024)
29
+	for {
30
+		n, err := r.reader.Read(buf)
31
+		if err != nil {
32
+			r.err = err
33
+		} else {
34
+			r.buf.Write(buf[0:n])
35
+		}
36
+		r.l.Lock()
37
+		r.wait.Signal()
38
+		r.l.Unlock()
39
+		if err != nil {
40
+			break
41
+		}
42
+	}
43
+}
44
+
45
+func (r *bufReader) Read(p []byte) (n int, err error) {
46
+	for {
47
+		n, err = r.buf.Read(p)
48
+		if n > 0 {
49
+			return n, err
50
+		}
51
+		if r.err != nil {
52
+			return 0, r.err
53
+		}
54
+		r.l.Lock()
55
+		r.wait.Wait()
56
+		r.l.Unlock()
57
+	}
58
+	return
59
+}
60
+
61
+func (r *bufReader) Close() error {
62
+	closer, ok := r.reader.(io.ReadCloser)
63
+	if !ok {
64
+		return nil
65
+	}
66
+	return closer.Close()
67
+}
68
+
69
+type writeBroadcaster struct {
70
+	writers *list.List
71
+}
72
+
73
+func (w *writeBroadcaster) AddWriter(writer io.WriteCloser) {
74
+	w.writers.PushBack(writer)
75
+}
76
+
77
+func (w *writeBroadcaster) RemoveWriter(writer io.WriteCloser) {
78
+	for e := w.writers.Front(); e != nil; e = e.Next() {
79
+		v := e.Value.(io.Writer)
80
+		if v == writer {
81
+			w.writers.Remove(e)
82
+			return
83
+		}
84
+	}
85
+}
86
+
87
+func (w *writeBroadcaster) Write(p []byte) (n int, err error) {
88
+	failed := []*list.Element{}
89
+	for e := w.writers.Front(); e != nil; e = e.Next() {
90
+		writer := e.Value.(io.Writer)
91
+		if n, err := writer.Write(p); err != nil || n != len(p) {
92
+			// On error, evict the writer
93
+			failed = append(failed, e)
94
+		}
95
+	}
96
+	// We cannot remove while iterating, so it has to be done in
97
+	// a separate step
98
+	for _, e := range failed {
99
+		w.writers.Remove(e)
100
+	}
101
+	return len(p), nil
102
+}
103
+
104
+func (w *writeBroadcaster) Close() error {
105
+	for e := w.writers.Front(); e != nil; e = e.Next() {
106
+		writer := e.Value.(io.WriteCloser)
107
+		writer.Close()
108
+	}
109
+	return nil
110
+}
111
+
112
+func newWriteBroadcaster() *writeBroadcaster {
113
+	return &writeBroadcaster{list.New()}
114
+}
0 115
new file mode 100644
... ...
@@ -0,0 +1,126 @@
0
+package docker
1
+
2
+import (
3
+	"bytes"
4
+	"errors"
5
+	"io"
6
+	"io/ioutil"
7
+	"testing"
8
+)
9
+
10
+func TestBufReader(t *testing.T) {
11
+	reader, writer := io.Pipe()
12
+	bufreader := newBufReader(reader)
13
+
14
+	// Write everything down to a Pipe
15
+	// Usually, a pipe should block but because of the buffered reader,
16
+	// the writes will go through
17
+	done := make(chan bool)
18
+	go func() {
19
+		writer.Write([]byte("hello world"))
20
+		writer.Close()
21
+		done <- true
22
+	}()
23
+
24
+	// Drain the reader *after* everything has been written, just to verify
25
+	// it is indeed buffering
26
+	<-done
27
+	output, err := ioutil.ReadAll(bufreader)
28
+	if err != nil {
29
+		t.Fatal(err)
30
+	}
31
+	if !bytes.Equal(output, []byte("hello world")) {
32
+		t.Error(string(output))
33
+	}
34
+}
35
+
36
+type dummyWriter struct {
37
+	buffer      bytes.Buffer
38
+	failOnWrite bool
39
+}
40
+
41
+func (dw *dummyWriter) Write(p []byte) (n int, err error) {
42
+	if dw.failOnWrite {
43
+		return 0, errors.New("Fake fail")
44
+	}
45
+	return dw.buffer.Write(p)
46
+}
47
+
48
+func (dw *dummyWriter) String() string {
49
+	return dw.buffer.String()
50
+}
51
+
52
+func (dw *dummyWriter) Close() error {
53
+	return nil
54
+}
55
+
56
+func TestWriteBroadcaster(t *testing.T) {
57
+	writer := newWriteBroadcaster()
58
+
59
+	// Test 1: Both bufferA and bufferB should contain "foo"
60
+	bufferA := &dummyWriter{}
61
+	writer.AddWriter(bufferA)
62
+	bufferB := &dummyWriter{}
63
+	writer.AddWriter(bufferB)
64
+	writer.Write([]byte("foo"))
65
+
66
+	if bufferA.String() != "foo" {
67
+		t.Errorf("Buffer contains %v", bufferA.String())
68
+	}
69
+
70
+	if bufferB.String() != "foo" {
71
+		t.Errorf("Buffer contains %v", bufferB.String())
72
+	}
73
+
74
+	// Test2: bufferA and bufferB should contain "foobar",
75
+	// while bufferC should only contain "bar"
76
+	bufferC := &dummyWriter{}
77
+	writer.AddWriter(bufferC)
78
+	writer.Write([]byte("bar"))
79
+
80
+	if bufferA.String() != "foobar" {
81
+		t.Errorf("Buffer contains %v", bufferA.String())
82
+	}
83
+
84
+	if bufferB.String() != "foobar" {
85
+		t.Errorf("Buffer contains %v", bufferB.String())
86
+	}
87
+
88
+	if bufferC.String() != "bar" {
89
+		t.Errorf("Buffer contains %v", bufferC.String())
90
+	}
91
+
92
+	// Test3: Test removal
93
+	writer.RemoveWriter(bufferB)
94
+	writer.Write([]byte("42"))
95
+	if bufferA.String() != "foobar42" {
96
+		t.Errorf("Buffer contains %v", bufferA.String())
97
+	}
98
+	if bufferB.String() != "foobar" {
99
+		t.Errorf("Buffer contains %v", bufferB.String())
100
+	}
101
+	if bufferC.String() != "bar42" {
102
+		t.Errorf("Buffer contains %v", bufferC.String())
103
+	}
104
+
105
+	// Test4: Test eviction on failure
106
+	bufferA.failOnWrite = true
107
+	writer.Write([]byte("fail"))
108
+	if bufferA.String() != "foobar42" {
109
+		t.Errorf("Buffer contains %v", bufferA.String())
110
+	}
111
+	if bufferC.String() != "bar42fail" {
112
+		t.Errorf("Buffer contains %v", bufferC.String())
113
+	}
114
+	// Even though we reset the flag, no more writes should go in there
115
+	bufferA.failOnWrite = false
116
+	writer.Write([]byte("test"))
117
+	if bufferA.String() != "foobar42" {
118
+		t.Errorf("Buffer contains %v", bufferA.String())
119
+	}
120
+	if bufferC.String() != "bar42failtest" {
121
+		t.Errorf("Buffer contains %v", bufferC.String())
122
+	}
123
+
124
+	writer.Close()
125
+}