From d8bc912238dfa75dff0902b9d97133e200807bea Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Mon, 28 Jan 2013 11:51:41 -0800 Subject: [PATCH 1/5] filesystem: Added IsMounted() unit tests --- filesystem_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/filesystem_test.go b/filesystem_test.go index b4deb02b3..fb57344ed 100644 --- a/filesystem_test.go +++ b/filesystem_test.go @@ -27,21 +27,41 @@ func TestFilesystem(t *testing.T) { t.Errorf("Umount succeeded even though the filesystem was not mounted") } + if filesystem.IsMounted() { + t.Fatal("Filesystem should not be mounted") + } + if err := filesystem.Mount(); err != nil { t.Fatal(err) } + if !filesystem.IsMounted() { + t.Fatal("Filesystem should be mounted") + } + if err := filesystem.Mount(); err == nil { t.Errorf("Double mount succeeded") } + if !filesystem.IsMounted() { + t.Fatal("Filesystem should be mounted") + } + if err := filesystem.Umount(); err != nil { t.Fatal(err) } + if filesystem.IsMounted() { + t.Fatal("Filesystem should not be mounted") + } + if err := filesystem.Umount(); err == nil { t.Errorf("Umount succeeded even though the filesystem was already umounted") } + + if filesystem.IsMounted() { + t.Fatal("Filesystem should not be mounted") + } } func TestFilesystemMultiLayer(t *testing.T) { From a2d7dd1a1908fc3dc6ac5648b5ea12eaecb19b1b Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Mon, 28 Jan 2013 11:51:51 -0800 Subject: [PATCH 2/5] Added sequential and parallel benchmarks in order to stress test docker (create, run, destroy workflow) --- container_test.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/container_test.go b/container_test.go index 6eedc6c65..161b32d76 100644 --- a/container_test.go +++ b/container_test.go @@ -1,7 +1,9 @@ package docker import ( + "fmt" "testing" + "time" ) func TestStart(t *testing.T) { @@ -240,3 +242,87 @@ func TestMultipleContainers(t *testing.T) { t.Fatal(err) } } + +func BenchmarkRunSequencial(b *testing.B) { + docker, err := newTestDocker() + if err != nil { + b.Fatal(err) + } + for i := 0; i < b.N; i++ { + container, err := docker.Create( + fmt.Sprintf("bench_%v", i), + "echo", + []string{"-n", "foo"}, + []string{"/var/lib/docker/images/ubuntu"}, + &Config{}, + ) + if err != nil { + b.Fatal(err) + } + defer docker.Destroy(container) + output, err := container.Output() + if err != nil { + b.Fatal(err) + } + if string(output) != "foo" { + b.Fatalf("Unexecpted output: %v", string(output)) + } + if err := docker.Destroy(container); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkRunParallel(b *testing.B) { + docker, err := newTestDocker() + if err != nil { + b.Fatal(err) + } + + var tasks []chan error + + for i := 0; i < b.N; i++ { + complete := make(chan error) + tasks = append(tasks, complete) + go func(i int, complete chan error) { + container, err := docker.Create( + fmt.Sprintf("bench_%v", i), + "echo", + []string{"-n", "foo"}, + []string{"/var/lib/docker/images/ubuntu"}, + &Config{}, + ) + if err != nil { + complete <- err + return + } + defer docker.Destroy(container) + if err := container.Start(); err != nil { + complete <- err + return + } + if err := container.WaitTimeout(15 * time.Second); err != nil { + complete <- err + return + } + // if string(output) != "foo" { + // complete <- fmt.Errorf("Unexecpted output: %v", string(output)) + // } + if err := docker.Destroy(container); err != nil { + complete <- err + return + } + complete <- nil + }(i, complete) + } + var errors []error + for _, task := range tasks { + err := <-task + if err != nil { + errors = append(errors, err) + } + } + if len(errors) > 0 { + b.Fatal(errors) + } +} From 174f25909c525015cb84f1b82376091a1ac9c118 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Mon, 28 Jan 2013 11:53:58 -0800 Subject: [PATCH 3/5] Filesystem: Re-implemented Umount() and IsMounted() to work around AUFS issues. Umount() will now attempt to remove the mntpoint after umounting. It will keep retrying for some time until the mntpoint is deleted. --- filesystem.go | 45 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/filesystem.go b/filesystem.go index 41dacd5fd..3a16bd698 100644 --- a/filesystem.go +++ b/filesystem.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" "syscall" + "time" ) type Filesystem struct { @@ -38,33 +39,57 @@ func (fs *Filesystem) Mount() error { roBranches += fmt.Sprintf("%v=ro:", layer) } branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches) - return syscall.Mount("none", fs.RootFS, "aufs", 0, branches) + if err := syscall.Mount("none", fs.RootFS, "aufs", 0, branches); err != nil { + return err + } + if !fs.IsMounted() { + return errors.New("Mount failed") + } + return nil } func (fs *Filesystem) Umount() error { if !fs.IsMounted() { return errors.New("Umount: Filesystem not mounted") } - return syscall.Unmount(fs.RootFS, 0) + if err := syscall.Unmount(fs.RootFS, 0); err != nil { + return err + } + if fs.IsMounted() { + return fmt.Errorf("Umount: Filesystem still mounted after calling umount(%v)", fs.RootFS) + } + for retries := 0; retries < 1000; retries++ { + err := os.Remove(fs.RootFS) + if err == nil { + // rm mntpoint succeeded + return nil + } + if os.IsNotExist(err) { + // mntpoint doesn't exist anymore. Success. + return nil + } + // fmt.Printf("(%v) Remove %v returned: %v\n", retries, fs.RootFS, err) + time.Sleep(10 * time.Millisecond) + } + return fmt.Errorf("Umount: Failed to umount %v", fs.RootFS) } func (fs *Filesystem) IsMounted() bool { - f, err := os.Open(fs.RootFS) + mntpoint, err := os.Stat(fs.RootFS) if err != nil { if os.IsNotExist(err) { return false } panic(err) } - list, err := f.Readdirnames(1) - f.Close() + parent, err := os.Stat(filepath.Join(fs.RootFS, "..")) if err != nil { - return false + panic(err) } - if len(list) > 0 { - return true - } - return false + + mntpointSt := mntpoint.Sys().(*syscall.Stat_t) + parentSt := parent.Sys().(*syscall.Stat_t) + return mntpointSt.Dev != parentSt.Dev } type ChangeType int From fb40a7880494afffe384ac955f5f4e3a4971c676 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Mon, 28 Jan 2013 11:58:59 -0800 Subject: [PATCH 4/5] docker: Make sure to umount the container if it's still mounted at destruction --- docker.go | 8 ++++++-- filesystem.go | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docker.go b/docker.go index 8a79e989a..7f9442306 100644 --- a/docker.go +++ b/docker.go @@ -67,10 +67,14 @@ func (docker *Docker) Destroy(container *Container) error { if err := container.Stop(); err != nil { return err } + if container.Filesystem.IsMounted() { + if err := container.Filesystem.Umount(); err != nil { + log.Printf("Unable to umount container %v: %v", container.Id, err) + } + } if err := os.RemoveAll(container.Root); err != nil { - return err + log.Printf("Unable to remove filesystem for %v: %v", container.Id, err) } - docker.containers.Remove(element) return nil } diff --git a/filesystem.go b/filesystem.go index 3a16bd698..1e4679fc0 100644 --- a/filesystem.go +++ b/filesystem.go @@ -58,6 +58,8 @@ func (fs *Filesystem) Umount() error { if fs.IsMounted() { return fmt.Errorf("Umount: Filesystem still mounted after calling umount(%v)", fs.RootFS) } + // Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint + // for some time. We'll just keep retrying until it succeeds. for retries := 0; retries < 1000; retries++ { err := os.Remove(fs.RootFS) if err == nil { From d802a31be538d7c90d3f33474595dfdb77b7e50e Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Mon, 28 Jan 2013 12:11:30 -0800 Subject: [PATCH 5/5] Fixed broken unit test after merge --- container.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/container.go b/container.go index 1c4b58fd3..fc73f1346 100644 --- a/container.go +++ b/container.go @@ -59,10 +59,6 @@ func createContainer(id string, root string, command string, args []string, laye stdoutLog: new(bytes.Buffer), stderrLog: new(bytes.Buffer), } - if err := container.Filesystem.createMountPoints(); err != nil { - return nil, err - } - container.stdout.AddWriter(NopWriteCloser(container.stdoutLog)) container.stderr.AddWriter(NopWriteCloser(container.stderrLog))