Merge branch 'master' of ssh://github.com/dotcloud/docker
This commit is contained in:
commit
98f090ead8
8 changed files with 213 additions and 69 deletions
97
container.go
97
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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
26
docker.go
26
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 {
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
9
state.go
9
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()
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue