From 054702fedc7d2b2d86346f72ff9769fadc914579 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Wed, 6 May 2015 23:52:51 -0700 Subject: [PATCH 1/8] container config: Add Swarm ID helpers. Signed-off-by: Andrea Luzzardi --- cluster/config.go | 11 +++++++++++ cluster/config_test.go | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/cluster/config.go b/cluster/config.go index 149c8ce387..c0381dd261 100644 --- a/cluster/config.go +++ b/cluster/config.go @@ -87,6 +87,17 @@ func (c *ContainerConfig) extractExprs(key string) []string { return exprs } +// SwarmID extracts the Swarm ID from the Config. +// May return an empty string if not set. +func (c *ContainerConfig) SwarmID() string { + return c.Labels[namespace+".id"] +} + +// SetSwarmID sets or overrides the Swarm ID in the Config. +func (c *ContainerConfig) SetSwarmID(id string) { + c.Labels[namespace+".id"] = id +} + // Affinities returns all the affinities from the ContainerConfig func (c *ContainerConfig) Affinities() []string { return c.extractExprs("affinities") diff --git a/cluster/config_test.go b/cluster/config_test.go index 10fd62b0c9..9348deae26 100644 --- a/cluster/config_test.go +++ b/cluster/config_test.go @@ -29,6 +29,18 @@ func TestBuildContainerConfig(t *testing.T) { assert.Len(t, config.Labels, 2) } +func TestSwarmID(t *testing.T) { + // Getter / Setter + config := BuildContainerConfig(dockerclient.ContainerConfig{}) + assert.Empty(t, config.SwarmID()) + config.SetSwarmID("foo") + assert.Equal(t, config.SwarmID(), "foo") + + // Retrieve an existing ID. + config = BuildContainerConfig(dockerclient.ContainerConfig{Labels: map[string]string{namespace + ".id": "test"}}) + assert.Equal(t, config.SwarmID(), "test") +} + func TestConstraints(t *testing.T) { config := BuildContainerConfig(dockerclient.ContainerConfig{}) assert.Empty(t, config.Constraints()) From c4c07c97ac57c7d75cb62b2950d56dd03848441c Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Thu, 7 May 2015 00:02:17 -0700 Subject: [PATCH 2/8] godeps: vendor dependency to docker/docker/pkg/stringid. Signed-off-by: Andrea Luzzardi --- Godeps/Godeps.json | 7 +++- .../docker/docker/pkg/stringid/README.md | 1 + .../docker/docker/pkg/stringid/stringid.go | 38 +++++++++++++++++++ .../docker/pkg/stringid/stringid_test.go | 35 +++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/README.md create mode 100644 Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid.go create mode 100644 Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index d4066de1e0..e516876c98 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "github.com/docker/swarm", - "GoVersion": "go1.3.1", + "GoVersion": "go1.3.3", "Packages": [ "./..." ], @@ -40,6 +40,11 @@ "Comment": "v1.4.1-3245-g443437f", "Rev": "443437f5ea04da9d62bf3e05d7951f7d30e77d96" }, + { + "ImportPath": "github.com/docker/docker/pkg/stringid", + "Comment": "v1.4.1-3245-g443437f", + "Rev": "443437f5ea04da9d62bf3e05d7951f7d30e77d96" + }, { "ImportPath": "github.com/docker/docker/pkg/units", "Comment": "v1.4.1-3245-g443437f", diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/README.md b/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/README.md new file mode 100644 index 0000000000..37a5098fd9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/README.md @@ -0,0 +1 @@ +This package provides helper functions for dealing with string identifiers diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid.go new file mode 100644 index 0000000000..bf39df9b73 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid.go @@ -0,0 +1,38 @@ +package stringid + +import ( + "crypto/rand" + "encoding/hex" + "io" + "strconv" +) + +// 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] +} + +// GenerateRandomID returns an unique id +func GenerateRandomID() string { + for { + id := make([]byte, 32) + if _, err := io.ReadFull(rand.Reader, id); err != nil { + panic(err) // This shouldn't happen + } + value := hex.EncodeToString(id) + // if we try to parse the truncated for as an int and we don't have + // an error then the value is all numberic and causes issues when + // used as a hostname. ref #3869 + if _, err := strconv.ParseInt(TruncateID(value), 10, 64); err == nil { + continue + } + return value + } +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid_test.go new file mode 100644 index 0000000000..21f8f8a2fb --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid_test.go @@ -0,0 +1,35 @@ +package stringid + +import "testing" + +func TestGenerateRandomID(t *testing.T) { + id := GenerateRandomID() + + if len(id) != 64 { + t.Fatalf("Id returned is incorrect: %s", id) + } +} + +func TestShortenId(t *testing.T) { + id := GenerateRandomID() + truncID := TruncateID(id) + if len(truncID) != 12 { + t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID) + } +} + +func TestShortenIdEmpty(t *testing.T) { + id := "" + truncID := TruncateID(id) + if len(truncID) > len(id) { + t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID) + } +} + +func TestShortenIdInvalid(t *testing.T) { + id := "1234" + truncID := TruncateID(id) + if len(truncID) != len(id) { + t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID) + } +} From abfe5e6f4c0cfe541e73f53026e6e5622531c4ad Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Thu, 7 May 2015 00:34:18 -0700 Subject: [PATCH 3/8] Add support for Swarm IDs. - Every container that gets created through Swarm gets a Swarm ID assigned (as a label). - All API operations (start, stop, ...) can be performed by using either the container ID or the swarm ID. Signed-off-by: Andrea Luzzardi --- cluster/engine.go | 7 ++++++- cluster/swarm/cluster.go | 16 +++++++++++++++- cluster/swarm/cluster_test.go | 22 +++++++++++++++++----- test/integration/swarm_id.bats | 31 +++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 test/integration/swarm_id.bats diff --git a/cluster/engine.go b/cluster/engine.go index 7235f23ff7..e2d2aac943 100644 --- a/cluster/engine.go +++ b/cluster/engine.go @@ -463,11 +463,16 @@ func (e *Engine) Container(IDOrName string) *Container { } for _, container := range e.Containers() { - // Match ID prefix. + // Match Container ID prefix. if strings.HasPrefix(container.Id, IDOrName) { return container } + // Match Swarm ID prefix. + if strings.HasPrefix(container.Config.SwarmID(), IDOrName) { + return container + } + // Match name, /name or engine/name. for _, name := range container.Names { if name == IDOrName || name == "/"+IDOrName || container.Engine.ID+name == IDOrName || container.Engine.Name+name == IDOrName { diff --git a/cluster/swarm/cluster.go b/cluster/swarm/cluster.go index 5f4f2254dc..40db12b139 100644 --- a/cluster/swarm/cluster.go +++ b/cluster/swarm/cluster.go @@ -9,6 +9,7 @@ import ( "sync" log "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/units" "github.com/docker/swarm/cluster" "github.com/docker/swarm/discovery" @@ -80,16 +81,29 @@ func (c *Cluster) RegisterEventHandler(h cluster.EventHandler) error { return nil } +// Generate a globally (across the cluster) unique ID. +func (c *Cluster) generateUniqueID() string { + for { + id := stringid.GenerateRandomID() + if c.Container(id) == nil { + return id + } + } +} + // CreateContainer aka schedule a brand new container into the cluster. func (c *Cluster) CreateContainer(config *cluster.ContainerConfig, name string) (*cluster.Container, error) { c.scheduler.Lock() defer c.scheduler.Unlock() - // check new name whether avaliable + // Ensure the name is avaliable if cID := c.getIDFromName(name); cID != "" { return nil, fmt.Errorf("Conflict, The name %s is already assigned to %s. You have to delete (or rename) that container to be able to assign %s to a container again.", name, cID, name) } + // Associate a Swarm ID to the container we are creating. + config.SetSwarmID(c.generateUniqueID()) + n, err := c.scheduler.SelectNodeForContainer(c.listNodes(), config) if err != nil { return nil, err diff --git a/cluster/swarm/cluster_test.go b/cluster/swarm/cluster_test.go index 3078e7b22c..aca6828e4c 100644 --- a/cluster/swarm/cluster_test.go +++ b/cluster/swarm/cluster_test.go @@ -8,13 +8,14 @@ import ( "github.com/stretchr/testify/assert" ) -func createEngine(t *testing.T, ID string, containers ...dockerclient.Container) *cluster.Engine { +func createEngine(t *testing.T, ID string, containers ...*cluster.Container) *cluster.Engine { engine := cluster.NewEngine(ID, 0) engine.Name = ID engine.ID = ID for _, container := range containers { - engine.AddContainer(&cluster.Container{Container: container, Engine: engine}) + container.Engine = engine + engine.AddContainer(container) } return engine @@ -24,9 +25,16 @@ func TestContainerLookup(t *testing.T) { c := &Cluster{ engines: make(map[string]*cluster.Engine), } - container := dockerclient.Container{ - Id: "container-id", - Names: []string{"/container-name1", "/container-name2"}, + container := &cluster.Container{ + Container: dockerclient.Container{ + Id: "container-id", + Names: []string{"/container-name1", "/container-name2"}, + }, + Config: cluster.BuildContainerConfig(dockerclient.ContainerConfig{ + Labels: map[string]string{ + "com.docker.swarm.id": "swarm-id", + }, + }), } n := createEngine(t, "test-engine", container) @@ -45,4 +53,8 @@ func TestContainerLookup(t *testing.T) { // Container engine/name matching. assert.NotNil(t, c.Container("test-engine/container-name1")) assert.NotNil(t, c.Container("test-engine/container-name2")) + // Swarm ID lookup. + assert.NotNil(t, c.Container("swarm-id")) + // Swarm ID prefix lookup. + assert.NotNil(t, c.Container("swarm-")) } diff --git a/test/integration/swarm_id.bats b/test/integration/swarm_id.bats new file mode 100644 index 0000000000..5140d031c4 --- /dev/null +++ b/test/integration/swarm_id.bats @@ -0,0 +1,31 @@ +#!/usr/bin/env bats + +load helpers + +function teardown() { + swarm_manage_cleanup + stop_docker +} + +@test "swarm id generation" { + start_docker_with_busybox 2 + swarm_manage + + # Create a dummy container just so we interfere with the tests. + # This one won't be used. + docker_swarm run -d busybox true + + # Create a container and get its Swarm ID back. + id=$(docker_swarm run -d busybox true) + swarm_id=$(docker_swarm inspect -f '{{ index .Config.Labels "com.docker.swarm.id" }}' "$id") + + # Make sure we got a valid Swarm ID. + [[ "${#swarm_id}" -eq 64 ]] + [[ "$id" != "$swarm_id" ]] + + # API operations should work with Swarm IDs as well as Container IDs. + [[ $(docker_swarm inspect -f "{{ .Id }}" "$swarm_id") == "$id" ]] + + # `docker ps` should be able to filter by Swarm ID using the label. + [[ $(docker_swarm ps -a -q --no-trunc --filter="label=com.docker.swarm.id=$swarm_id") == "$id" ]] +} From b2fa0609571a45fa01b116fe4f6dc19041f091c7 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Thu, 7 May 2015 00:35:32 -0700 Subject: [PATCH 4/8] gitignore: Fix "swarm" pattern matching. The pattern without slashes was conflicting with cluster/swarm: ``` $ git add cluster/swarm/cluster.go The following paths are ignored by one of your .gitignore files: cluster/swarm Use -f if you really want to add them. fatal: no files added ``` Signed-off-by: Andrea Luzzardi --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index acf599143a..438f4251c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -swarm \ No newline at end of file +swarm From 5c801d2c6af604f72085e61514ebcaa689fdeb9a Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Thu, 7 May 2015 01:09:19 -0700 Subject: [PATCH 5/8] swarm id: Handle compatibility. Containers created with Swarm<0.3.0 or directly on the host without going through Swarm don't have a Swarm ID. We are going to fake it by using the Container ID. Signed-off-by: Andrea Luzzardi --- api/handlers.go | 31 +++++++++++++++++++++++++++++++ cluster/engine.go | 7 +++++++ test/integration/swarm_id.bats | 26 +++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/api/handlers.go b/api/handlers.go index 39ab133980..ecef979529 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -198,6 +198,11 @@ func getContainersJSON(c *context, w http.ResponseWriter, r *http.Request) { tmp.Status = "Pending" } + // Overwrite labels with the ones we have in the config. + // This ensures that we can freely manipulate them in the codebase and + // they will be properly exported back (for instance Swarm IDs). + tmp.Labels = container.Config.Labels + // TODO remove the Node Name in the name when we have a good solution tmp.Names = make([]string, len(container.Names)) for i, name := range container.Names { @@ -251,6 +256,32 @@ func getContainerJSON(c *context, w http.ResponseWriter, r *http.Request) { httpError(w, err.Error(), http.StatusInternalServerError) return } + // Compatibility: Inject the Swarm ID label if not available. + // This **must** be done before inserting the `Node` field since it too has + // a `Label` field. + if !bytes.Contains(data, []byte("\"com.docker.swarm.id\":")) { + label := fmt.Sprintf("%q:%q", "com.docker.swarm.id", container.Config.SwarmID()) + switch { + // No `Labels` section at all + case !bytes.Contains(data, []byte("\"Labels\":")): + data = bytes.Replace(data, + []byte("\"Image\":"), + []byte("\"Labels\":{"+label+"},\"Image\":"), + 1) + // Empty `Labels` section + case bytes.Contains(data, []byte("\"Labels\":{}")): + data = bytes.Replace(data, + []byte("\"Labels\":{}"), + []byte("\"Labels\":{"+label+"}"), + 1) + // `Labels` section with labels in it + case bytes.Contains(data, []byte("\"Labels\":{")): + data = bytes.Replace(data, + []byte("\"Labels\":{"), + []byte("\"Labels\":{"+label+","), + 1) + } + } // insert Node field data = bytes.Replace(data, []byte("\"Name\":\"/"), []byte(fmt.Sprintf("\"Node\":%s,\"Name\":\"/", n)), -1) diff --git a/cluster/engine.go b/cluster/engine.go index e2d2aac943..ebb33453f0 100644 --- a/cluster/engine.go +++ b/cluster/engine.go @@ -252,6 +252,13 @@ func (e *Engine) updateContainer(c dockerclient.Container, containers map[string container.Config = BuildContainerConfig(*info.Config) container.Config.CpuShares = container.Config.CpuShares * 1024.0 / e.Cpus + // Compatibility: Containers created with Swarm<0.3.0 or directly on + // the host without going through Swarm don't have a Swarm ID. We are + // going to fake it by using the Container ID. + if container.Config.SwarmID() == "" { + container.Config.SetSwarmID(c.Id) + } + // Save the entire inspect back into the container. container.Info = *info } diff --git a/test/integration/swarm_id.bats b/test/integration/swarm_id.bats index 5140d031c4..4f3b3338fe 100644 --- a/test/integration/swarm_id.bats +++ b/test/integration/swarm_id.bats @@ -8,7 +8,7 @@ function teardown() { } @test "swarm id generation" { - start_docker_with_busybox 2 + start_docker_with_busybox 1 swarm_manage # Create a dummy container just so we interfere with the tests. @@ -29,3 +29,27 @@ function teardown() { # `docker ps` should be able to filter by Swarm ID using the label. [[ $(docker_swarm ps -a -q --no-trunc --filter="label=com.docker.swarm.id=$swarm_id") == "$id" ]] } + +@test "swarm id compatbility" { + start_docker_with_busybox 1 + swarm_manage + + # Create a dummy container just so we interfere with the tests. + # This one won't be used. + docker -H "${HOSTS[0]}" run -d busybox true + + # Create a container directly on the node. + id=$(docker -H "${HOSTS[0]}" run -d busybox true) + + # Wait a few seconds for Swarm to detect the container. + retry 10 0.5 docker_swarm inspect "$id" + + # Fetch its Swarm ID + swarm_id=$(docker_swarm inspect -f '{{ index .Config.Labels "com.docker.swarm.id" }}' "$id") + + # Make sure it's the same as the Container ID. + [[ "$id" == "$swarm_id" ]] + + # `docker ps` should be able to filter by Swarm ID using the label. + [[ $(docker_swarm ps -a -q --no-trunc --filter="label=com.docker.swarm.id=$swarm_id") == "$id" ]] +} From b6a7c3d7f5fca1606e99627ad0b120d237a76614 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Mon, 11 May 2015 15:40:46 -0700 Subject: [PATCH 6/8] swarm ID: Remove backward compatibility. Signed-off-by: Andrea Luzzardi --- api/handlers.go | 26 -------------------------- cluster/engine.go | 7 ------- test/integration/swarm_id.bats | 24 ------------------------ 3 files changed, 57 deletions(-) diff --git a/api/handlers.go b/api/handlers.go index ecef979529..6f9b2e120f 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -256,32 +256,6 @@ func getContainerJSON(c *context, w http.ResponseWriter, r *http.Request) { httpError(w, err.Error(), http.StatusInternalServerError) return } - // Compatibility: Inject the Swarm ID label if not available. - // This **must** be done before inserting the `Node` field since it too has - // a `Label` field. - if !bytes.Contains(data, []byte("\"com.docker.swarm.id\":")) { - label := fmt.Sprintf("%q:%q", "com.docker.swarm.id", container.Config.SwarmID()) - switch { - // No `Labels` section at all - case !bytes.Contains(data, []byte("\"Labels\":")): - data = bytes.Replace(data, - []byte("\"Image\":"), - []byte("\"Labels\":{"+label+"},\"Image\":"), - 1) - // Empty `Labels` section - case bytes.Contains(data, []byte("\"Labels\":{}")): - data = bytes.Replace(data, - []byte("\"Labels\":{}"), - []byte("\"Labels\":{"+label+"}"), - 1) - // `Labels` section with labels in it - case bytes.Contains(data, []byte("\"Labels\":{")): - data = bytes.Replace(data, - []byte("\"Labels\":{"), - []byte("\"Labels\":{"+label+","), - 1) - } - } // insert Node field data = bytes.Replace(data, []byte("\"Name\":\"/"), []byte(fmt.Sprintf("\"Node\":%s,\"Name\":\"/", n)), -1) diff --git a/cluster/engine.go b/cluster/engine.go index ebb33453f0..e2d2aac943 100644 --- a/cluster/engine.go +++ b/cluster/engine.go @@ -252,13 +252,6 @@ func (e *Engine) updateContainer(c dockerclient.Container, containers map[string container.Config = BuildContainerConfig(*info.Config) container.Config.CpuShares = container.Config.CpuShares * 1024.0 / e.Cpus - // Compatibility: Containers created with Swarm<0.3.0 or directly on - // the host without going through Swarm don't have a Swarm ID. We are - // going to fake it by using the Container ID. - if container.Config.SwarmID() == "" { - container.Config.SetSwarmID(c.Id) - } - // Save the entire inspect back into the container. container.Info = *info } diff --git a/test/integration/swarm_id.bats b/test/integration/swarm_id.bats index 4f3b3338fe..fa1d2e862c 100644 --- a/test/integration/swarm_id.bats +++ b/test/integration/swarm_id.bats @@ -29,27 +29,3 @@ function teardown() { # `docker ps` should be able to filter by Swarm ID using the label. [[ $(docker_swarm ps -a -q --no-trunc --filter="label=com.docker.swarm.id=$swarm_id") == "$id" ]] } - -@test "swarm id compatbility" { - start_docker_with_busybox 1 - swarm_manage - - # Create a dummy container just so we interfere with the tests. - # This one won't be used. - docker -H "${HOSTS[0]}" run -d busybox true - - # Create a container directly on the node. - id=$(docker -H "${HOSTS[0]}" run -d busybox true) - - # Wait a few seconds for Swarm to detect the container. - retry 10 0.5 docker_swarm inspect "$id" - - # Fetch its Swarm ID - swarm_id=$(docker_swarm inspect -f '{{ index .Config.Labels "com.docker.swarm.id" }}' "$id") - - # Make sure it's the same as the Container ID. - [[ "$id" == "$swarm_id" ]] - - # `docker ps` should be able to filter by Swarm ID using the label. - [[ $(docker_swarm ps -a -q --no-trunc --filter="label=com.docker.swarm.id=$swarm_id") == "$id" ]] -} From dde47ee650df659300893c09ec931b2eca6f40c7 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Mon, 11 May 2015 16:40:42 -0700 Subject: [PATCH 7/8] swarm IDs: Convert Swarm ID to Container ID in API Proxy. Signed-off-by: Andrea Luzzardi --- api/handlers.go | 15 +++++++++++---- api/utils.go | 13 ++++++------- test/integration/swarm_id.bats | 6 +++++- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/api/handlers.go b/api/handlers.go index 6f9b2e120f..88af60afd4 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -478,12 +478,15 @@ func ping(c *context, w http.ResponseWriter, r *http.Request) { // Proxy a request to the right node func proxyContainer(c *context, w http.ResponseWriter, r *http.Request) { - container, err := getContainerFromVars(c, mux.Vars(r)) + name, container, err := getContainerFromVars(c, mux.Vars(r)) if err != nil { httpError(w, err.Error(), http.StatusNotFound) return } + // Set the full container ID in the proxied URL path. + r.URL.Path = strings.Replace(r.URL.Path, name, container.Id, 1) + if err := proxy(c.tlsConfig, container.Engine.Addr, w, r); err != nil { httpError(w, err.Error(), http.StatusInternalServerError) } @@ -579,11 +582,13 @@ func postCommit(c *context, w http.ResponseWriter, r *http.Request) { vars["name"] = r.Form.Get("container") // get container - container, err := getContainerFromVars(c, vars) + name, container, err := getContainerFromVars(c, vars) if err != nil { httpError(w, err.Error(), http.StatusNotFound) return } + // Set the full container ID in the proxied URL path. + r.URL.RawQuery = strings.Replace(r.URL.RawQuery, name, container.Id, 1) cb := func(resp *http.Response) { if resp.StatusCode == http.StatusCreated { @@ -599,7 +604,7 @@ func postCommit(c *context, w http.ResponseWriter, r *http.Request) { // POST /containers/{name:.*}/rename func postRenameContainer(c *context, w http.ResponseWriter, r *http.Request) { - container, err := getContainerFromVars(c, mux.Vars(r)) + _, container, err := getContainerFromVars(c, mux.Vars(r)) if err != nil { httpError(w, err.Error(), http.StatusNotFound) return @@ -622,11 +627,13 @@ func postRenameContainer(c *context, w http.ResponseWriter, r *http.Request) { // Proxy a hijack request to the right node func proxyHijack(c *context, w http.ResponseWriter, r *http.Request) { - container, err := getContainerFromVars(c, mux.Vars(r)) + name, container, err := getContainerFromVars(c, mux.Vars(r)) if err != nil { httpError(w, err.Error(), http.StatusNotFound) return } + // Set the full container ID in the proxied URL path. + r.URL.Path = strings.Replace(r.URL.Path, name, container.Id, 1) if err := hijack(c.tlsConfig, container.Engine.Addr, w, r); err != nil { httpError(w, err.Error(), http.StatusInternalServerError) diff --git a/api/utils.go b/api/utils.go index d66a6a8d50..8041616c80 100644 --- a/api/utils.go +++ b/api/utils.go @@ -26,25 +26,24 @@ func newClientAndScheme(tlsConfig *tls.Config) (*http.Client, string) { return &http.Client{}, "http" } -func getContainerFromVars(c *context, vars map[string]string) (*cluster.Container, error) { +func getContainerFromVars(c *context, vars map[string]string) (string, *cluster.Container, error) { if name, ok := vars["name"]; ok { if container := c.cluster.Container(name); container != nil { - return container, nil + return name, container, nil } - return nil, fmt.Errorf("No such container: %s", name) - + return name, nil, fmt.Errorf("No such container: %s", name) } if ID, ok := vars["execid"]; ok { for _, container := range c.cluster.Containers() { for _, execID := range container.Info.ExecIDs { if ID == execID { - return container, nil + return "", container, nil } } } - return nil, fmt.Errorf("Exec %s not found", ID) + return "", nil, fmt.Errorf("Exec %s not found", ID) } - return nil, errors.New("Not found") + return "", nil, errors.New("Not found") } // from https://github.com/golang/go/blob/master/src/net/http/httputil/reverseproxy.go#L82 diff --git a/test/integration/swarm_id.bats b/test/integration/swarm_id.bats index fa1d2e862c..dda16401da 100644 --- a/test/integration/swarm_id.bats +++ b/test/integration/swarm_id.bats @@ -16,7 +16,7 @@ function teardown() { docker_swarm run -d busybox true # Create a container and get its Swarm ID back. - id=$(docker_swarm run -d busybox true) + id=$(docker_swarm run -d -i busybox sh -c "head -n 1; echo output") swarm_id=$(docker_swarm inspect -f '{{ index .Config.Labels "com.docker.swarm.id" }}' "$id") # Make sure we got a valid Swarm ID. @@ -25,6 +25,10 @@ function teardown() { # API operations should work with Swarm IDs as well as Container IDs. [[ $(docker_swarm inspect -f "{{ .Id }}" "$swarm_id") == "$id" ]] + # These should work with a Swarm ID. + docker_swarm logs "$swarm_id" + docker_swarm commit "$swarm_id" + attach_output=$(echo input | docker_swarm attach "$swarm_id") # `docker ps` should be able to filter by Swarm ID using the label. [[ $(docker_swarm ps -a -q --no-trunc --filter="label=com.docker.swarm.id=$swarm_id") == "$id" ]] From bdd97c1d72c6049eadbbdf4bacf7efc2ac612778 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Tue, 12 May 2015 14:21:09 -0700 Subject: [PATCH 8/8] swarm id: Fix proxy for execs. Signed-off-by: Andrea Luzzardi --- api/handlers.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/api/handlers.go b/api/handlers.go index 88af60afd4..addb8d1be9 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -485,7 +485,9 @@ func proxyContainer(c *context, w http.ResponseWriter, r *http.Request) { } // Set the full container ID in the proxied URL path. - r.URL.Path = strings.Replace(r.URL.Path, name, container.Id, 1) + if name != "" { + r.URL.Path = strings.Replace(r.URL.Path, name, container.Id, 1) + } if err := proxy(c.tlsConfig, container.Engine.Addr, w, r); err != nil { httpError(w, err.Error(), http.StatusInternalServerError) @@ -588,7 +590,9 @@ func postCommit(c *context, w http.ResponseWriter, r *http.Request) { return } // Set the full container ID in the proxied URL path. - r.URL.RawQuery = strings.Replace(r.URL.RawQuery, name, container.Id, 1) + if name != "" { + r.URL.RawQuery = strings.Replace(r.URL.RawQuery, name, container.Id, 1) + } cb := func(resp *http.Response) { if resp.StatusCode == http.StatusCreated { @@ -633,7 +637,9 @@ func proxyHijack(c *context, w http.ResponseWriter, r *http.Request) { return } // Set the full container ID in the proxied URL path. - r.URL.Path = strings.Replace(r.URL.Path, name, container.Id, 1) + if name != "" { + r.URL.Path = strings.Replace(r.URL.Path, name, container.Id, 1) + } if err := hijack(c.tlsConfig, container.Engine.Addr, w, r); err != nil { httpError(w, err.Error(), http.StatusInternalServerError)