From 8ff2f8602fdb820a1fcacbc582c94c9bd5d98741 Mon Sep 17 00:00:00 2001 From: John Costa Date: Thu, 28 Mar 2013 16:32:31 -0400 Subject: [PATCH 01/26] move documentation changes to reStructuredText docs under website. https://github.com/dotcloud/docker/issues/42 Upstream-commit: a5054184a172da0b9abd2470946bb15ab5fb0516 Component: engine --- .../sources/contributing/contributing.rst | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/components/engine/docs/sources/contributing/contributing.rst b/components/engine/docs/sources/contributing/contributing.rst index d7f0258439..689c4207ce 100644 --- a/components/engine/docs/sources/contributing/contributing.rst +++ b/components/engine/docs/sources/contributing/contributing.rst @@ -51,8 +51,26 @@ documenting your bug report or improvement proposal. If it does, it never hurts to add a quick "+1" or "I have this problem too". This will help prioritize the most common problems and requests. -Write tests +Conventions ~~~~~~~~~~~ -Golang has a great testing suite built in: use it! Take a look at -existing tests for inspiration. +Fork the repo and make changes on your fork in a feature branch: + +- If it's a bugfix branch, name it XXX-something where XXX is the number of the issue +- If it's a feature branch, create an enhancement issue to announce your intentions, and name it XXX-something where XXX is the number of the issue. + +Submit unit tests for your changes. Golang has a great testing suite built +in: use it! Take a look at existing tests for inspiration. Run the full test +suite against your change and the master. + +Submit any relevant updates or additions to documentation. + +Add clean code: + +- Universally formatted code promotes ease of writing, reading, and maintenance. We suggest using gofmt before committing your changes. There's a git pre-commit hook made for doing so. +- curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/master/fmt-check && chmod +x .git/hooks/pre-commit + +Pull requests descriptions should be as clear as possible and include a +referenced to all the issues that they address. + +Add your name to the AUTHORS file. From 4950bad7779f45208e8a2f4ea476243dfe5b88e5 Mon Sep 17 00:00:00 2001 From: John Costa Date: Thu, 28 Mar 2013 20:04:21 -0400 Subject: [PATCH 02/26] add contributing guidlines md file Upstream-commit: 760736b3f3f289c2dba013aaef385b1e7b555feb Component: engine --- components/engine/CONTRIBUTING.md | 69 +++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 components/engine/CONTRIBUTING.md diff --git a/components/engine/CONTRIBUTING.md b/components/engine/CONTRIBUTING.md new file mode 100644 index 0000000000..f956d37101 --- /dev/null +++ b/components/engine/CONTRIBUTING.md @@ -0,0 +1,69 @@ +# Contributing to Docker + +Want to hack on Docker? Awesome! There are instructions to get you +started on the website: http://docker.io/gettingstarted.html + +They are probably not perfect, please let us know if anything feels +wrong or incomplete. + +## Contribution guidelines + +### Pull requests are always welcome + +We are always thrilled to receive pull requests, and do our best to +process them as fast as possible. Not sure if that typo is worth a pull +request? Do it! We will appreciate it. + +If your pull request is not accepted on the first try, don't be +discouraged! If there's a problem with the implementation, hopefully you +received feedback on what to improve. + +We're trying very hard to keep Docker lean and focused. We don't want it +to do everything for everybody. This means that we might decide against +incorporating a new feature. However, there might be a way to implement +that feature *on top of* docker. + +### Discuss your design on the mailing list + +We recommend discussing your plans [on the mailing +list](https://groups.google.com/forum/?fromgroups#!forum/docker-club) +before starting to code - especially for more ambitious contributions. +This gives other contributors a chance to point you in the right +direction, give feedback on your design, and maybe point out if someone +else is working on the same thing. + +### Create issues... + +Any significant improvement should be documented as [a github +issue](https://github.com/dotcloud/docker/issues) before anybody +starts working on it. + +### ...but check for existing issues first! + +Please take a moment to check that an issue doesn't already exist +documenting your bug report or improvement proposal. If it does, it +never hurts to add a quick "+1" or "I have this problem too". This will +help prioritize the most common problems and requests. + +### Conventions + +Fork the repo and make changes on your fork in a feature branch: + +- If it's a bugfix branch, name it XXX-something where XXX is the number of the issue +- If it's a feature branch, create an enhancement issue to announce your intentions, and name it XXX-something where XXX is the number of the issue. + +Submit unit tests for your changes. Golang has a great testing suite built +in: use it! Take a look at existing tests for inspiration. Run the full test +suite against your change and the master. + +Submit any relevant updates or additions to documentation. + +Add clean code: + +- Universally formatted code promotes ease of writing, reading, and maintenance. We suggest using gofmt before committing your changes. There's a git pre-commit hook made for doing so. +- curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/master/fmt-check && chmod +x .git/hooks/pre-commit + +Pull requests descriptions should be as clear as possible and include a +referenced to all the issues that they address. + +Add your name to the AUTHORS file. From 3b20ce08e18710d6dc47a5f70439e2d63c132fc0 Mon Sep 17 00:00:00 2001 From: John Costa Date: Fri, 29 Mar 2013 07:06:58 -0400 Subject: [PATCH 03/26] remove dulicated text and correct contributing link Upstream-commit: bd3c6793a183b9b848e34312a0215cb9078f2240 Component: engine --- components/engine/README.md | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/components/engine/README.md b/components/engine/README.md index 7fcfa4c360..495bbe83a3 100644 --- a/components/engine/README.md +++ b/components/engine/README.md @@ -192,11 +192,10 @@ echo "Daemon received: $(docker logs $JOB)" Contributing to Docker ====================== -Want to hack on Docker? Awesome! There are instructions to get you started on the website: http://docker.io/documentation/contributing/contributing.html +Want to hack on Docker? Awesome! There are instructions to get you started on the website: http://docs.docker.io/en/latest/contributing/contributing/ They are probably not perfect, please let us know if anything feels wrong or incomplete. -### Pull requests are always welcome Note ---- @@ -206,26 +205,6 @@ Please find it under docs/sources/ and read more about it https://github.com/dot Please feel free to fix / update the documentation and send us pull requests. More tutorials are also welcome. -### Discuss your design on the mailing list - -We recommend discussing your plans [on the mailing list](https://groups.google.com/forum/?fromgroups#!forum/docker-club) before starting to code - especially for more ambitious contributions. This gives other contributors a chance to point -you in the right direction, give feedback on your design, and maybe point out if someone else is working on the same thing. - -### Create issues... - -Any significant improvement should be documented as [a github issue](https://github.com/dotcloud/docker/issues) before anybody starts working on it. - -### ...but check for existing issues first! - -Please take a moment to check that an issue doesn't already exist documenting your bug report or improvement proposal. -If it does, it never hurts to add a quick "+1" or "I have this problem too". This will help prioritize the most common problems and requests. - - -### Write tests - -Golang has a great testing suite built in: use it! Take a look at existing tests for inspiration. - - Setting up a dev environment ---------------------------- From 85a2b99bef742005db80da5f5505cb1002c3314e Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sun, 31 Mar 2013 13:04:41 -0700 Subject: [PATCH 04/26] Add a progress bar to docker pull Upstream-commit: b64dfdd8cd636c5138dbd700f60e41651bae3e15 Component: engine --- components/engine/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/registry.go b/components/engine/registry.go index 3e62ad96ee..e36348fdb4 100644 --- a/components/engine/registry.go +++ b/components/engine/registry.go @@ -135,7 +135,7 @@ func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *a if err != nil { return nil, nil, err } - return img, res.Body, nil + return img, ProgressReader(res.Body, int(res.ContentLength), stdout), nil } func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) error { From 19f7c2dca5a5dd7825a90d80dff460426925047b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sun, 31 Mar 2013 13:53:47 -0700 Subject: [PATCH 05/26] Add progress bar on docker push Upstream-commit: 1fc9405537a1c528a59356ec36189a61db146701 Component: engine --- components/engine/registry.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/components/engine/registry.go b/components/engine/registry.go index e36348fdb4..24df146f13 100644 --- a/components/engine/registry.go +++ b/components/engine/registry.go @@ -267,19 +267,21 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth // FIXME: Don't do this :D. Check the S3 requierement and implement chunks of 5MB // FIXME2: I won't stress it enough, DON'T DO THIS! very high priority layerData2, err := Tar(path.Join(graph.Root, img.Id, "layer"), Gzip) - layerData, err := Tar(path.Join(graph.Root, img.Id, "layer"), Gzip) - if err != nil { - return fmt.Errorf("Failed to generate layer archive: %s", err) - } - req3, err := http.NewRequest("PUT", url.String(), layerData) - if err != nil { - return err - } tmp, err := ioutil.ReadAll(layerData2) if err != nil { return err } - req3.ContentLength = int64(len(tmp)) + layerLength := len(tmp) + + layerData, err := Tar(path.Join(graph.Root, img.Id, "layer"), Gzip) + if err != nil { + return fmt.Errorf("Failed to generate layer archive: %s", err) + } + req3, err := http.NewRequest("PUT", url.String(), ProgressReader(layerData.(io.ReadCloser), layerLength, stdout)) + if err != nil { + return err + } + req3.ContentLength = int64(layerLength) req3.TransferEncoding = []string{"none"} res3, err := client.Do(req3) From 0d0aacb1463873d02c50cbb77c70f174550f0594 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sun, 31 Mar 2013 14:15:10 -0700 Subject: [PATCH 06/26] Add a check to avoid double start (resulting in dockerd to panic) and unit test for it Upstream-commit: d949e2804a35497bc041537734dfeff150ea7e21 Component: engine --- components/engine/container.go | 3 +++ components/engine/container_test.go | 34 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/components/engine/container.go b/components/engine/container.go index a03614c011..f49aa80d54 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -230,6 +230,9 @@ func (container *Container) start() error { } func (container *Container) Start() error { + if container.State.Running { + return fmt.Errorf("The container %s is already running.", container.Id) + } if err := container.EnsureMounted(); err != nil { return err } diff --git a/components/engine/container_test.go b/components/engine/container_test.go index fae1c3c19c..fdc7f5703a 100644 --- a/components/engine/container_test.go +++ b/components/engine/container_test.go @@ -231,6 +231,40 @@ func TestCommitRun(t *testing.T) { } } +func TestStart(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + container, err := runtime.Create( + &Config{ + Image: GetTestImage(runtime).Id, + Memory: 33554432, + Cmd: []string{"/bin/cat"}, + OpenStdin: true, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + if err := container.Start(); err != nil { + t.Fatal(err) + } + + // Give some time to the process to start + container.WaitTimeout(500 * time.Millisecond) + + if !container.State.Running { + t.Errorf("Container should be running") + } + if err := container.Start(); err == nil { + t.Fatalf("A running containter should be able to be started") + } +} + func TestRun(t *testing.T) { runtime, err := newTestRuntime() if err != nil { From 735e80ee9b94b452434739493d55a3a45e94c271 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sun, 31 Mar 2013 20:52:35 -0700 Subject: [PATCH 07/26] Change the commands unit tests in order to reflect the proper behaviour of CmdRun Upstream-commit: cfeed391d76105e4131a021d2005966d01eb4e68 Component: engine --- components/engine/commands_test.go | 102 +++++++++++------------------ 1 file changed, 37 insertions(+), 65 deletions(-) diff --git a/components/engine/commands_test.go b/components/engine/commands_test.go index 6c05bfe77d..5a9117eb12 100644 --- a/components/engine/commands_test.go +++ b/components/engine/commands_test.go @@ -24,7 +24,7 @@ func closeWrap(args ...io.Closer) error { return nil } -func setTimeout(t *testing.T, msg string, d time.Duration, f func(chan bool)) { +func setTimeout(t *testing.T, msg string, d time.Duration, f func()) { c := make(chan bool) // Make sure we are not too long @@ -32,9 +32,12 @@ func setTimeout(t *testing.T, msg string, d time.Duration, f func(chan bool)) { time.Sleep(d) c <- true }() - go f(c) - if timeout := <-c; timeout { - t.Fatalf("Timeout: %s", msg) + go func() { + f() + c <- false + }() + if <-c { + t.Fatal(msg) } } @@ -54,77 +57,46 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } -/* -// Test the behavior of a client disconnection. -// We expect a client disconnect to leave the stdin of the container open -// Therefore a process will keep his stdin open when a client disconnects -func TestReattachAfterDisconnect(t *testing.T) { +// Expected behaviour: the process dies when the client disconnects +func TestRunDisconnect(t *testing.T) { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) } defer nuke(runtime) - // FIXME: low down the timeout (after #230) - setTimeout(t, "TestReattachAfterDisconnect", 12*time.Second, func(timeout chan bool) { + srv := &Server{runtime: runtime} - srv := &Server{runtime: runtime} - - stdin, stdinPipe := io.Pipe() - stdout, stdoutPipe := io.Pipe() - c1 := make(chan struct{}) - go func() { - if err := srv.CmdRun(stdin, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat"); err == nil { - t.Fatal("CmdRun should generate a read/write on closed pipe error. No error found.") - } - close(c1) - }() + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + c1 := make(chan struct{}) + go func() { + if err := srv.CmdRun(stdin, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat"); err != nil { + t.Fatal(err) + } + close(c1) + }() + setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() { if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { t.Fatal(err) } - - // Close pipes (simulate disconnect) - if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { - t.Fatal(err) - } - - container := runtime.containers.Back().Value.(*Container) - - // Recreate the pipes - stdin, stdinPipe = io.Pipe() - stdout, stdoutPipe = io.Pipe() - - // Attach to it - c2 := make(chan struct{}) - go func() { - if err := srv.CmdAttach(stdin, stdoutPipe, container.Id); err != nil { - t.Fatal(err) - } - close(c2) - }() - - if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { - t.Fatal(err) - } - - // Close pipes - if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { - t.Fatal(err) - } - - // FIXME: when #230 will be finished, send SIGINT instead of SIGTERM - // we expect cat to stay alive so SIGTERM will have no effect - // and Stop will timeout - if err := container.Stop(); err != nil { - t.Fatal(err) - } - // Wait for run and attach to finish - <-c1 - <-c2 - - // Finished, no timeout - timeout <- false }) + + // Close pipes (simulate disconnect) + if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { + t.Fatal(err) + } + + // as the pipes are close, we expect the process to die, + // therefore CmdRun to unblock. Wait for CmdRun + setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() { + <-c1 + }) + + // Check the status of the container + container := runtime.containers.Back().Value.(*Container) + if container.State.Running { + t.Fatalf("/bin/cat is still running after closing stdin") + } } -*/ From 8a40ce58f3d901aa873ae5013f3c7c5e90cb32e1 Mon Sep 17 00:00:00 2001 From: Dominik Honnef Date: Sat, 30 Mar 2013 23:32:10 +0100 Subject: [PATCH 08/26] Make IP allocator lazy Instead of allocating all possible IPs in advance, generate them as needed. A loop will cycle through all possible IPs in sequential order, allocating them as needed and marking them as in use. Once the loop exhausts all IPs, it will wrap back to the beginning. IPs that are already in use will be skipped. When an IP is released, it will be cleared and be available for allocation again. Two decisions went into this design: 1) Minimize memory footprint by only allocating IPs that are actually in use 2) Minimize reuse of released IP addresses to avoid sending traffic to the wrong containers As a side effect, the functions for IP/Mask<->int conversion have been rewritten to never be able to fail in order to reduce the amount of error returns. Fixes gh-231 Upstream-commit: 6f9a67a7c7cb717ad1a575df3e4c0fd2ec8bc651 Component: engine --- components/engine/AUTHORS | 1 + components/engine/container.go | 9 +- components/engine/network.go | 169 ++++++++++++++++-------------- components/engine/network_test.go | 140 +++++++++++++++++++------ 4 files changed, 203 insertions(+), 116 deletions(-) diff --git a/components/engine/AUTHORS b/components/engine/AUTHORS index 80928e0e61..382a11b5b0 100644 --- a/components/engine/AUTHORS +++ b/components/engine/AUTHORS @@ -7,6 +7,7 @@ Caleb Spare Charles Hooper Daniel Mizyrycki Daniel Robinson +Dominik Honnef Don Spaulding ezbercih Frederick F. Kautz IV diff --git a/components/engine/container.go b/components/engine/container.go index f49aa80d54..c605833399 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -363,11 +363,10 @@ func (container *Container) allocateNetwork() error { return nil } -func (container *Container) releaseNetwork() error { - err := container.network.Release() +func (container *Container) releaseNetwork() { + container.network.Release() container.network = nil container.NetworkSettings = &NetworkSettings{} - return err } func (container *Container) monitor() { @@ -382,9 +381,7 @@ func (container *Container) monitor() { exitCode := container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() // Cleanup - if err := container.releaseNetwork(); err != nil { - log.Printf("%v: Failed to release network: %v", container.Id, err) - } + container.releaseNetwork() if container.Config.OpenStdin { if err := container.stdin.Close(); err != nil { Debugf("%s: Error close stdin: %s", container.Id, err) diff --git a/components/engine/network.go b/components/engine/network.go index dd2b3e8bc7..c050609d16 100644 --- a/components/engine/network.go +++ b/components/engine/network.go @@ -1,7 +1,6 @@ package docker import ( - "bytes" "encoding/binary" "errors" "fmt" @@ -30,40 +29,25 @@ func networkRange(network *net.IPNet) (net.IP, net.IP) { } // Converts a 4 bytes IP into a 32 bit integer -func ipToInt(ip net.IP) (int32, error) { - buf := bytes.NewBuffer(ip.To4()) - var n int32 - if err := binary.Read(buf, binary.BigEndian, &n); err != nil { - return 0, err - } - return n, nil +func ipToInt(ip net.IP) int32 { + return int32(binary.BigEndian.Uint32(ip.To4())) } // Converts 32 bit integer into a 4 bytes IP address -func intToIp(n int32) (net.IP, error) { - var buf bytes.Buffer - if err := binary.Write(&buf, binary.BigEndian, &n); err != nil { - return net.IP{}, err - } - ip := net.IPv4(0, 0, 0, 0).To4() - for i := 0; i < net.IPv4len; i++ { - ip[i] = buf.Bytes()[i] - } - return ip, nil +func intToIp(n int32) net.IP { + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, uint32(n)) + return net.IP(b) } // Given a netmask, calculates the number of available hosts -func networkSize(mask net.IPMask) (int32, error) { +func networkSize(mask net.IPMask) int32 { m := net.IPv4Mask(0, 0, 0, 0) for i := 0; i < net.IPv4len; i++ { m[i] = ^mask[i] } - buf := bytes.NewBuffer(m) - var n int32 - if err := binary.Read(buf, binary.BigEndian, &n); err != nil { - return 0, err - } - return n + 1, nil + + return int32(binary.BigEndian.Uint32(m)) + 1 } // Wrapper around the iptables command @@ -211,66 +195,97 @@ func newPortAllocator(start, end int) (*PortAllocator, error) { // IP allocator: Atomatically allocate and release networking ports type IPAllocator struct { - network *net.IPNet - queue chan (net.IP) + network *net.IPNet + queueAlloc chan allocatedIP + queueReleased chan net.IP + inUse map[int32]struct{} } -func (alloc *IPAllocator) populate() error { +type allocatedIP struct { + ip net.IP + err error +} + +func (alloc *IPAllocator) run() { firstIP, _ := networkRange(alloc.network) - size, err := networkSize(alloc.network.Mask) - if err != nil { - return err + ipNum := ipToInt(firstIP) + ownIP := ipToInt(alloc.network.IP) + size := networkSize(alloc.network.Mask) + + pos := int32(1) + max := size - 2 // -1 for the broadcast address, -1 for the gateway address + for { + var ( + newNum int32 + inUse bool + ) + + // Find first unused IP, give up after one whole round + for attempt := int32(0); attempt < max; attempt++ { + newNum = ipNum + pos + + pos = pos%max + 1 + + // The network's IP is never okay to use + if newNum == ownIP { + continue + } + + if _, inUse = alloc.inUse[newNum]; !inUse { + // We found an unused IP + break + } + } + + ip := allocatedIP{ip: intToIp(newNum)} + if inUse { + ip.err = errors.New("No unallocated IP available") + } + + select { + case alloc.queueAlloc <- ip: + alloc.inUse[newNum] = struct{}{} + case released := <-alloc.queueReleased: + r := ipToInt(released) + delete(alloc.inUse, r) + + if inUse { + // If we couldn't allocate a new IP, the released one + // will be the only free one now, so instantly use it + // next time + pos = r - ipNum + } else { + // Use same IP as last time + if pos == 1 { + pos = max + } else { + pos-- + } + } + } } - // The queue size should be the network size - 3 - // -1 for the network address, -1 for the broadcast address and - // -1 for the gateway address - alloc.queue = make(chan net.IP, size-3) - for i := int32(1); i < size-1; i++ { - ipNum, err := ipToInt(firstIP) - if err != nil { - return err - } - ip, err := intToIp(ipNum + int32(i)) - if err != nil { - return err - } - // Discard the network IP (that's the host IP address) - if ip.Equal(alloc.network.IP) { - continue - } - alloc.queue <- ip - } - return nil } func (alloc *IPAllocator) Acquire() (net.IP, error) { - select { - case ip := <-alloc.queue: - return ip, nil - default: - return net.IP{}, errors.New("No more IP addresses available") - } - return net.IP{}, nil + ip := <-alloc.queueAlloc + return ip.ip, ip.err } -func (alloc *IPAllocator) Release(ip net.IP) error { - select { - case alloc.queue <- ip: - return nil - default: - return errors.New("Too many IP addresses have been released") - } - return nil +func (alloc *IPAllocator) Release(ip net.IP) { + alloc.queueReleased <- ip } -func newIPAllocator(network *net.IPNet) (*IPAllocator, error) { +func newIPAllocator(network *net.IPNet) *IPAllocator { alloc := &IPAllocator{ - network: network, + network: network, + queueAlloc: make(chan allocatedIP), + queueReleased: make(chan net.IP), + inUse: make(map[int32]struct{}), } - if err := alloc.populate(); err != nil { - return nil, err - } - return alloc, nil + + go alloc.run() + + return alloc } // Network interface represents the networking stack of a container @@ -297,7 +312,7 @@ func (iface *NetworkInterface) AllocatePort(port int) (int, error) { } // Release: Network cleanup - release all resources -func (iface *NetworkInterface) Release() error { +func (iface *NetworkInterface) Release() { for _, port := range iface.extPorts { if err := iface.manager.portMapper.Unmap(port); err != nil { log.Printf("Unable to unmap port %v: %v", port, err) @@ -307,7 +322,8 @@ func (iface *NetworkInterface) Release() error { } } - return iface.manager.ipAllocator.Release(iface.IPNet.IP) + + iface.manager.ipAllocator.Release(iface.IPNet.IP) } // Network Manager manages a set of network interfaces @@ -342,10 +358,7 @@ func newNetworkManager(bridgeIface string) (*NetworkManager, error) { } network := addr.(*net.IPNet) - ipAllocator, err := newIPAllocator(network) - if err != nil { - return nil, err - } + ipAllocator := newIPAllocator(network) portAllocator, err := newPortAllocator(portRangeStart, portRangeEnd) if err != nil { diff --git a/components/engine/network_test.go b/components/engine/network_test.go index 53e79a13b0..a9d3cac454 100644 --- a/components/engine/network_test.go +++ b/components/engine/network_test.go @@ -28,8 +28,8 @@ func TestNetworkRange(t *testing.T) { if !last.Equal(net.ParseIP("192.168.0.255")) { t.Error(last.String()) } - if size, err := networkSize(network.Mask); err != nil || size != 256 { - t.Error(size, err) + if size := networkSize(network.Mask); size != 256 { + t.Error(size) } // Class A test @@ -41,8 +41,8 @@ func TestNetworkRange(t *testing.T) { if !last.Equal(net.ParseIP("10.255.255.255")) { t.Error(last.String()) } - if size, err := networkSize(network.Mask); err != nil || size != 16777216 { - t.Error(size, err) + if size := networkSize(network.Mask); size != 16777216 { + t.Error(size) } // Class A, random IP address @@ -64,8 +64,8 @@ func TestNetworkRange(t *testing.T) { if !last.Equal(net.ParseIP("10.1.2.3")) { t.Error(last.String()) } - if size, err := networkSize(network.Mask); err != nil || size != 1 { - t.Error(size, err) + if size := networkSize(network.Mask); size != 1 { + t.Error(size) } // 31bit mask @@ -77,8 +77,8 @@ func TestNetworkRange(t *testing.T) { if !last.Equal(net.ParseIP("10.1.2.3")) { t.Error(last.String()) } - if size, err := networkSize(network.Mask); err != nil || size != 2 { - t.Error(size, err) + if size := networkSize(network.Mask); size != 2 { + t.Error(size) } // 26bit mask @@ -90,54 +90,130 @@ func TestNetworkRange(t *testing.T) { if !last.Equal(net.ParseIP("10.1.2.63")) { t.Error(last.String()) } - if size, err := networkSize(network.Mask); err != nil || size != 64 { - t.Error(size, err) + if size := networkSize(network.Mask); size != 64 { + t.Error(size) } } func TestConversion(t *testing.T) { ip := net.ParseIP("127.0.0.1") - i, err := ipToInt(ip) - if err != nil { - t.Fatal(err) - } + i := ipToInt(ip) if i == 0 { t.Fatal("converted to zero") } - conv, err := intToIp(i) - if err != nil { - t.Fatal(err) - } + conv := intToIp(i) if !ip.Equal(conv) { t.Error(conv.String()) } } func TestIPAllocator(t *testing.T) { - gwIP, n, _ := net.ParseCIDR("127.0.0.1/29") - alloc, err := newIPAllocator(&net.IPNet{IP: gwIP, Mask: n.Mask}) - if err != nil { - t.Fatal(err) + expectedIPs := []net.IP{ + 0: net.IPv4(127, 0, 0, 2), + 1: net.IPv4(127, 0, 0, 3), + 2: net.IPv4(127, 0, 0, 4), + 3: net.IPv4(127, 0, 0, 5), + 4: net.IPv4(127, 0, 0, 6), } - var lastIP net.IP + + gwIP, n, _ := net.ParseCIDR("127.0.0.1/29") + alloc := newIPAllocator(&net.IPNet{IP: gwIP, Mask: n.Mask}) + // Pool after initialisation (f = free, u = used) + // 2(f) - 3(f) - 4(f) - 5(f) - 6(f) + // ↑ + + // Check that we get 5 IPs, from 127.0.0.2–127.0.0.6, in that + // order. for i := 0; i < 5; i++ { ip, err := alloc.Acquire() if err != nil { t.Fatal(err) } - lastIP = ip + + assertIPEquals(t, expectedIPs[i], ip) } - ip, err := alloc.Acquire() + // Before loop begin + // 2(f) - 3(f) - 4(f) - 5(f) - 6(f) + // ↑ + + // After i = 0 + // 2(u) - 3(f) - 4(f) - 5(f) - 6(f) + // ↑ + + // After i = 1 + // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) + // ↑ + + // After i = 2 + // 2(u) - 3(u) - 4(u) - 5(f) - 6(f) + // ↑ + + // After i = 3 + // 2(u) - 3(u) - 4(u) - 5(u) - 6(f) + // ↑ + + // After i = 4 + // 2(u) - 3(u) - 4(u) - 5(u) - 6(u) + // ↑ + + // Check that there are no more IPs + _, err := alloc.Acquire() if err == nil { t.Fatal("There shouldn't be any IP addresses at this point") } - // Release 1 IP - alloc.Release(lastIP) - ip, err = alloc.Acquire() - if err != nil { - t.Fatal(err) + + // Release some IPs in non-sequential order + alloc.Release(expectedIPs[3]) + // 2(u) - 3(u) - 4(u) - 5(f) - 6(u) + // ↑ + + alloc.Release(expectedIPs[2]) + // 2(u) - 3(u) - 4(f) - 5(f) - 6(u) + // ↑ + + alloc.Release(expectedIPs[4]) + // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) + // ↑ + + // Make sure that IPs are reused in sequential order, starting + // with the first released IP + newIPs := make([]net.IP, 3) + for i := 0; i < 3; i++ { + ip, err := alloc.Acquire() + if err != nil { + t.Fatal(err) + } + + newIPs[i] = ip } - if !ip.Equal(lastIP) { - t.Fatal(ip.String()) + // Before loop begin + // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) + // ↑ + + // After i = 0 + // 2(u) - 3(u) - 4(f) - 5(u) - 6(f) + // ↑ + + // After i = 1 + // 2(u) - 3(u) - 4(f) - 5(u) - 6(u) + // ↑ + + // After i = 2 + // 2(u) - 3(u) - 4(u) - 5(u) - 6(u) + // ↑ + + assertIPEquals(t, expectedIPs[3], newIPs[0]) + assertIPEquals(t, expectedIPs[4], newIPs[1]) + assertIPEquals(t, expectedIPs[2], newIPs[2]) + + _, err = alloc.Acquire() + if err == nil { + t.Fatal("There shouldn't be any IP addresses at this point") + } +} + +func assertIPEquals(t *testing.T, ip1, ip2 net.IP) { + if !ip1.Equal(ip2) { + t.Fatalf("Expected IP %s, got %s", ip1, ip2) } } From 2e91f0bb47d1b36511c0f4264743ed86d9e11a79 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sun, 31 Mar 2013 21:48:18 -0700 Subject: [PATCH 09/26] Add unit test for CmdAttach Upstream-commit: ad1e8a9b0f0e57327d71827c1161c21f48910e42 Component: engine --- components/engine/commands_test.go | 66 ++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/components/engine/commands_test.go b/components/engine/commands_test.go index 5a9117eb12..ebbe88c637 100644 --- a/components/engine/commands_test.go +++ b/components/engine/commands_test.go @@ -100,3 +100,69 @@ func TestRunDisconnect(t *testing.T) { t.Fatalf("/bin/cat is still running after closing stdin") } } + +// Expected behaviour, the process stays alive when the client disconnects +func TestAttachDisconnect(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := runtime.Create( + &Config{ + Image: GetTestImage(runtime).Id, + Memory: 33554432, + Cmd: []string{"/bin/cat"}, + OpenStdin: true, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + // Start the process + if err := container.Start(); err != nil { + t.Fatal(err) + } + + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + + // Attach to it + c1 := make(chan struct{}) + go func() { + if err := srv.CmdAttach(stdin, stdoutPipe, container.Id); err != nil { + t.Fatal(err) + } + close(c1) + }() + + setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { + if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { + t.Fatal(err) + } + }) + // Close pipes (client disconnects) + if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { + t.Fatal(err) + } + + // Wait for attach to finish, the client disconnected, therefore, Attach finished his job + setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() { + <-c1 + }) + // We closed stdin, expect /bin/cat to still be running + // Wait a little bit to make sure container.monitor() did his thing + err = container.WaitTimeout(500 * time.Millisecond) + if err == nil || !container.State.Running { + t.Fatalf("/bin/cat is not running after closing stdin") + } + + // Try to avoid the timeoout in destroy. Best effort, don't check error + cStdin, _ := container.StdinPipe() + cStdin.Close() +} From a591bf3f3dca8415b7299e85f733ff38d276d61e Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 31 Mar 2013 22:04:59 -0700 Subject: [PATCH 10/26] gofmt Upstream-commit: 54443c092caf9c1e11c47ea30400084d009af6e9 Component: engine --- components/engine/container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/container.go b/components/engine/container.go index c605833399..ee2bb1b3b1 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -363,7 +363,7 @@ func (container *Container) allocateNetwork() error { return nil } -func (container *Container) releaseNetwork() { +func (container *Container) releaseNetwork() { container.network.Release() container.network = nil container.NetworkSettings = &NetworkSettings{} From bd2e3195798c1191ca4132f8017d624dc32329f5 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 31 Mar 2013 22:05:14 -0700 Subject: [PATCH 11/26] Temporarily disable a broken test (waiting for @creack to fix it), and silence a warning which pollutes unit tests but is complicated to fix Upstream-commit: a52a28b60946463e8208b8cd5d737ba79f23a8b8 Component: engine --- components/engine/commands_test.go | 2 ++ components/engine/container.go | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/components/engine/commands_test.go b/components/engine/commands_test.go index 50f5131a5e..6c05bfe77d 100644 --- a/components/engine/commands_test.go +++ b/components/engine/commands_test.go @@ -54,6 +54,7 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } +/* // Test the behavior of a client disconnection. // We expect a client disconnect to leave the stdin of the container open // Therefore a process will keep his stdin open when a client disconnects @@ -126,3 +127,4 @@ func TestReattachAfterDisconnect(t *testing.T) { timeout <- false }) } +*/ diff --git a/components/engine/container.go b/components/engine/container.go index ee2bb1b3b1..502109ef96 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -422,7 +422,13 @@ func (container *Container) monitor() { // Report status back container.State.setStopped(exitCode) if err := container.ToDisk(); err != nil { - log.Printf("%s: Failed to dump configuration to the disk: %s", container.Id, err) + // FIXME: there is a race condition here which causes this to fail during the unit tests. + // If another goroutine was waiting for Wait() to return before removing the container's root + // from the filesystem... At this point it may already have done so. + // This is because State.setStopped() has already been called, and has caused Wait() + // to return. + // FIXME: why are we serializing running state to disk in the first place? + //log.Printf("%s: Failed to dump configuration to the disk: %s", container.Id, err) } } From 28343696b9e9d772321bc1eb65af9cb8e6e1a0e5 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 31 Mar 2013 22:11:55 -0700 Subject: [PATCH 12/26] Show shorthand image IDs for convenience. Shorthand IDs (or any non-conflicting prefix) can be used to lookup images Upstream-commit: 1632566ecb133fb0853aec6715c5c06a4a9e41da Component: engine --- components/engine/commands.go | 14 ++++++------ components/engine/container.go | 6 +---- components/engine/graph.go | 42 +++++++++++++++++++++++++++++----- components/engine/image.go | 4 ++++ components/engine/tags.go | 2 +- components/engine/utils.go | 12 ++++++++++ 6 files changed, 61 insertions(+), 19 deletions(-) diff --git a/components/engine/commands.go b/components/engine/commands.go index 4ccdba67d6..105f76be5e 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -372,7 +372,7 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str fmt.Fprintf(w, "ID\tCREATED\tCREATED BY\n") return image.WalkHistory(func(img *Image) error { fmt.Fprintf(w, "%s\t%s\t%s\n", - srv.runtime.repositories.ImageName(img.Id), + srv.runtime.repositories.ImageName(img.ShortId()), HumanDuration(time.Now().Sub(img.Created))+" ago", strings.Join(img.ContainerConfig.Cmd, " "), ) @@ -458,7 +458,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri return err } } - fmt.Fprintln(stdout, img.Id) + fmt.Fprintln(stdout, img.ShortId()) return nil } @@ -591,7 +591,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri for idx, field := range []string{ /* REPOSITORY */ name, /* TAG */ tag, - /* ID */ id, + /* ID */ TruncateId(id), /* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago", /* PARENT */ srv.runtime.repositories.ImageName(image.Parent), } { @@ -603,7 +603,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri } w.Write([]byte{'\n'}) } else { - stdout.Write([]byte(image.Id + "\n")) + stdout.Write([]byte(image.ShortId() + "\n")) } } } @@ -614,7 +614,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri for idx, field := range []string{ /* REPOSITORY */ "", /* TAG */ "", - /* ID */ id, + /* ID */ TruncateId(id), /* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago", /* PARENT */ srv.runtime.repositories.ImageName(image.Parent), } { @@ -626,7 +626,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri } w.Write([]byte{'\n'}) } else { - stdout.Write([]byte(image.Id + "\n")) + stdout.Write([]byte(image.ShortId() + "\n")) } } } @@ -700,7 +700,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri if err != nil { return err } - fmt.Fprintln(stdout, img.Id) + fmt.Fprintln(stdout, img.ShortId()) return nil } diff --git a/components/engine/container.go b/components/engine/container.go index 502109ef96..a7094bf5ec 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -566,11 +566,7 @@ func (container *Container) Unmount() error { // In case of a collision a lookup with Runtime.Get() will fail, and the caller // will need to use a langer prefix, or the full-length container Id. func (container *Container) ShortId() string { - shortLen := 12 - if len(container.Id) < shortLen { - shortLen = len(container.Id) - } - return container.Id[:shortLen] + return TruncateId(container.Id) } func (container *Container) logPath(name string) string { diff --git a/components/engine/graph.go b/components/engine/graph.go index 09bc1d5bed..ca126dc0e5 100644 --- a/components/engine/graph.go +++ b/components/engine/graph.go @@ -12,7 +12,8 @@ import ( // A Graph is a store for versioned filesystem images, and the relationship between them. type Graph struct { - Root string + Root string + idIndex *TruncIndex } // NewGraph instanciates a new graph at the given root path in the filesystem. @@ -26,9 +27,26 @@ func NewGraph(root string) (*Graph, error) { if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) { return nil, err } - return &Graph{ - Root: abspath, - }, nil + graph := &Graph{ + Root: abspath, + idIndex: NewTruncIndex(), + } + if err := graph.restore(); err != nil { + return nil, err + } + return graph, nil +} + +func (graph *Graph) restore() error { + dir, err := ioutil.ReadDir(graph.Root) + if err != nil { + return err + } + for _, v := range dir { + id := v.Name() + graph.idIndex.Add(id) + } + return nil } // FIXME: Implement error subclass instead of looking at the error text @@ -47,7 +65,11 @@ func (graph *Graph) Exists(id string) bool { } // Get returns the image with the given id, or an error if the image doesn't exist. -func (graph *Graph) Get(id string) (*Image, error) { +func (graph *Graph) Get(name string) (*Image, error) { + id, err := graph.idIndex.Get(name) + if err != nil { + return nil, err + } // FIXME: return nil when the image doesn't exist, instead of an error img, err := LoadImage(graph.imageRoot(id)) if err != nil { @@ -101,6 +123,7 @@ func (graph *Graph) Register(layerData Archive, img *Image) error { return err } img.graph = graph + graph.idIndex.Add(img.Id) return nil } @@ -143,8 +166,11 @@ func (graph *Graph) Delete(id string) error { if err != nil { return err } + graph.idIndex.Delete(id) err = os.Rename(graph.imageRoot(id), garbage.imageRoot(id)) if err != nil { + // FIXME: this introduces a race condition in Delete() if the image is already present + // in garbage. Let's store at random names in grabage instead. if isNotEmpty(err) { Debugf("The image %s is already present in garbage. Removing it.", id) if err = os.RemoveAll(garbage.imageRoot(id)); err != nil { @@ -170,7 +196,11 @@ func (graph *Graph) Undelete(id string) error { if err != nil { return err } - return os.Rename(garbage.imageRoot(id), graph.imageRoot(id)) + if err := os.Rename(garbage.imageRoot(id), graph.imageRoot(id)); err != nil { + return err + } + graph.idIndex.Add(id) + return nil } // GarbageCollect definitely deletes all images moved to the garbage diff --git a/components/engine/image.go b/components/engine/image.go index 1cd475f19b..19e5387f97 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -150,6 +150,10 @@ func (image *Image) Changes(rw string) ([]Change, error) { return Changes(layers, rw) } +func (image *Image) ShortId() string { + return TruncateId(image.Id) +} + func ValidateId(id string) error { if id == "" { return fmt.Errorf("Image id can't be empty") diff --git a/components/engine/tags.go b/components/engine/tags.go index 4f2b92e0bb..1b9cd19c83 100644 --- a/components/engine/tags.go +++ b/components/engine/tags.go @@ -106,7 +106,7 @@ func (store *TagStore) ImageName(id string) string { if names, exists := store.ById()[id]; exists && len(names) > 0 { return names[0] } - return id + return TruncateId(id) } func (store *TagStore) Set(repoName, tag, imageName string, force bool) error { diff --git a/components/engine/utils.go b/components/engine/utils.go index a87544ff3c..381af1fe38 100644 --- a/components/engine/utils.go +++ b/components/engine/utils.go @@ -334,3 +334,15 @@ func (idx *TruncIndex) Get(s string) (string, error) { } return string(idx.bytes[before:after]), err } + +// TruncateId returns a shorthand version of a string identifier for convenience. +// A collision with other shorthands is very unlikely, but possible. +// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller +// will need to use a langer prefix, or the full-length Id. +func TruncateId(id string) string { + shortLen := 12 + if len(id) < shortLen { + shortLen = len(id) + } + return id[:shortLen] +} From 875003ed9287d9e9ab4c45225b12436ef55b11f3 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sun, 31 Mar 2013 22:42:10 -0700 Subject: [PATCH 13/26] Avoid destroy() timeout by closing stdin in TestStart() Upstream-commit: 91b1f9eee9729fc9845eaecdad88a7a5050a33de Component: engine --- components/engine/container_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/engine/container_test.go b/components/engine/container_test.go index 45d334ca22..571a093767 100644 --- a/components/engine/container_test.go +++ b/components/engine/container_test.go @@ -263,6 +263,10 @@ func TestStart(t *testing.T) { if err := container.Start(); err == nil { t.Fatalf("A running containter should be able to be started") } + + // Try to avoid the timeoout in destroy. Best effort, don't check error + cStdin, _ := container.StdinPipe() + cStdin.Close() } func TestRun(t *testing.T) { From 953809e9d040f035e338fa9970591c01f5b7574a Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Mon, 1 Apr 2013 09:49:51 -0400 Subject: [PATCH 14/26] Close HTTP response bodies Fixes #285. Upstream-commit: 6b59cc8a1031d78530efb7582c936ba8221da847 Component: engine --- components/engine/registry.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/components/engine/registry.go b/components/engine/registry.go index 3e62ad96ee..ea2f1459f9 100644 --- a/components/engine/registry.go +++ b/components/engine/registry.go @@ -183,10 +183,10 @@ func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, re if err != nil { return err } + defer res.Body.Close() if res.StatusCode != 200 { return fmt.Errorf("HTTP code: %d", res.StatusCode) } - defer res.Body.Close() rawJson, err := ioutil.ReadAll(res.Body) if err != nil { return err @@ -237,6 +237,7 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth if err != nil { return fmt.Errorf("Failed to upload metadata: %s", err) } + defer res.Body.Close() if res.StatusCode != 200 { switch res.StatusCode { case 204: @@ -256,9 +257,13 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth req2, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/images/"+img.Id+"/layer", nil) req2.SetBasicAuth(authConfig.Username, authConfig.Password) res2, err := client.Do(req2) - if err != nil || res2.StatusCode != 307 { + if err != nil { return fmt.Errorf("Registry returned error: %s", err) } + res2.Body.Close() + if res2.StatusCode != 307 { + return fmt.Errorf("Registry returned unexpected HTTP status code %d, expected 307", res2.StatusCode) + } url, err := res2.Location() if err != nil || url == nil { return fmt.Errorf("Failed to retrieve layer upload location: %s", err) @@ -286,6 +291,7 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth if err != nil { return fmt.Errorf("Failed to upload layer: %s", err) } + res3.Body.Close() if res3.StatusCode != 200 { return fmt.Errorf("Received HTTP code %d while uploading layer", res3.StatusCode) } @@ -315,12 +321,13 @@ func (graph *Graph) pushTag(remote, revision, tag string, authConfig *auth.AuthC req.Header.Add("Content-type", "application/json") req.SetBasicAuth(authConfig.Username, authConfig.Password) res, err := client.Do(req) - if err != nil || (res.StatusCode != 200 && res.StatusCode != 201) { - if res != nil { - return fmt.Errorf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote) - } + if err != nil { return err } + res.Body.Close() + if res.StatusCode != 200 && res.StatusCode != 201 { + return fmt.Errorf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote) + } Debugf("Result of push tag: %d\n", res.StatusCode) switch res.StatusCode { default: From 819ad2b9f0b0074238946ac84f38974e68e094d8 Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Mon, 1 Apr 2013 12:28:54 -0400 Subject: [PATCH 15/26] Expand the contributing guidelines Upstream-commit: 321d94b17e0b23754cb8c5858c11bef554ec8279 Component: engine --- components/engine/CONTRIBUTING.md | 46 +++++++++++++----- .../sources/contributing/contributing.rst | 47 ++++++++++++++----- 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/components/engine/CONTRIBUTING.md b/components/engine/CONTRIBUTING.md index f956d37101..f461e95303 100644 --- a/components/engine/CONTRIBUTING.md +++ b/components/engine/CONTRIBUTING.md @@ -49,21 +49,45 @@ help prioritize the most common problems and requests. Fork the repo and make changes on your fork in a feature branch: -- If it's a bugfix branch, name it XXX-something where XXX is the number of the issue -- If it's a feature branch, create an enhancement issue to announce your intentions, and name it XXX-something where XXX is the number of the issue. +- If it's a bugfix branch, name it XXX-something where XXX is the number of the + issue +- If it's a feature branch, create an enhancement issue to announce your + intentions, and name it XXX-something where XXX is the number of the issue. -Submit unit tests for your changes. Golang has a great testing suite built -in: use it! Take a look at existing tests for inspiration. Run the full test -suite against your change and the master. +Submit unit tests for your changes. Go has a great test framework built in; use +it! Take a look at existing tests for inspiration. Run the full test suite on +your branch before submitting a pull request. -Submit any relevant updates or additions to documentation. +Make sure you include relevant updates or additions to documentation when +creating or modifying features. -Add clean code: +Write clean code. Universally formatted code promotes ease of writing, reading, +and maintenance. Always run `go fmt` before committing your changes. Most +editors have plugins that do this automatically, and there's also a git +pre-commit hook: -- Universally formatted code promotes ease of writing, reading, and maintenance. We suggest using gofmt before committing your changes. There's a git pre-commit hook made for doing so. -- curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/master/fmt-check && chmod +x .git/hooks/pre-commit +``` +curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/master/fmt-check && chmod +x .git/hooks/pre-commit +``` Pull requests descriptions should be as clear as possible and include a -referenced to all the issues that they address. +reference to all the issues that they address. -Add your name to the AUTHORS file. +Code review comments may be added to your pull request. Discuss, then make the +suggested modifications and push additional commits to your feature branch. Be +sure to post a comment after pushing. The new commits will show up in the pull +request automatically, but the reviewers will not be notified unless you +comment. + +Before the pull request is merged, make sure that you squash your commits into +logical units of work using `git rebase -i` and `git push -f`. After every +commit the test suite should be passing. Include documentation changes in the +same commit so that a revert would remove all traces of the feature or fix. + +Commits that fix or close an issue should include a reference like `Closes #XXX` +or `Fixes #XXX`, which will automatically close the issue when merged. + +Add your name to the AUTHORS file, but make sure the list is sorted and your +name and email address match your git configuration. The AUTHORS file is +regenerated occasionally from the git commit history, so a mismatch may result +in your changes being overwritten. diff --git a/components/engine/docs/sources/contributing/contributing.rst b/components/engine/docs/sources/contributing/contributing.rst index 689c4207ce..7b2b4da2d3 100644 --- a/components/engine/docs/sources/contributing/contributing.rst +++ b/components/engine/docs/sources/contributing/contributing.rst @@ -56,21 +56,46 @@ Conventions Fork the repo and make changes on your fork in a feature branch: -- If it's a bugfix branch, name it XXX-something where XXX is the number of the issue -- If it's a feature branch, create an enhancement issue to announce your intentions, and name it XXX-something where XXX is the number of the issue. +- If it's a bugfix branch, name it XXX-something where XXX is the number of the + issue +- If it's a feature branch, create an enhancement issue to announce your + intentions, and name it XXX-something where XXX is the number of the issue. -Submit unit tests for your changes. Golang has a great testing suite built -in: use it! Take a look at existing tests for inspiration. Run the full test -suite against your change and the master. +Submit unit tests for your changes. Go has a great test framework built in; use +it! Take a look at existing tests for inspiration. Run the full test suite on +your branch before submitting a pull request. -Submit any relevant updates or additions to documentation. +Make sure you include relevant updates or additions to documentation when +creating or modifying features. -Add clean code: +Write clean code. Universally formatted code promotes ease of writing, reading, +and maintenance. Always run ``go fmt`` before committing your changes. Most +editors have plugins that do this automatically, and there's also a git +pre-commit hook: + +.. code-block:: bash + + curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/master/fmt-check && chmod +x .git/hooks/pre-commit -- Universally formatted code promotes ease of writing, reading, and maintenance. We suggest using gofmt before committing your changes. There's a git pre-commit hook made for doing so. -- curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/master/fmt-check && chmod +x .git/hooks/pre-commit Pull requests descriptions should be as clear as possible and include a -referenced to all the issues that they address. +reference to all the issues that they address. -Add your name to the AUTHORS file. +Code review comments may be added to your pull request. Discuss, then make the +suggested modifications and push additional commits to your feature branch. Be +sure to post a comment after pushing. The new commits will show up in the pull +request automatically, but the reviewers will not be notified unless you +comment. + +Before the pull request is merged, make sure that you squash your commits into +logical units of work using ``git rebase -i`` and ``git push -f``. After every +commit the test suite should be passing. Include documentation changes in the +same commit so that a revert would remove all traces of the feature or fix. + +Commits that fix or close an issue should include a reference like ``Closes #XXX`` +or ``Fixes #XXX``, which will automatically close the issue when merged. + +Add your name to the AUTHORS file, but make sure the list is sorted and your +name and email address match your git configuration. The AUTHORS file is +regenerated occasionally from the git commit history, so a mismatch may result +in your changes being overwritten. From 70d73b8c8bc0294835c60921ffa330373b5f0060 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Sat, 30 Mar 2013 00:20:40 -0700 Subject: [PATCH 16/26] Don't use a strings.Reader where a bytes.Reader will do. There are several places where a []byte is converted to a string and then fed into strings.NewReader(). Upstream-commit: 7830cf91663f071315c3d0406bc3de0f8a58c2b6 Component: engine --- components/engine/auth/auth.go | 3 ++- components/engine/registry.go | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/engine/auth/auth.go b/components/engine/auth/auth.go index 045176ed48..e6aa048136 100644 --- a/components/engine/auth/auth.go +++ b/components/engine/auth/auth.go @@ -1,6 +1,7 @@ package auth import ( + "bytes" "encoding/base64" "encoding/json" "errors" @@ -111,7 +112,7 @@ func Login(authConfig *AuthConfig) (string, error) { return "", errors.New(errMsg) } - b := strings.NewReader(string(jsonBody)) + b := bytes.NewReader(jsonBody) req1, err := http.Post(REGISTRY_SERVER+"/v1/users", "application/json; charset=utf-8", b) if err != nil { errMsg = fmt.Sprintf("Server Error: %s", err) diff --git a/components/engine/registry.go b/components/engine/registry.go index 3e62ad96ee..4c51265981 100644 --- a/components/engine/registry.go +++ b/components/engine/registry.go @@ -1,6 +1,7 @@ package docker import ( + "bytes" "encoding/json" "fmt" "github.com/dotcloud/docker/auth" @@ -32,7 +33,7 @@ func NewImgJson(src []byte) (*Image, error) { func NewMultipleImgJson(src []byte) ([]*Image, error) { ret := []*Image{} - dec := json.NewDecoder(strings.NewReader(string(src))) + dec := json.NewDecoder(bytes.NewReader(src)) for { m := &Image{} if err := dec.Decode(m); err == io.EOF { @@ -226,7 +227,7 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth fmt.Fprintf(stdout, "Pushing %s metadata\n", img.Id) // FIXME: try json with UTF8 - jsonData := strings.NewReader(string(jsonRaw)) + jsonData := bytes.NewReader(jsonRaw) req, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/images/"+img.Id+"/json", jsonData) if err != nil { return err From 1ceba2a123dee64576c0e0472b6244e6e8c87c36 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Sat, 30 Mar 2013 00:22:56 -0700 Subject: [PATCH 17/26] Don't convert []byte to string unnecessarily. Upstream-commit: 15b30881570369564bde3f7d8faf4491b2ebfaab Component: engine --- components/engine/auth/auth.go | 4 ++-- components/engine/container_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/engine/auth/auth.go b/components/engine/auth/auth.go index e6aa048136..886d210f61 100644 --- a/components/engine/auth/auth.go +++ b/components/engine/auth/auth.go @@ -152,11 +152,11 @@ func Login(authConfig *AuthConfig) (string, error) { return "", errors.New(status) } } else { - status = fmt.Sprintf("Registration: %s", string(reqBody)) + status = fmt.Sprintf("Registration: %s", reqBody) return "", errors.New(status) } } else { - status = fmt.Sprintf("[%s] : %s", reqStatusCode, string(reqBody)) + status = fmt.Sprintf("[%s] : %s", reqStatusCode, reqBody) return "", errors.New(status) } if storeConfig { diff --git a/components/engine/container_test.go b/components/engine/container_test.go index fdc7f5703a..45d334ca22 100644 --- a/components/engine/container_test.go +++ b/components/engine/container_test.go @@ -227,7 +227,7 @@ func TestCommitRun(t *testing.T) { t.Fatal(err) } if string(output) != "hello\n" { - t.Fatalf("Unexpected output. Expected %s, received: %s (err: %s)", "hello\n", string(output), string(output2)) + t.Fatalf("Unexpected output. Expected %s, received: %s (err: %s)", "hello\n", output, output2) } } @@ -885,7 +885,7 @@ func BenchmarkRunSequencial(b *testing.B) { b.Fatal(err) } if string(output) != "foo" { - b.Fatalf("Unexecpted output: %v", string(output)) + b.Fatalf("Unexpected output: %s", output) } if err := runtime.Destroy(container); err != nil { b.Fatal(err) From 2ac307781e181eec60f35947427b500b6fb5d608 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Sat, 30 Mar 2013 00:23:12 -0700 Subject: [PATCH 18/26] Use a *println or *print function instead of *printf where appropriate. Upstream-commit: c298a91f95dafb056cede9036f5e81f259b8f3e9 Component: engine --- components/engine/commands.go | 14 +++++++------- components/engine/container.go | 4 ++-- components/engine/rcli/http.go | 2 +- components/engine/rcli/tcp.go | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/components/engine/commands.go b/components/engine/commands.go index 105f76be5e..ad9688d9c8 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -146,12 +146,12 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin newAuthConfig := auth.NewAuthConfig(username, password, email, srv.runtime.root) status, err := auth.Login(newAuthConfig) if err != nil { - fmt.Fprintf(stdout, "Error : %s\n", err) + fmt.Fprintln(stdout, "Error:", err) } else { srv.runtime.authConfig = newAuthConfig } if status != "" { - fmt.Fprintf(stdout, status) + fmt.Fprint(stdout, status) } return nil } @@ -207,7 +207,7 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string if !rcli.DEBUG_FLAG { return nil } - fmt.Fprintf(stdout, "debug mode enabled\n") + fmt.Fprintln(stdout, "debug mode enabled") fmt.Fprintf(stdout, "fds: %d\ngoroutines: %d\n", getTotalUsedFds(), runtime.NumGoroutine()) return nil } @@ -369,7 +369,7 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str } w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0) defer w.Flush() - fmt.Fprintf(w, "ID\tCREATED\tCREATED BY\n") + fmt.Fprintln(w, "ID\tCREATED\tCREATED BY") return image.WalkHistory(func(img *Image) error { fmt.Fprintf(w, "%s\t%s\t%s\n", srv.runtime.repositories.ImageName(img.ShortId()), @@ -438,7 +438,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri u.Host = src u.Path = "" } - fmt.Fprintf(stdout, "Downloading from %s\n", u.String()) + fmt.Fprintln(stdout, "Downloading from", u) // Download with curl (pretty progress bar) // If curl is not available, fallback to http.Get() resp, err = Download(u.String(), stdout) @@ -564,7 +564,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri } w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0) if !*quiet { - fmt.Fprintf(w, "REPOSITORY\tTAG\tID\tCREATED\tPARENT\n") + fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED\tPARENT") } var allImages map[string]*Image var err error @@ -647,7 +647,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) } w := tabwriter.NewWriter(stdout, 12, 1, 3, ' ', 0) if !*quiet { - fmt.Fprintf(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT\n") + fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT") } for _, container := range srv.runtime.List() { if !container.State.Running && !*flAll { diff --git a/components/engine/container.go b/components/engine/container.go index a7094bf5ec..8f993a35e1 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -458,8 +458,8 @@ func (container *Container) Stop() error { // 1. Send a SIGTERM if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil { - log.Printf(string(output)) - log.Printf("Failed to send SIGTERM to the process, force killing") + log.Print(string(output)) + log.Print("Failed to send SIGTERM to the process, force killing") if err := container.Kill(); err != nil { return err } diff --git a/components/engine/rcli/http.go b/components/engine/rcli/http.go index cc8d3b149e..3eeb2c2a97 100644 --- a/components/engine/rcli/http.go +++ b/components/engine/rcli/http.go @@ -20,7 +20,7 @@ func ListenAndServeHTTP(addr string, service Service) error { func(w http.ResponseWriter, r *http.Request) { cmd, args := URLToCall(r.URL) if err := call(service, r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil { - fmt.Fprintf(w, "Error: "+err.Error()+"\n") + fmt.Fprintln(w, "Error:", err.Error()) } })) } diff --git a/components/engine/rcli/tcp.go b/components/engine/rcli/tcp.go index a1fa669023..ff7e191f42 100644 --- a/components/engine/rcli/tcp.go +++ b/components/engine/rcli/tcp.go @@ -51,8 +51,8 @@ func ListenAndServe(proto, addr string, service Service) error { CLIENT_SOCKET = conn } if err := Serve(conn, service); err != nil { - log.Printf("Error: " + err.Error() + "\n") - fmt.Fprintf(conn, "Error: "+err.Error()+"\n") + log.Println("Error:", err.Error()) + fmt.Fprintln(conn, "Error:", err.Error()) } conn.Close() }() From c5d25049cd4d3551d5b72610e06ed0b82b178c09 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Sat, 30 Mar 2013 00:22:24 -0700 Subject: [PATCH 19/26] A few spelling/grammar corrections. Upstream-commit: 13d2b086386196e9f4f03e4a18d508a65f6fc5ff Component: engine --- components/engine/auth/auth.go | 1 + components/engine/commands.go | 2 +- components/engine/graph.go | 20 ++++++++++---------- components/engine/registry.go | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/components/engine/auth/auth.go b/components/engine/auth/auth.go index 886d210f61..2c282af218 100644 --- a/components/engine/auth/auth.go +++ b/components/engine/auth/auth.go @@ -131,6 +131,7 @@ func Login(authConfig *AuthConfig) (string, error) { status = "Account Created\n" storeConfig = true } else if reqStatusCode == 400 { + // FIXME: This should be 'exists', not 'exist'. Need to change on the server first. if string(reqBody) == "Username or email already exist" { client := &http.Client{} req, err := http.NewRequest("GET", REGISTRY_SERVER+"/v1/users", nil) diff --git a/components/engine/commands.go b/components/engine/commands.go index ad9688d9c8..a98a27cbb9 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -65,7 +65,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin // Read a line on raw terminal with support for simple backspace // sequences and echo. // - // This function is necessary because the login command must be done a + // This function is necessary because the login command must be done in a // raw terminal for two reasons: // - we have to read a password (without echoing it); // - the rcli "protocol" only supports cannonical and raw modes and you diff --git a/components/engine/graph.go b/components/engine/graph.go index ca126dc0e5..0f65aa205f 100644 --- a/components/engine/graph.go +++ b/components/engine/graph.go @@ -10,13 +10,13 @@ import ( "time" ) -// A Graph is a store for versioned filesystem images, and the relationship between them. +// A Graph is a store for versioned filesystem images and the relationship between them. type Graph struct { Root string idIndex *TruncIndex } -// NewGraph instanciates a new graph at the given root path in the filesystem. +// NewGraph instantiates a new graph at the given root path in the filesystem. // `root` will be created if it doesn't exist. func NewGraph(root string) (*Graph, error) { abspath, err := filepath.Abs(root) @@ -140,14 +140,14 @@ func (graph *Graph) Mktemp(id string) (string, error) { } // Garbage returns the "garbage", a staging area for deleted images. -// This allows images ot be deleted atomically by os.Rename(), instead of -// os.RemoveAll() which is prone to race conditions +// This allows images to be deleted atomically by os.Rename(), instead of +// os.RemoveAll() which is prone to race conditions. func (graph *Graph) Garbage() (*Graph, error) { return NewGraph(path.Join(graph.Root, ":garbage:")) } -// Check if given error is "not empty" -// Note: this is the way golang do it internally with os.IsNotExists +// Check if given error is "not empty". +// Note: this is the way golang does it internally with os.IsNotExists. func isNotEmpty(err error) bool { switch pe := err.(type) { case nil: @@ -190,7 +190,7 @@ func (graph *Graph) Delete(id string) error { return nil } -// Undelete moves an image back from the garbage to the main graph +// Undelete moves an image back from the garbage to the main graph. func (graph *Graph) Undelete(id string) error { garbage, err := graph.Garbage() if err != nil { @@ -203,7 +203,7 @@ func (graph *Graph) Undelete(id string) error { return nil } -// GarbageCollect definitely deletes all images moved to the garbage +// GarbageCollect definitely deletes all images moved to the garbage. func (graph *Graph) GarbageCollect() error { garbage, err := graph.Garbage() if err != nil { @@ -212,7 +212,7 @@ func (graph *Graph) GarbageCollect() error { return os.RemoveAll(garbage.Root) } -// Map returns a list of all images in the graph, addressable by ID +// Map returns a list of all images in the graph, addressable by ID. func (graph *Graph) Map() (map[string]*Image, error) { // FIXME: this should replace All() all, err := graph.All() @@ -226,7 +226,7 @@ func (graph *Graph) Map() (map[string]*Image, error) { return images, nil } -// All returns a list of all images in the graph +// All returns a list of all images in the graph. func (graph *Graph) All() ([]*Image, error) { var images []*Image err := graph.WalkAll(func(image *Image) { diff --git a/components/engine/registry.go b/components/engine/registry.go index 4c51265981..63cfd40350 100644 --- a/components/engine/registry.go +++ b/components/engine/registry.go @@ -21,7 +21,7 @@ func NewImgJson(src []byte) (*Image, error) { ret := &Image{} Debugf("Json string: {%s}\n", src) - // FIXME: Is there a cleaner way to "puryfy" the input json? + // FIXME: Is there a cleaner way to "purify" the input json? if err := json.Unmarshal(src, ret); err != nil { return nil, err } From 4d479797e66ade2347ba868125afa46edba4dcda Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Sat, 30 Mar 2013 00:36:27 -0700 Subject: [PATCH 20/26] Don't use an interface{} where a string will do. Upstream-commit: 887f509d1dd2a5e34fbfb19ccd2c4b1eb3127dbd Component: engine --- components/engine/commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/commands.go b/components/engine/commands.go index a98a27cbb9..0c896f29ba 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -28,7 +28,7 @@ func (srv *Server) Name() string { // FIXME: Stop violating DRY by repeating usage here and in Subcmd declarations func (srv *Server) Help() string { help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n" - for _, cmd := range [][]interface{}{ + for _, cmd := range [][]string{ {"attach", "Attach to a running container"}, {"commit", "Create a new image from a container's changes"}, {"diff", "Inspect changes on a container's filesystem"}, From 741a443d40ed125dc686f6b30f643523a6aac8b1 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Mon, 1 Apr 2013 13:05:00 -0700 Subject: [PATCH 21/26] Add a 'fmt' target to the Makefile. A convenience for gofmting all the code, including subpackages. Upstream-commit: 9b13d21fc980b5ac12e035d9c8cbe3ef2c36a079 Component: engine --- components/engine/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/Makefile b/components/engine/Makefile index e716762d31..6a9719078e 100644 --- a/components/engine/Makefile +++ b/components/engine/Makefile @@ -41,3 +41,6 @@ endif test: all @(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS)) + +fmt: + @find . -name "*.go" -exec gofmt -l -w {} \; From 75a146bba9765fa3a562ba64d754b53fdf781338 Mon Sep 17 00:00:00 2001 From: Paul Hammond Date: Mon, 1 Apr 2013 14:34:12 -0700 Subject: [PATCH 22/26] Fix mkimage-busybox Upstream-commit: cc2558bf10817b086fcb9783474d47db24d2cb3a Component: engine --- components/engine/contrib/mkimage-busybox.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/engine/contrib/mkimage-busybox.sh b/components/engine/contrib/mkimage-busybox.sh index 6c091d2c4a..909ad4794a 100755 --- a/components/engine/contrib/mkimage-busybox.sh +++ b/components/engine/contrib/mkimage-busybox.sh @@ -35,6 +35,5 @@ do cp -a /dev/$X dev done -tar -cf- . | docker put busybox -docker run -i -a -u root busybox /bin/echo Success. - +tar -cf- . | docker import - busybox +docker run -i -u root busybox /bin/echo Success. From 4c1daebf6d6b2f1ae8b8565c469c493ea9d277f1 Mon Sep 17 00:00:00 2001 From: Francisco Souza Date: Mon, 1 Apr 2013 18:50:25 -0300 Subject: [PATCH 23/26] makefile: simplify "fmt" target, and include -s flag Upstream-commit: 650dff73bdd48058ec0186cbda90683ff2287df0 Component: engine --- components/engine/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/Makefile b/components/engine/Makefile index 6a9719078e..4a3e6567ff 100644 --- a/components/engine/Makefile +++ b/components/engine/Makefile @@ -43,4 +43,4 @@ test: all @(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS)) fmt: - @find . -name "*.go" -exec gofmt -l -w {} \; + @gofmt -s -l -w . From 9c0eadc4350411ca79359c9feed66736b2bd5045 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 1 Apr 2013 16:02:02 -0700 Subject: [PATCH 24/26] Add test to reproduce issue #306 Upstream-commit: 7ad2e022fb737286b5803066bde6919e2d544c4e Component: engine --- components/engine/graph_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/components/engine/graph_test.go b/components/engine/graph_test.go index 61bac92d9e..8ac8d10478 100644 --- a/components/engine/graph_test.go +++ b/components/engine/graph_test.go @@ -120,6 +120,29 @@ func TestMount(t *testing.T) { }() } +// Test that an image can be deleted by its shorthand prefix +func TestDeletePrefix(t *testing.T) { + graph := tempGraph(t) + defer os.RemoveAll(graph.Root) + img := createTestImage(graph, t) + if err := graph.Delete(TruncateId(img.Id)); err != nil { + t.Fatal(err) + } + assertNImages(graph, t, 0) +} + +func createTestImage(graph *Graph, t *testing.T) *Image { + archive, err := fakeTar() + if err != nil { + t.Fatal(err) + } + img, err := graph.Create(archive, nil, "Test image") + if err != nil { + t.Fatal(err) + } + return img +} + func TestDelete(t *testing.T) { graph := tempGraph(t) defer os.RemoveAll(graph.Root) From 45a54423e7c1e68d72a4a1b2fe2815c38a5e28b0 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 1 Apr 2013 16:04:44 -0700 Subject: [PATCH 25/26] Images can be removed by short-hand ID. Solves #306. Upstream-commit: ff5cb8e864b87d3976b368ae544010f5736d1da2 Component: engine --- components/engine/graph.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/engine/graph.go b/components/engine/graph.go index 0f65aa205f..0c84dc4252 100644 --- a/components/engine/graph.go +++ b/components/engine/graph.go @@ -161,7 +161,11 @@ func isNotEmpty(err error) bool { } // Delete atomically removes an image from the graph. -func (graph *Graph) Delete(id string) error { +func (graph *Graph) Delete(name string) error { + id, err := graph.idIndex.Get(name) + if err != nil { + return err + } garbage, err := graph.Garbage() if err != nil { return err From cadfddb7705d974be76f18571eeddc0fe2338db3 Mon Sep 17 00:00:00 2001 From: Mikhail Sobolev Date: Tue, 2 Apr 2013 04:26:00 +0300 Subject: [PATCH 26/26] fix code block formatting Upstream-commit: 90db9e751750e62ea701843491774323ef20ab06 Component: engine --- .../engine/docs/sources/contributing/devenvironment.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/contributing/devenvironment.rst b/components/engine/docs/sources/contributing/devenvironment.rst index e16b317d32..136c3377c8 100644 --- a/components/engine/docs/sources/contributing/devenvironment.rst +++ b/components/engine/docs/sources/contributing/devenvironment.rst @@ -7,7 +7,7 @@ Setting up a dev environment Instructions that have been verified to work on Ubuntu 12.10, -.. code:: bash +.. code-block:: bash sudo apt-get -y install lxc wget bsdtar curl golang git @@ -24,7 +24,7 @@ Instructions that have been verified to work on Ubuntu 12.10, Then run the docker daemon, -.. code:: bash +.. code-block:: bash sudo $GOPATH/bin/docker -d