From 1225eddc79374c1ec1d54475a8874b8c215269a3 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Sat, 16 Jan 2016 13:44:48 -0800 Subject: [PATCH 1/3] allow engine/name support in volumes Signed-off-by: Victor Vieux --- api/handlers.go | 18 ++++--- cluster/cluster.go | 5 +- cluster/engine.go | 4 +- cluster/mesos/cluster.go | 7 +-- cluster/network.go | 2 +- cluster/swarm/cluster.go | 90 ++++++++++++++++++-------------- cluster/volume.go | 39 ++++++++++++++ test/integration/api/volume.bats | 10 +++- 8 files changed, 116 insertions(+), 59 deletions(-) diff --git a/api/handlers.go b/api/handlers.go index 77ce4aa97c..86e3438fbb 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -223,9 +223,15 @@ func getNetworks(c *context, w http.ResponseWriter, r *http.Request) { // GET /volumes func getVolumes(c *context, w http.ResponseWriter, r *http.Request) { - volumes := struct { - Volumes []*cluster.Volume - }{c.cluster.Volumes()} + volumes := struct{ Volumes []*dockerclient.Volume }{} + + for _, volume := range c.cluster.Volumes() { + tmp := (*volume).Volume + if tmp.Driver == "local" { + tmp.Name = volume.Engine.Name + "/" + volume.Name + } + volumes.Volumes = append(volumes.Volumes, &tmp) + } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(volumes) @@ -808,9 +814,9 @@ func proxyNetwork(c *context, w http.ResponseWriter, r *http.Request) { // Proxy a request to the right node func proxyVolume(c *context, w http.ResponseWriter, r *http.Request) { var name = mux.Vars(r)["volumename"] - if volume := c.cluster.Volume(name); volume != nil { - err := proxy(c.tlsConfig, volume.Engine.Addr, w, r) - volume.Engine.CheckConnectionErr(err) + if volume := c.cluster.Volumes().Get(name); volume != nil { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(volume) return } httpError(w, fmt.Sprintf("No such volume: %s", name), http.StatusNotFound) diff --git a/cluster/cluster.go b/cluster/cluster.go index 6169683f2b..480b2c6eee 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -44,10 +44,7 @@ type Cluster interface { CreateVolume(request *dockerclient.VolumeCreateRequest) (*Volume, error) // Return all volumes - Volumes() []*Volume - - // Return one volume from the cluster - Volume(name string) *Volume + Volumes() Volumes // Remove volumes from the cluster RemoveVolumes(name string) (bool, error) diff --git a/cluster/engine.go b/cluster/engine.go index f091eda0db..f173e41df5 100644 --- a/cluster/engine.go +++ b/cluster/engine.go @@ -857,10 +857,10 @@ func (e *Engine) Networks() Networks { } // Volumes returns all the volumes in the engine -func (e *Engine) Volumes() []*Volume { +func (e *Engine) Volumes() Volumes { e.RLock() - volumes := make([]*Volume, 0, len(e.volumes)) + volumes := Volumes{} for _, volume := range e.volumes { volumes = append(volumes, volume) } diff --git a/cluster/mesos/cluster.go b/cluster/mesos/cluster.go index e2bb9638f5..fd08f5cb1e 100644 --- a/cluster/mesos/cluster.go +++ b/cluster/mesos/cluster.go @@ -338,12 +338,7 @@ func (c *Cluster) Networks() cluster.Networks { } // Volumes returns all the volumes in the cluster. -func (c *Cluster) Volumes() []*cluster.Volume { - return nil -} - -// Volume returns the volume name in the cluster -func (c *Cluster) Volume(name string) *cluster.Volume { +func (c *Cluster) Volumes() cluster.Volumes { return nil } diff --git a/cluster/network.go b/cluster/network.go index 498c15e74a..ca02a2f1d6 100644 --- a/cluster/network.go +++ b/cluster/network.go @@ -14,7 +14,7 @@ type Network struct { Engine *Engine } -// Networks represents a map of networks +// Networks represents an array of networks type Networks []*Network // Uniq returns all uniq networks diff --git a/cluster/swarm/cluster.go b/cluster/swarm/cluster.go index 4cd5d96ebe..2d42aab498 100644 --- a/cluster/swarm/cluster.go +++ b/cluster/swarm/cluster.go @@ -441,6 +441,18 @@ func (c *Cluster) refreshNetworks() { wg.Wait() } +func (c *Cluster) refreshVolumes() { + var wg sync.WaitGroup + for _, e := range c.engines { + wg.Add(1) + go func(e *cluster.Engine) { + e.RefreshVolumes() + wg.Done() + }(e) + } + wg.Wait() +} + // CreateNetwork creates a network in the cluster func (c *Cluster) CreateNetwork(request *dockerclient.NetworkCreate) (response *dockerclient.NetworkCreateResponse, err error) { var ( @@ -472,20 +484,45 @@ func (c *Cluster) CreateVolume(request *dockerclient.VolumeCreateRequest) (*clus wg sync.WaitGroup volume *cluster.Volume err error + parts = strings.SplitN(request.Name, "/", 2) + node = "" ) if request.Name == "" { request.Name = stringid.GenerateRandomID() + } else if len(parts) == 2 { + node = parts[0] + request.Name = parts[1] } + if node == "" { + c.RLock() + for _, e := range c.engines { + wg.Add(1) - c.RLock() - for _, e := range c.engines { - wg.Add(1) + go func(engine *cluster.Engine) { + defer wg.Done() - go func(engine *cluster.Engine) { - defer wg.Done() + v, er := engine.CreateVolume(request) + if v != nil { + volume = v + err = nil + } + if er != nil && volume == nil { + err = er + } + }(e) + } + c.RUnlock() - v, er := engine.CreateVolume(request) + wg.Wait() + } else { + config := cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:node==" + parts[0]}}) + nodes, err := c.scheduler.SelectNodesForContainer(c.listNodes(), config) + if err != nil { + return nil, err + } + if nodes != nil { + v, er := c.engines[nodes[0].ID].CreateVolume(request) if v != nil { volume = v err = nil @@ -493,11 +530,8 @@ func (c *Cluster) CreateVolume(request *dockerclient.VolumeCreateRequest) (*clus if er != nil && volume == nil { err = er } - }(e) + } } - c.RUnlock() - - wg.Wait() return volume, err } @@ -511,14 +545,12 @@ func (c *Cluster) RemoveVolumes(name string) (bool, error) { errs := []string{} var err error for _, e := range c.engines { - for _, volume := range e.Volumes() { - if volume.Name == name { - if err := volume.Engine.RemoveVolume(name); err != nil { - errs = append(errs, fmt.Sprintf("%s: %s", volume.Engine.Name, err.Error())) - continue - } - found = true + if volume := e.Volumes().Get(name); volume != nil { + if err := volume.Engine.RemoveVolume(volume.Name); err != nil { + errs = append(errs, fmt.Sprintf("%s: %s", volume.Engine.Name, err.Error())) + continue } + found = true } } if len(errs) > 0 { @@ -723,11 +755,11 @@ func (c *Cluster) Networks() cluster.Networks { } // Volumes returns all the volumes in the cluster. -func (c *Cluster) Volumes() []*cluster.Volume { +func (c *Cluster) Volumes() cluster.Volumes { c.RLock() defer c.RUnlock() - out := []*cluster.Volume{} + out := cluster.Volumes{} for _, e := range c.engines { out = append(out, e.Volumes()...) } @@ -735,26 +767,6 @@ func (c *Cluster) Volumes() []*cluster.Volume { return out } -// Volume returns the volume name in the cluster -func (c *Cluster) Volume(name string) *cluster.Volume { - // Abort immediately if the name is empty. - if len(name) == 0 { - return nil - } - - c.RLock() - defer c.RUnlock() - - for _, e := range c.engines { - for _, v := range e.Volumes() { - if v.Name == name { - return v - } - } - } - return nil -} - // listNodes returns all validated engines in the cluster, excluding pendingEngines. func (c *Cluster) listNodes() []*node.Node { c.RLock() diff --git a/cluster/volume.go b/cluster/volume.go index ba31547431..394d097bf6 100644 --- a/cluster/volume.go +++ b/cluster/volume.go @@ -8,3 +8,42 @@ type Volume struct { Engine *Engine } + +// Volumes represents an array of volumes +type Volumes []*Volume + +// Get returns a volume using it's ID or Name +func (volumes Volumes) Get(name string) *Volume { + // Abort immediately if the name is empty. + if len(name) == 0 { + return nil + } + + candidates := []*Volume{} + + // Match name, /name or engine/name. + for _, volume := range volumes { + if volume.Name == name || volume.Engine.ID+"/"+volume.Name == name || volume.Engine.Name+"/"+volume.Name == name { + candidates = append(candidates, volume) + } + } + + if size := len(candidates); size == 1 { + return candidates[0] + } else if size > 1 { + return nil + } + + // Match name, /name or engine/name. + for _, volume := range volumes { + if volume.Name == "/"+name { + return volume + } + } + + if len(candidates) == 1 { + return candidates[0] + } + + return nil +} diff --git a/test/integration/api/volume.bats b/test/integration/api/volume.bats index 917c7017f6..66e6e0aada 100644 --- a/test/integration/api/volume.bats +++ b/test/integration/api/volume.bats @@ -32,10 +32,11 @@ function teardown() { swarm_manage # run - docker_swarm run -d -v=/tmp busybox true + docker_swarm run -d -v=/tmp -e constraint:node==node-0 busybox true run docker_swarm volume ls -q [ "${#lines[@]}" -eq 1 ] + [[ "${output}" == *"node-0/"* ]] run docker_swarm volume inspect ${output} [ "${#lines[@]}" -eq 7 ] @@ -56,6 +57,13 @@ function teardown() { docker_swarm run -d -v=/tmp busybox true run docker_swarm volume ls [ "${#lines[@]}" -eq 4 ] + + run docker_swarm volume create --name=node-2/test_volume2 + [ "$status" -ne 0 ] + + docker_swarm volume create --name=node-0/test_volume2 + run docker_swarm volume ls + [ "${#lines[@]}" -eq 5 ] } @test "docker volume rm" { From 13d2212a3ad748d7568923017c411e193473a76e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Sat, 16 Jan 2016 20:29:59 -0800 Subject: [PATCH 2/3] rename 2 handlers Signed-off-by: Victor Vieux --- api/handlers.go | 44 ++++++++++++++++++++++---------------------- api/primary.go | 4 ++-- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/api/handlers.go b/api/handlers.go index 86e3438fbb..0adcd8ed62 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -221,6 +221,28 @@ func getNetworks(c *context, w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(out) } +// GET /networks/{networkid:.*} +func getNetwork(c *context, w http.ResponseWriter, r *http.Request) { + var id = mux.Vars(r)["networkid"] + if network := c.cluster.Networks().Uniq().Get(id); network != nil { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(network) + return + } + httpError(w, fmt.Sprintf("No such network: %s", id), http.StatusNotFound) +} + +// GET /volumes/{volumename:.*} +func getVolume(c *context, w http.ResponseWriter, r *http.Request) { + var name = mux.Vars(r)["volumename"] + if volume := c.cluster.Volumes().Get(name); volume != nil { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(volume) + return + } + httpError(w, fmt.Sprintf("No such volume: %s", name), http.StatusNotFound) +} + // GET /volumes func getVolumes(c *context, w http.ResponseWriter, r *http.Request) { volumes := struct{ Volumes []*dockerclient.Volume }{} @@ -800,28 +822,6 @@ func ping(c *context, w http.ResponseWriter, r *http.Request) { w.Write([]byte{'O', 'K'}) } -// Proxy a request to the right node -func proxyNetwork(c *context, w http.ResponseWriter, r *http.Request) { - var id = mux.Vars(r)["networkid"] - if network := c.cluster.Networks().Uniq().Get(id); network != nil { - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(network) - return - } - httpError(w, fmt.Sprintf("No such network: %s", id), http.StatusNotFound) -} - -// Proxy a request to the right node -func proxyVolume(c *context, w http.ResponseWriter, r *http.Request) { - var name = mux.Vars(r)["volumename"] - if volume := c.cluster.Volumes().Get(name); volume != nil { - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(volume) - return - } - httpError(w, fmt.Sprintf("No such volume: %s", name), http.StatusNotFound) -} - // Proxy network to container operations, including connect/disconnect request func proxyNetworkContainerOperation(c *context, w http.ResponseWriter, r *http.Request) { var networkid = mux.Vars(r)["networkid"] diff --git a/api/primary.go b/api/primary.go index 4fcfc7f98b..4046c15205 100644 --- a/api/primary.go +++ b/api/primary.go @@ -50,9 +50,9 @@ var routes = map[string]map[string]handler{ "/containers/{name:.*}/attach/ws": proxyHijack, "/exec/{execid:.*}/json": proxyContainer, "/networks": getNetworks, - "/networks/{networkid:.*}": proxyNetwork, + "/networks/{networkid:.*}": getNetwork, "/volumes": getVolumes, - "/volumes/{volumename:.*}": proxyVolume, + "/volumes/{volumename:.*}": getVolume, }, "POST": { "/auth": proxyRandom, From c6de0c3dc6537efea2fd05045af4c8dedc45c67f Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Sat, 16 Jan 2016 21:08:00 -0800 Subject: [PATCH 3/3] update some comments Signed-off-by: Victor Vieux --- cluster/network.go | 5 +++-- cluster/volume.go | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cluster/network.go b/cluster/network.go index ca02a2f1d6..e7b2a5fa3b 100644 --- a/cluster/network.go +++ b/cluster/network.go @@ -67,20 +67,21 @@ func (networks Networks) Get(IDOrName string) *Network { candidates := []*Network{} - // Match name, /name or engine/name. + // Match name or engine/name. for _, network := range networks { if network.Name == IDOrName || network.Engine.ID+"/"+network.Name == IDOrName || network.Engine.Name+"/"+network.Name == IDOrName { candidates = append(candidates, network) } } + // Return if we found a unique match. if size := len(candidates); size == 1 { return candidates[0] } else if size > 1 { return nil } - // Match name, /name or engine/name. + // Match /name and return as soon as we find one. for _, network := range networks { if network.Name == "/"+IDOrName { return network diff --git a/cluster/volume.go b/cluster/volume.go index 394d097bf6..ecf832b59e 100644 --- a/cluster/volume.go +++ b/cluster/volume.go @@ -21,20 +21,21 @@ func (volumes Volumes) Get(name string) *Volume { candidates := []*Volume{} - // Match name, /name or engine/name. + // Match name or engine/name. for _, volume := range volumes { if volume.Name == name || volume.Engine.ID+"/"+volume.Name == name || volume.Engine.Name+"/"+volume.Name == name { candidates = append(candidates, volume) } } + // Return if we found a unique match. if size := len(candidates); size == 1 { return candidates[0] } else if size > 1 { return nil } - // Match name, /name or engine/name. + // Match /name and return as soon as we find one. for _, volume := range volumes { if volume.Name == "/"+name { return volume