-
Notifications
You must be signed in to change notification settings - Fork 18.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a27b4b8
Showing
10 changed files
with
1,146 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
package docker | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"io" | ||
"io/ioutil" | ||
"os" | ||
"os/exec" | ||
"path" | ||
"syscall" | ||
) | ||
|
||
type Container struct { | ||
Name string | ||
Root string | ||
Path string | ||
Args []string | ||
|
||
*Config | ||
*Filesystem | ||
*State | ||
|
||
lxcConfigPath string | ||
cmd *exec.Cmd | ||
stdout *writeBroadcaster | ||
stderr *writeBroadcaster | ||
} | ||
|
||
type Config struct { | ||
Hostname string | ||
Ram int64 | ||
} | ||
|
||
func createContainer(name string, root string, command string, args []string, layers []string, config *Config) (*Container, error) { | ||
container := &Container{ | ||
Name: name, | ||
Root: root, | ||
Path: command, | ||
Args: args, | ||
Config: config, | ||
Filesystem: newFilesystem(path.Join(root, "rootfs"), path.Join(root, "rw"), layers), | ||
State: newState(), | ||
|
||
lxcConfigPath: path.Join(root, "config.lxc"), | ||
stdout: newWriteBroadcaster(), | ||
stderr: newWriteBroadcaster(), | ||
} | ||
|
||
if err := os.Mkdir(root, 0700); err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := container.save(); err != nil { | ||
return nil, err | ||
} | ||
if err := container.generateLXCConfig(); err != nil { | ||
return nil, err | ||
} | ||
return container, nil | ||
} | ||
|
||
func loadContainer(containerPath string) (*Container, error) { | ||
configPath := path.Join(containerPath, "config.json") | ||
fi, err := os.Open(configPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer fi.Close() | ||
enc := json.NewDecoder(fi) | ||
container := &Container{} | ||
if err := enc.Decode(container); err != nil { | ||
return nil, err | ||
} | ||
return container, nil | ||
} | ||
|
||
func (container *Container) save() error { | ||
configPath := path.Join(container.Root, "config.json") | ||
fo, err := os.Create(configPath) | ||
if err != nil { | ||
return err | ||
} | ||
defer fo.Close() | ||
enc := json.NewEncoder(fo) | ||
if err := enc.Encode(container); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func (container *Container) generateLXCConfig() error { | ||
fo, err := os.Create(container.lxcConfigPath) | ||
if err != nil { | ||
return err | ||
} | ||
defer fo.Close() | ||
|
||
if err := LxcTemplateCompiled.Execute(fo, container); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func (container *Container) Start() error { | ||
if err := container.Filesystem.Mount(); err != nil { | ||
return err | ||
} | ||
|
||
params := []string{ | ||
"-n", container.Name, | ||
"-f", container.lxcConfigPath, | ||
"--", | ||
container.Path, | ||
} | ||
params = append(params, container.Args...) | ||
|
||
container.cmd = exec.Command("/usr/bin/lxc-start", params...) | ||
container.cmd.Stdout = container.stdout | ||
container.cmd.Stderr = container.stderr | ||
|
||
if err := container.cmd.Start(); err != nil { | ||
return err | ||
} | ||
container.State.setRunning(container.cmd.Process.Pid) | ||
go container.monitor() | ||
|
||
// Wait until we are out of the STARTING state before returning | ||
// | ||
// Even though lxc-wait blocks until the container reaches a given state, | ||
// sometimes it returns an error code, which is why we have to retry. | ||
// | ||
// This is a rare race condition that happens for short lived programs | ||
for retries := 0; retries < 3; retries++ { | ||
err := exec.Command("/usr/bin/lxc-wait", "-n", container.Name, "-s", "RUNNING|STOPPED").Run() | ||
if err == nil { | ||
return nil | ||
} | ||
} | ||
return errors.New("Container failed to start") | ||
} | ||
|
||
func (container *Container) Run() error { | ||
if err := container.Start(); err != nil { | ||
return err | ||
} | ||
container.Wait() | ||
return nil | ||
} | ||
|
||
func (container *Container) Output() (output []byte, err error) { | ||
pipe, err := container.StdoutPipe() | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer pipe.Close() | ||
if err := container.Start(); err != nil { | ||
return nil, err | ||
} | ||
output, err = ioutil.ReadAll(pipe) | ||
container.Wait() | ||
return output, err | ||
} | ||
|
||
func (container *Container) StdoutPipe() (io.ReadCloser, error) { | ||
reader, writer := io.Pipe() | ||
container.stdout.AddWriter(writer) | ||
return newBufReader(reader), nil | ||
} | ||
|
||
func (container *Container) StderrPipe() (io.ReadCloser, error) { | ||
reader, writer := io.Pipe() | ||
container.stderr.AddWriter(writer) | ||
return newBufReader(reader), nil | ||
} | ||
|
||
func (container *Container) monitor() { | ||
container.cmd.Wait() | ||
container.stdout.Close() | ||
container.stderr.Close() | ||
container.State.setStopped(container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()) | ||
} | ||
|
||
func (container *Container) Stop() error { | ||
if container.State.Running { | ||
if err := exec.Command("/usr/bin/lxc-stop", "-n", container.Name).Run(); err != nil { | ||
return err | ||
} | ||
//FIXME: We should lxc-wait for the container to stop | ||
} | ||
|
||
if err := container.Filesystem.Umount(); err != nil { | ||
// FIXME: Do not abort, probably already umounted? | ||
return nil | ||
} | ||
return nil | ||
} | ||
|
||
func (container *Container) Wait() { | ||
for container.State.Running { | ||
container.State.wait() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
package docker | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestStart(t *testing.T) { | ||
docker, err := newTestDocker() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
container, err := docker.Create( | ||
"start_test", | ||
"ls", | ||
[]string{"-al"}, | ||
[]string{"/var/lib/docker/images/ubuntu"}, | ||
&Config{ | ||
Ram: 33554432, | ||
}, | ||
) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer docker.Destroy(container) | ||
|
||
if container.State.Running { | ||
t.Errorf("Container shouldn't be running") | ||
} | ||
if err := container.Start(); err != nil { | ||
t.Fatal(err) | ||
} | ||
container.Wait() | ||
if container.State.Running { | ||
t.Errorf("Container shouldn't be running") | ||
} | ||
// We should be able to call Wait again | ||
container.Wait() | ||
if container.State.Running { | ||
t.Errorf("Container shouldn't be running") | ||
} | ||
} | ||
|
||
func TestRun(t *testing.T) { | ||
docker, err := newTestDocker() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
container, err := docker.Create( | ||
"run_test", | ||
"ls", | ||
[]string{"-al"}, | ||
[]string{"/var/lib/docker/images/ubuntu"}, | ||
&Config{ | ||
Ram: 33554432, | ||
}, | ||
) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer docker.Destroy(container) | ||
|
||
if container.State.Running { | ||
t.Errorf("Container shouldn't be running") | ||
} | ||
if err := container.Run(); err != nil { | ||
t.Fatal(err) | ||
} | ||
if container.State.Running { | ||
t.Errorf("Container shouldn't be running") | ||
} | ||
} | ||
|
||
func TestOutput(t *testing.T) { | ||
docker, err := newTestDocker() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
container, err := docker.Create( | ||
"output_test", | ||
"echo", | ||
[]string{"-n", "foobar"}, | ||
[]string{"/var/lib/docker/images/ubuntu"}, | ||
&Config{}, | ||
) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer docker.Destroy(container) | ||
|
||
pipe, err := container.StdoutPipe() | ||
defer pipe.Close() | ||
output, err := container.Output() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if string(output) != "foobar" { | ||
t.Error(string(output)) | ||
} | ||
} | ||
|
||
func TestStop(t *testing.T) { | ||
docker, err := newTestDocker() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
container, err := docker.Create( | ||
"stop_test", | ||
"sleep", | ||
[]string{"300"}, | ||
[]string{"/var/lib/docker/images/ubuntu"}, | ||
&Config{}, | ||
) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer docker.Destroy(container) | ||
|
||
if container.State.Running { | ||
t.Errorf("Container shouldn't be running") | ||
} | ||
if err := container.Start(); err != nil { | ||
t.Fatal(err) | ||
} | ||
if !container.State.Running { | ||
t.Errorf("Container should be running") | ||
} | ||
if err := container.Stop(); err != nil { | ||
t.Fatal(err) | ||
} | ||
if container.State.Running { | ||
t.Errorf("Container shouldn't be running") | ||
} | ||
container.Wait() | ||
if container.State.Running { | ||
t.Errorf("Container shouldn't be running") | ||
} | ||
// Try stopping twice | ||
if err := container.Stop(); err != nil { | ||
t.Fatal(err) | ||
} | ||
} | ||
|
||
func TestExitCode(t *testing.T) { | ||
docker, err := newTestDocker() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
trueContainer, err := docker.Create( | ||
"exit_test_1", | ||
"/bin/true", | ||
[]string{""}, | ||
[]string{"/var/lib/docker/images/ubuntu"}, | ||
&Config{}, | ||
) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer docker.Destroy(trueContainer) | ||
if err := trueContainer.Run(); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
falseContainer, err := docker.Create( | ||
"exit_test_2", | ||
"/bin/false", | ||
[]string{""}, | ||
[]string{"/var/lib/docker/images/ubuntu"}, | ||
&Config{}, | ||
) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer docker.Destroy(falseContainer) | ||
if err := falseContainer.Run(); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if trueContainer.State.ExitCode != 0 { | ||
t.Errorf("Unexpected exit code %v", trueContainer.State.ExitCode) | ||
} | ||
|
||
if falseContainer.State.ExitCode != 1 { | ||
t.Errorf("Unexpected exit code %v", falseContainer.State.ExitCode) | ||
} | ||
} |
Oops, something went wrong.
a27b4b8
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is where it all began. What a glorious history.