Return error in case docker network inspect is ambiguous

This fix is partially based on comment
https://github.com/docker/docker/issues/30242#issuecomment-273517205

Currently, `docker network inspect` relies on `FindNetwork()` which
does not take into consideration that multiple networks with the same
name might exist.

This fix propose to return `docker network inspect` in a similiar
fashion like other commands:
1. Lookup full ID
2. Lookup full name
3. Lookup partial ID
If multiple networks exist, an error will be returned.

NOTE: this fix is not a complete fix for the issue raised in
https://github.com/docker/docker/issues/30242#issuecomment-273517205
where SwarmKit is unable to update when multiple networks with the same
name exit.
To fix that issue requires multiple places when `FindNetwork()` is called.
Because of the impact of changing `FindNetwork()`, this fix focus on
the issue in `docker network inspect`.

A separate PR will be created to address
https://github.com/docker/docker/issues/30242#issuecomment-273517205

An integration test has been added.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
This commit is contained in:
Yong Tang 2017-01-19 03:04:26 -08:00
parent 14790e4008
commit abf31ee083
3 changed files with 149 additions and 8 deletions

View file

@ -11,8 +11,6 @@ import (
// to provide network specific functionality. // to provide network specific functionality.
type Backend interface { type Backend interface {
FindNetwork(idName string) (libnetwork.Network, error) FindNetwork(idName string) (libnetwork.Network, error)
GetNetworkByName(idName string) (libnetwork.Network, error)
GetNetworksByID(partialID string) []libnetwork.Network
GetNetworks() []libnetwork.Network GetNetworks() []libnetwork.Network
CreateNetwork(nc types.NetworkCreateRequest) (*types.NetworkCreateResponse, error) CreateNetwork(nc types.NetworkCreateRequest) (*types.NetworkCreateResponse, error)
ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error

View file

@ -2,7 +2,9 @@ package network
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"strings"
"golang.org/x/net/context" "golang.org/x/net/context"
@ -82,14 +84,80 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
return err return err
} }
nw, err := n.backend.FindNetwork(vars["id"]) term := vars["id"]
if err != nil {
if nr, err := n.cluster.GetNetwork(vars["id"]); err == nil { // In case multiple networks have duplicate names, return error.
return httputils.WriteJSON(w, http.StatusOK, nr) // TODO (yongtang): should we wrap with version here for backward compatibility?
// First find based on full ID, return immediately once one is found.
// If a network appears both in swarm and local, assume it is in local first
// For full name and partial ID, save the result first, and process later
// in case multiple records was found based on the same term
listByFullName := map[string]types.NetworkResource{}
listByPartialID := map[string]types.NetworkResource{}
nw := n.backend.GetNetworks()
for _, network := range nw {
if network.ID() == term {
return httputils.WriteJSON(w, http.StatusOK, *n.buildDetailedNetworkResources(network))
}
if network.Name() == term {
// No need to check the ID collision here as we are still in
// local scope and the network ID is unique in this scope.
listByFullName[network.ID()] = *n.buildDetailedNetworkResources(network)
}
if strings.HasPrefix(network.ID(), term) {
// No need to check the ID collision here as we are still in
// local scope and the network ID is unique in this scope.
listByPartialID[network.ID()] = *n.buildDetailedNetworkResources(network)
} }
return err
} }
return httputils.WriteJSON(w, http.StatusOK, n.buildDetailedNetworkResources(nw))
nr, _ := n.cluster.GetNetworks()
for _, network := range nr {
if network.ID == term {
return httputils.WriteJSON(w, http.StatusOK, network)
}
if network.Name == term {
// Check the ID collision as we are in swarm scope here, and
// the map (of the listByFullName) may have already had a
// network with the same ID (from local scope previously)
if _, ok := listByFullName[network.ID]; !ok {
listByFullName[network.ID] = network
}
}
if strings.HasPrefix(network.ID, term) {
// Check the ID collision as we are in swarm scope here, and
// the map (of the listByPartialID) may have already had a
// network with the same ID (from local scope previously)
if _, ok := listByPartialID[network.ID]; !ok {
listByPartialID[network.ID] = network
}
}
}
// Find based on full name, returns true only if no duplicates
if len(listByFullName) == 1 {
for _, v := range listByFullName {
return httputils.WriteJSON(w, http.StatusOK, v)
}
}
if len(listByFullName) > 1 {
return fmt.Errorf("network %s is ambiguous (%d matches found based on name)", term, len(listByFullName))
}
// Find based on partial ID, returns true only if no duplicates
if len(listByPartialID) == 1 {
for _, v := range listByPartialID {
return httputils.WriteJSON(w, http.StatusOK, v)
}
}
if len(listByPartialID) > 1 {
return fmt.Errorf("network %s is ambiguous (%d matches found based on ID prefix)", term, len(listByPartialID))
}
return libnetwork.ErrNoSuchNetwork(term)
} }
func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {

View file

@ -14,6 +14,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/integration-cli/checker" "github.com/docker/docker/integration-cli/checker"
"github.com/docker/docker/integration-cli/daemon" "github.com/docker/docker/integration-cli/daemon"
@ -1665,3 +1666,77 @@ func (s *DockerSwarmSuite) TestSwarmReadonlyRootfs(c *check.C) {
c.Assert(err, checker.IsNil, check.Commentf(out)) c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(strings.TrimSpace(out), checker.Equals, "true") c.Assert(strings.TrimSpace(out), checker.Equals, "true")
} }
func (s *DockerSwarmSuite) TestNetworkInspectWithDuplicateNames(c *check.C) {
d := s.AddDaemon(c, true, true)
name := "foo"
networkCreateRequest := types.NetworkCreateRequest{
Name: name,
NetworkCreate: types.NetworkCreate{
CheckDuplicate: false,
Driver: "bridge",
},
}
var n1 types.NetworkCreateResponse
status, body, err := d.SockRequest("POST", "/networks/create", networkCreateRequest)
c.Assert(err, checker.IsNil, check.Commentf(string(body)))
c.Assert(status, checker.Equals, http.StatusCreated, check.Commentf(string(body)))
c.Assert(json.Unmarshal(body, &n1), checker.IsNil)
// Full ID always works
out, err := d.Cmd("network", "inspect", "--format", "{{.ID}}", n1.ID)
c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(strings.TrimSpace(out), checker.Equals, n1.ID)
// Name works if it is unique
out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", name)
c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(strings.TrimSpace(out), checker.Equals, n1.ID)
var n2 types.NetworkCreateResponse
status, body, err = d.SockRequest("POST", "/networks/create", networkCreateRequest)
c.Assert(err, checker.IsNil, check.Commentf(string(body)))
c.Assert(status, checker.Equals, http.StatusCreated, check.Commentf(string(body)))
c.Assert(json.Unmarshal(body, &n2), checker.IsNil)
// Full ID always works
out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", n1.ID)
c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(strings.TrimSpace(out), checker.Equals, n1.ID)
out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", n2.ID)
c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(strings.TrimSpace(out), checker.Equals, n2.ID)
// Name with duplicates
out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", name)
c.Assert(err, checker.NotNil, check.Commentf(out))
c.Assert(out, checker.Contains, "network foo is ambiguous (2 matches found based on name)")
out, err = d.Cmd("network", "rm", n2.ID)
c.Assert(err, checker.IsNil, check.Commentf(out))
// Dupliates with name but with different driver
networkCreateRequest.NetworkCreate.Driver = "overlay"
status, body, err = d.SockRequest("POST", "/networks/create", networkCreateRequest)
c.Assert(err, checker.IsNil, check.Commentf(string(body)))
c.Assert(status, checker.Equals, http.StatusCreated, check.Commentf(string(body)))
c.Assert(json.Unmarshal(body, &n2), checker.IsNil)
// Full ID always works
out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", n1.ID)
c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(strings.TrimSpace(out), checker.Equals, n1.ID)
out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", n2.ID)
c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(strings.TrimSpace(out), checker.Equals, n2.ID)
// Name with duplicates
out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", name)
c.Assert(err, checker.NotNil, check.Commentf(out))
c.Assert(out, checker.Contains, "network foo is ambiguous (2 matches found based on name)")
}