Merge branch 'master' of ssh://github.com/dotcloud/docker

This commit is contained in:
Solomon Hykes 2013-01-22 18:58:04 -08:00
commit 98f090ead8
8 changed files with 213 additions and 69 deletions

View file

@ -14,14 +14,17 @@ import (
)
type Container struct {
Name string
Id string
Root string
Created time.Time
Path string
Args []string
*Config
*Filesystem
*State
Config *Config
Filesystem *Filesystem
State *State
lxcConfigPath string
cmd *exec.Cmd
@ -34,10 +37,11 @@ type Config struct {
Ram int64
}
func createContainer(name string, root string, command string, args []string, layers []string, config *Config) (*Container, error) {
func createContainer(id string, root string, command string, args []string, layers []string, config *Config) (*Container, error) {
container := &Container{
Name: name,
Id: id,
Root: root,
Created: time.Now(),
Path: command,
Args: args,
Config: config,
@ -52,7 +56,6 @@ func createContainer(name string, root string, command string, args []string, la
if err := os.Mkdir(root, 0700); err != nil {
return nil, err
}
if err := container.save(); err != nil {
return nil, err
}
@ -63,32 +66,23 @@ func createContainer(name string, root string, command string, args []string, la
}
func loadContainer(containerPath string) (*Container, error) {
configPath := path.Join(containerPath, "config.json")
fi, err := os.Open(configPath)
data, err := ioutil.ReadFile(path.Join(containerPath, "config.json"))
if err != nil {
return nil, err
}
defer fi.Close()
enc := json.NewDecoder(fi)
container := &Container{}
if err := enc.Decode(container); err != nil {
var container *Container
if err := json.Unmarshal(data, 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)
func (container *Container) save() (err error) {
data, err := json.Marshal(container)
if err != nil {
return err
return
}
defer fo.Close()
enc := json.NewEncoder(fo)
if err := enc.Encode(container); err != nil {
return err
}
return nil
return ioutil.WriteFile(path.Join(container.Root, "config.json"), data, 0700)
}
func (container *Container) generateLXCConfig() error {
@ -110,7 +104,7 @@ func (container *Container) Start() error {
}
params := []string{
"-n", container.Name,
"-n", container.Id,
"-f", container.lxcConfigPath,
"--",
container.Path,
@ -125,21 +119,21 @@ func (container *Container) Start() error {
return err
}
container.State.setRunning(container.cmd.Process.Pid)
container.save()
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 {
if err := exec.Command("/usr/bin/lxc-wait", "-n", container.Id, "-s", "RUNNING|STOPPED").Run(); err != nil {
// lxc-wait might return an error if by the time we call it,
// the container we just started is already STOPPED.
// This is a rare race condition that happens for short living programs.
//
// A workaround is to discard lxc-wait errors if the container is not
// running anymore.
if !container.State.Running {
return nil
}
return errors.New("Container failed to start")
}
return errors.New("Container failed to start")
return nil
}
func (container *Container) Run() error {
@ -177,30 +171,37 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) {
}
func (container *Container) monitor() {
// Wait for the program to exit
container.cmd.Wait()
exitCode := container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
// Cleanup container
// Cleanup
container.stdout.Close()
container.stderr.Close()
if err := container.Filesystem.Umount(); err != nil {
log.Printf("%v: Failed to umount filesystem: %v", container.Name, err)
log.Printf("%v: Failed to umount filesystem: %v", container.Id, err)
}
// Report status back
container.State.setStopped(exitCode)
container.save()
}
func (container *Container) kill() error {
// This will cause the main container process to receive a SIGKILL
if err := exec.Command("/usr/bin/lxc-stop", "-n", container.Name).Run(); err != nil {
if err := exec.Command("/usr/bin/lxc-stop", "-n", container.Id).Run(); err != nil {
log.Printf("Failed to lxc-stop %v", container.Id)
return err
}
// Wait for the container to be actually stopped
if err := exec.Command("/usr/bin/lxc-wait", "-n", container.Name, "-s", "STOPPED").Run(); err != nil {
return err
}
container.Wait()
// Make sure the underlying LXC thinks it's stopped too
// LXC Issue: lxc-wait MIGHT say that the container doesn't exist
// That's probably because it was destroyed and it cannot find it anymore
// We are going to ignore lxc-wait's error
exec.Command("/usr/bin/lxc-wait", "-n", container.Id, "-s", "STOPPED").Run()
return nil
}
@ -217,13 +218,13 @@ func (container *Container) Stop() error {
}
// 1. Send a SIGTERM
if err := exec.Command("/usr/bin/lxc-kill", "-n", container.Name, "15").Run(); err != nil {
if err := exec.Command("/usr/bin/lxc-kill", "-n", container.Id, "15").Run(); err != nil {
return err
}
// 2. Wait for the process to exit on its own
if err := container.WaitTimeout(10 * time.Second); err != nil {
log.Printf("Container %v failed to exit within 10 seconds of SIGTERM", container.Name)
log.Printf("Container %v failed to exit within 10 seconds of SIGTERM", container.Id)
}
// 3. Force kill
@ -233,6 +234,16 @@ func (container *Container) Stop() error {
return nil
}
func (container *Container) Restart() error {
if err := container.Stop(); err != nil {
return err
}
if err := container.Start(); err != nil {
return err
}
return nil
}
func (container *Container) Wait() {
for container.State.Running {
container.State.wait()

View file

@ -184,3 +184,59 @@ func TestExitCode(t *testing.T) {
t.Errorf("Unexpected exit code %v", falseContainer.State.ExitCode)
}
}
func TestMultipleContainers(t *testing.T) {
docker, err := newTestDocker()
if err != nil {
t.Fatal(err)
}
container1, err := docker.Create(
"container1",
"cat",
[]string{"/dev/zero"},
[]string{"/var/lib/docker/images/ubuntu"},
&Config{},
)
if err != nil {
t.Fatal(err)
}
defer docker.Destroy(container1)
container2, err := docker.Create(
"container2",
"cat",
[]string{"/dev/zero"},
[]string{"/var/lib/docker/images/ubuntu"},
&Config{},
)
if err != nil {
t.Fatal(err)
}
defer docker.Destroy(container2)
// Start both containers
if err := container1.Start(); err != nil {
t.Fatal(err)
}
if err := container2.Start(); err != nil {
t.Fatal(err)
}
// If we are here, both containers should be running
if !container1.State.Running {
t.Fatal("Container not running")
}
if !container2.State.Running {
t.Fatal("Container not running")
}
// Kill them
if err := container1.Kill(); err != nil {
t.Fatal(err)
}
if err := container2.Kill(); err != nil {
t.Fatal(err)
}
}

View file

@ -22,34 +22,34 @@ func (docker *Docker) List() []*Container {
return containers
}
func (docker *Docker) getContainerElement(name string) *list.Element {
func (docker *Docker) getContainerElement(id string) *list.Element {
for e := docker.containers.Front(); e != nil; e = e.Next() {
container := e.Value.(*Container)
if container.Name == name {
if container.Id == id {
return e
}
}
return nil
}
func (docker *Docker) Get(name string) *Container {
e := docker.getContainerElement(name)
func (docker *Docker) Get(id string) *Container {
e := docker.getContainerElement(id)
if e == nil {
return nil
}
return e.Value.(*Container)
}
func (docker *Docker) Exists(name string) bool {
return docker.Get(name) != nil
func (docker *Docker) Exists(id string) bool {
return docker.Get(id) != nil
}
func (docker *Docker) Create(name string, command string, args []string, layers []string, config *Config) (*Container, error) {
if docker.Exists(name) {
return nil, fmt.Errorf("Container %v already exists", name)
func (docker *Docker) Create(id string, command string, args []string, layers []string, config *Config) (*Container, error) {
if docker.Exists(id) {
return nil, fmt.Errorf("Container %v already exists", id)
}
root := path.Join(docker.repository, name)
container, err := createContainer(name, root, command, args, layers, config)
root := path.Join(docker.repository, id)
container, err := createContainer(id, root, command, args, layers, config)
if err != nil {
return nil, err
}
@ -58,9 +58,9 @@ func (docker *Docker) Create(name string, command string, args []string, layers
}
func (docker *Docker) Destroy(container *Container) error {
element := docker.getContainerElement(container.Name)
element := docker.getContainerElement(container.Id)
if element == nil {
return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Name)
return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Id)
}
if err := container.Stop(); err != nil {

View file

@ -51,7 +51,7 @@ func TestCreate(t *testing.T) {
}
// Make sure the container List() returns is the right one
if docker.List()[0].Name != "test_create" {
if docker.List()[0].Id != "test_create" {
t.Errorf("Unexpected container %v returned by List", docker.List()[0])
}

View file

@ -1,9 +1,10 @@
package docker
import (
"errors"
"fmt"
"os"
"os/exec"
"syscall"
)
type Filesystem struct {
@ -23,6 +24,9 @@ func (fs *Filesystem) createMountPoints() error {
}
func (fs *Filesystem) Mount() error {
if fs.IsMounted() {
return errors.New("Mount: Filesystem already mounted")
}
if err := fs.createMountPoints(); err != nil {
return err
}
@ -32,15 +36,33 @@ func (fs *Filesystem) Mount() error {
roBranches += fmt.Sprintf("%v=ro:", layer)
}
branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
cmd := exec.Command("mount", "-t", "aufs", "-o", branches, "none", fs.RootFS)
if err := cmd.Run(); err != nil {
return err
}
return nil
return syscall.Mount("none", fs.RootFS, "aufs", 0, branches)
}
func (fs *Filesystem) Umount() error {
return exec.Command("umount", fs.RootFS).Run()
if !fs.IsMounted() {
return errors.New("Umount: Filesystem not mounted")
}
return syscall.Unmount(fs.RootFS, 0)
}
func (fs *Filesystem) IsMounted() bool {
f, err := os.Open(fs.RootFS)
if err != nil {
if os.IsNotExist(err) {
return false
}
panic(err)
}
list, err := f.Readdirnames(1)
f.Close()
if err != nil {
return false
}
if len(list) > 0 {
return true
}
return false
}
func newFilesystem(rootfs string, rwpath string, layers []string) *Filesystem {

View file

@ -1,7 +1,10 @@
package docker
import (
"bytes"
"io/ioutil"
"os"
"path"
"testing"
)
@ -15,7 +18,7 @@ func TestFilesystem(t *testing.T) {
t.Fatal(err)
}
filesystem := newFilesystem(rootfs, rwpath, []string{"/var/lib/docker/images/ubuntu", "/var/lib/docker/images/test"})
filesystem := newFilesystem(rootfs, rwpath, []string{"/var/lib/docker/images/ubuntu"})
if err := filesystem.Umount(); err == nil {
t.Errorf("Umount succeeded even though the filesystem was not mounted")
@ -25,6 +28,10 @@ func TestFilesystem(t *testing.T) {
t.Fatal(err)
}
if err := filesystem.Mount(); err == nil {
t.Errorf("Double mount succeeded")
}
if err := filesystem.Umount(); err != nil {
t.Fatal(err)
}
@ -33,3 +40,48 @@ func TestFilesystem(t *testing.T) {
t.Errorf("Umount succeeded even though the filesystem was already umounted")
}
}
func TestFilesystemMultiLayer(t *testing.T) {
// Create a fake layer
fakeLayer, err := ioutil.TempDir("", "docker-layer")
if err != nil {
t.Fatal(err)
}
data := []byte("hello world")
if err := ioutil.WriteFile(path.Join(fakeLayer, "test_file"), data, 0700); err != nil {
t.Fatal(err)
}
// Create the layered filesystem and add our fake layer on top
rootfs, err := ioutil.TempDir("", "docker-test-root")
if err != nil {
t.Fatal(err)
}
rwpath, err := ioutil.TempDir("", "docker-test-rw")
if err != nil {
t.Fatal(err)
}
filesystem := newFilesystem(rootfs, rwpath, []string{"/var/lib/docker/images/ubuntu", fakeLayer})
// Mount it
if err := filesystem.Mount(); err != nil {
t.Fatal(err)
}
defer func() {
if err := filesystem.Umount(); err != nil {
t.Fatal(err)
}
}()
// Check to see whether we can access our fake layer
if _, err := os.Stat(path.Join(rootfs, "test_file")); err != nil {
t.Fatal(err)
}
fsdata, err := ioutil.ReadFile(path.Join(rootfs, "test_file"))
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(data, fsdata) {
t.Error(string(fsdata))
}
}

View file

@ -9,7 +9,7 @@ const LxcTemplate = `
{{if .Config.Hostname}}
lxc.utsname = {{.Config.Hostname}}
{{else}}
lxc.utsname = {{.Name}}
lxc.utsname = {{.Id}}
{{end}}
#lxc.aa_profile = unconfined

View file

@ -2,12 +2,14 @@ package docker
import (
"sync"
"time"
)
type State struct {
Running bool
Pid int
ExitCode int
Running bool
Pid int
ExitCode int
StartedAt time.Time
stateChangeLock *sync.Mutex
stateChangeCond *sync.Cond
@ -25,6 +27,7 @@ func (s *State) setRunning(pid int) {
s.Running = true
s.ExitCode = 0
s.Pid = pid
s.StartedAt = time.Now()
s.broadcast()
}