Selaa lähdekoodia

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

Solomon Hykes 12 vuotta sitten
vanhempi
commit
98f090ead8
8 muutettua tiedostoa jossa 213 lisäystä ja 69 poistoa
  1. 54 43
      container.go
  2. 56 0
      container_test.go
  3. 13 13
      docker.go
  4. 1 1
      docker_test.go
  5. 29 7
      filesystem.go
  6. 53 1
      filesystem_test.go
  7. 1 1
      lxc_template.go
  8. 6 3
      state.go

+ 54 - 43
container.go

@@ -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()

+ 56 - 0
container_test.go

@@ -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)
+	}
+}

+ 13 - 13
docker.go

@@ -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 {

+ 1 - 1
docker_test.go

@@ -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])
 	}
 

+ 29 - 7
filesystem.go

@@ -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 {

+ 53 - 1
filesystem_test.go

@@ -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))
+	}
+}

+ 1 - 1
lxc_template.go

@@ -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
 

+ 6 - 3
state.go

@@ -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()
 }