Ver código fonte

Merge pull request #30897 from yongtang/30242-network-duplicate-names

Update `FindNetwork` to address network name duplications
Yong Tang 7 anos atrás
pai
commit
484612f388

+ 2 - 2
api/server/router/network/backend.go

@@ -12,11 +12,11 @@ import (
 // Backend is all the methods that need to be implemented
 // Backend is all the methods that need to be implemented
 // to provide network specific functionality.
 // to provide network specific functionality.
 type Backend interface {
 type Backend interface {
-	FindNetwork(idName string) (libnetwork.Network, error)
+	FindUniqueNetwork(idName string) (libnetwork.Network, error)
 	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
 	DisconnectContainerFromNetwork(containerName string, networkName string, force bool) error
 	DisconnectContainerFromNetwork(containerName string, networkName string, force bool) error
-	DeleteNetwork(name string) error
+	DeleteNetwork(networkID string) error
 	NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error)
 	NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error)
 }
 }

+ 94 - 8
api/server/router/network/network_routes.go

@@ -2,6 +2,7 @@ package network
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
+	"fmt"
 	"net/http"
 	"net/http"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
@@ -288,7 +289,12 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
 		return err
 		return err
 	}
 	}
 
 
-	return n.backend.ConnectContainerToNetwork(connect.Container, vars["id"], connect.EndpointConfig)
+	// Always make sure there is no ambiguity with respect to the network ID/name
+	nw, err := n.backend.FindUniqueNetwork(vars["id"])
+	if err != nil {
+		return err
+	}
+	return n.backend.ConnectContainerToNetwork(connect.Container, nw.ID(), connect.EndpointConfig)
 }
 }
 
 
 func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
@@ -312,15 +318,19 @@ func (n *networkRouter) deleteNetwork(ctx context.Context, w http.ResponseWriter
 	if err := httputils.ParseForm(r); err != nil {
 	if err := httputils.ParseForm(r); err != nil {
 		return err
 		return err
 	}
 	}
-	if _, err := n.cluster.GetNetwork(vars["id"]); err == nil {
-		if err = n.cluster.RemoveNetwork(vars["id"]); err != nil {
+
+	nw, err := n.findUniqueNetwork(vars["id"])
+	if err != nil {
+		return err
+	}
+	if nw.Scope == "swarm" {
+		if err = n.cluster.RemoveNetwork(nw.ID); err != nil {
+			return err
+		}
+	} else {
+		if err := n.backend.DeleteNetwork(nw.ID); err != nil {
 			return err
 			return err
 		}
 		}
-		w.WriteHeader(http.StatusNoContent)
-		return nil
-	}
-	if err := n.backend.DeleteNetwork(vars["id"]); err != nil {
-		return err
 	}
 	}
 	w.WriteHeader(http.StatusNoContent)
 	w.WriteHeader(http.StatusNoContent)
 	return nil
 	return nil
@@ -518,3 +528,79 @@ func (n *networkRouter) postNetworksPrune(ctx context.Context, w http.ResponseWr
 	}
 	}
 	return httputils.WriteJSON(w, http.StatusOK, pruneReport)
 	return httputils.WriteJSON(w, http.StatusOK, pruneReport)
 }
 }
+
+// findUniqueNetwork will search network across different scopes (both local and swarm).
+// NOTE: This findUniqueNetwork is different from FindUniqueNetwork in the daemon.
+// In case multiple networks have duplicate names, return error.
+// 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
+// TODO (yongtang): should we wrap with version here for backward compatibility?
+func (n *networkRouter) findUniqueNetwork(term string) (types.NetworkResource, error) {
+	listByFullName := map[string]types.NetworkResource{}
+	listByPartialID := map[string]types.NetworkResource{}
+
+	nw := n.backend.GetNetworks()
+	for _, network := range nw {
+		if network.ID() == term {
+			return *n.buildDetailedNetworkResources(network, false), nil
+
+		}
+		if network.Name() == term && !network.Info().Ingress() {
+			// 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, false)
+		}
+		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, false)
+		}
+	}
+
+	nr, _ := n.cluster.GetNetworks()
+	for _, network := range nr {
+		if network.ID == term {
+			return network, nil
+		}
+		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 v, nil
+		}
+	}
+	if len(listByFullName) > 1 {
+		return types.NetworkResource{}, 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 v, nil
+		}
+	}
+	if len(listByPartialID) > 1 {
+		return types.NetworkResource{}, fmt.Errorf("network %s is ambiguous (%d matches found based on ID prefix)", term, len(listByPartialID))
+	}
+
+	return types.NetworkResource{}, libnetwork.ErrNoSuchNetwork(term)
+}

+ 2 - 2
daemon/cluster/executor/backend.go

@@ -27,8 +27,8 @@ import (
 // Backend defines the executor component for a swarm agent.
 // Backend defines the executor component for a swarm agent.
 type Backend interface {
 type Backend interface {
 	CreateManagedNetwork(clustertypes.NetworkCreateRequest) error
 	CreateManagedNetwork(clustertypes.NetworkCreateRequest) error
-	DeleteManagedNetwork(name string) error
-	FindNetwork(idName string) (libnetwork.Network, error)
+	DeleteManagedNetwork(networkID string) error
+	FindUniqueNetwork(idName string) (libnetwork.Network, error)
 	SetupIngress(clustertypes.NetworkCreateRequest, string) (<-chan struct{}, error)
 	SetupIngress(clustertypes.NetworkCreateRequest, string) (<-chan struct{}, error)
 	ReleaseIngress() (<-chan struct{}, error)
 	ReleaseIngress() (<-chan struct{}, error)
 	PullImage(ctx context.Context, image, tag, platform string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
 	PullImage(ctx context.Context, image, tag, platform string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error

+ 5 - 5
daemon/cluster/executor/container/adapter.go

@@ -143,8 +143,8 @@ func (c *containerAdapter) pullImage(ctx context.Context) error {
 }
 }
 
 
 func (c *containerAdapter) createNetworks(ctx context.Context) error {
 func (c *containerAdapter) createNetworks(ctx context.Context) error {
-	for _, network := range c.container.networks() {
-		ncr, err := c.container.networkCreateRequest(network)
+	for name := range c.container.networksAttachments {
+		ncr, err := c.container.networkCreateRequest(name)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -162,15 +162,15 @@ func (c *containerAdapter) createNetworks(ctx context.Context) error {
 }
 }
 
 
 func (c *containerAdapter) removeNetworks(ctx context.Context) error {
 func (c *containerAdapter) removeNetworks(ctx context.Context) error {
-	for _, nid := range c.container.networks() {
-		if err := c.backend.DeleteManagedNetwork(nid); err != nil {
+	for name, v := range c.container.networksAttachments {
+		if err := c.backend.DeleteManagedNetwork(v.Network.ID); err != nil {
 			switch err.(type) {
 			switch err.(type) {
 			case *libnetwork.ActiveEndpointsError:
 			case *libnetwork.ActiveEndpointsError:
 				continue
 				continue
 			case libnetwork.ErrNoSuchNetwork:
 			case libnetwork.ErrNoSuchNetwork:
 				continue
 				continue
 			default:
 			default:
-				log.G(ctx).Errorf("network %s remove failed: %v", nid, err)
+				log.G(ctx).Errorf("network %s remove failed: %v", name, err)
 				return err
 				return err
 			}
 			}
 		}
 		}

+ 1 - 14
daemon/cluster/executor/container/container.go

@@ -507,7 +507,7 @@ func getEndpointConfig(na *api.NetworkAttachment, b executorpkg.Backend) *networ
 		DriverOpts: na.DriverAttachmentOpts,
 		DriverOpts: na.DriverAttachmentOpts,
 	}
 	}
 	if v, ok := na.Network.Spec.Annotations.Labels["com.docker.swarm.predefined"]; ok && v == "true" {
 	if v, ok := na.Network.Spec.Annotations.Labels["com.docker.swarm.predefined"]; ok && v == "true" {
-		if ln, err := b.FindNetwork(na.Network.Spec.Annotations.Name); err == nil {
+		if ln, err := b.FindUniqueNetwork(na.Network.Spec.Annotations.Name); err == nil {
 			n.NetworkID = ln.ID()
 			n.NetworkID = ln.ID()
 		}
 		}
 	}
 	}
@@ -575,19 +575,6 @@ func (c *containerConfig) serviceConfig() *clustertypes.ServiceConfig {
 	return svcCfg
 	return svcCfg
 }
 }
 
 
-// networks returns a list of network names attached to the container. The
-// returned name can be used to lookup the corresponding network create
-// options.
-func (c *containerConfig) networks() []string {
-	var networks []string
-
-	for name := range c.networksAttachments {
-		networks = append(networks, name)
-	}
-
-	return networks
-}
-
 func (c *containerConfig) networkCreateRequest(name string) (clustertypes.NetworkCreateRequest, error) {
 func (c *containerConfig) networkCreateRequest(name string) (clustertypes.NetworkCreateRequest, error) {
 	na, ok := c.networksAttachments[name]
 	na, ok := c.networksAttachments[name]
 	if !ok {
 	if !ok {

+ 1 - 1
daemon/cluster/networks.go

@@ -292,7 +292,7 @@ func (c *Cluster) populateNetworkID(ctx context.Context, client swarmapi.Control
 	for i, n := range networks {
 	for i, n := range networks {
 		apiNetwork, err := getNetwork(ctx, client, n.Target)
 		apiNetwork, err := getNetwork(ctx, client, n.Target)
 		if err != nil {
 		if err != nil {
-			ln, _ := c.config.Backend.FindNetwork(n.Target)
+			ln, _ := c.config.Backend.FindUniqueNetwork(n.Target)
 			if ln != nil && runconfig.IsPreDefinedNetwork(ln.Name()) {
 			if ln != nil && runconfig.IsPreDefinedNetwork(ln.Name()) {
 				// Need to retrieve the corresponding predefined swarm network
 				// Need to retrieve the corresponding predefined swarm network
 				// and use its id for the request.
 				// and use its id for the request.

+ 22 - 13
daemon/container_operations.go

@@ -251,8 +251,8 @@ func (daemon *Daemon) updateNetworkSettings(container *container.Container, n li
 		return runconfig.ErrConflictHostNetwork
 		return runconfig.ErrConflictHostNetwork
 	}
 	}
 
 
-	for s := range container.NetworkSettings.Networks {
-		sn, err := daemon.FindNetwork(s)
+	for s, v := range container.NetworkSettings.Networks {
+		sn, err := daemon.FindUniqueNetwork(getNetworkID(s, v.EndpointSettings))
 		if err != nil {
 		if err != nil {
 			continue
 			continue
 		}
 		}
@@ -308,8 +308,8 @@ func (daemon *Daemon) updateNetwork(container *container.Container) error {
 
 
 	// Find if container is connected to the default bridge network
 	// Find if container is connected to the default bridge network
 	var n libnetwork.Network
 	var n libnetwork.Network
-	for name := range container.NetworkSettings.Networks {
-		sn, err := daemon.FindNetwork(name)
+	for name, v := range container.NetworkSettings.Networks {
+		sn, err := daemon.FindUniqueNetwork(getNetworkID(name, v.EndpointSettings))
 		if err != nil {
 		if err != nil {
 			continue
 			continue
 		}
 		}
@@ -339,7 +339,7 @@ func (daemon *Daemon) updateNetwork(container *container.Container) error {
 }
 }
 
 
 func (daemon *Daemon) findAndAttachNetwork(container *container.Container, idOrName string, epConfig *networktypes.EndpointSettings) (libnetwork.Network, *networktypes.NetworkingConfig, error) {
 func (daemon *Daemon) findAndAttachNetwork(container *container.Container, idOrName string, epConfig *networktypes.EndpointSettings) (libnetwork.Network, *networktypes.NetworkingConfig, error) {
-	n, err := daemon.FindNetwork(idOrName)
+	n, err := daemon.FindUniqueNetwork(getNetworkID(idOrName, epConfig))
 	if err != nil {
 	if err != nil {
 		// We should always be able to find the network for a
 		// We should always be able to find the network for a
 		// managed container.
 		// managed container.
@@ -377,16 +377,16 @@ func (daemon *Daemon) findAndAttachNetwork(container *container.Container, idOrN
 		// trigger attachment in the swarm cluster manager.
 		// trigger attachment in the swarm cluster manager.
 		if daemon.clusterProvider != nil {
 		if daemon.clusterProvider != nil {
 			var err error
 			var err error
-			config, err = daemon.clusterProvider.AttachNetwork(idOrName, container.ID, addresses)
+			config, err = daemon.clusterProvider.AttachNetwork(getNetworkID(idOrName, epConfig), container.ID, addresses)
 			if err != nil {
 			if err != nil {
 				return nil, nil, err
 				return nil, nil, err
 			}
 			}
 		}
 		}
 
 
-		n, err = daemon.FindNetwork(idOrName)
+		n, err = daemon.FindUniqueNetwork(getNetworkID(idOrName, epConfig))
 		if err != nil {
 		if err != nil {
 			if daemon.clusterProvider != nil {
 			if daemon.clusterProvider != nil {
-				if err := daemon.clusterProvider.DetachNetwork(idOrName, container.ID); err != nil {
+				if err := daemon.clusterProvider.DetachNetwork(getNetworkID(idOrName, epConfig), container.ID); err != nil {
 					logrus.Warnf("Could not rollback attachment for container %s to network %s: %v", container.ID, idOrName, err)
 					logrus.Warnf("Could not rollback attachment for container %s to network %s: %v", container.ID, idOrName, err)
 				}
 				}
 			}
 			}
@@ -437,7 +437,7 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
 	if mode.IsUserDefined() {
 	if mode.IsUserDefined() {
 		var err error
 		var err error
 
 
-		n, err = daemon.FindNetwork(networkName)
+		n, err = daemon.FindUniqueNetwork(networkName)
 		if err == nil {
 		if err == nil {
 			networkName = n.Name()
 			networkName = n.Name()
 		}
 		}
@@ -797,7 +797,7 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
 
 
 // ForceEndpointDelete deletes an endpoint from a network forcefully
 // ForceEndpointDelete deletes an endpoint from a network forcefully
 func (daemon *Daemon) ForceEndpointDelete(name string, networkName string) error {
 func (daemon *Daemon) ForceEndpointDelete(name string, networkName string) error {
-	n, err := daemon.FindNetwork(networkName)
+	n, err := daemon.FindUniqueNetwork(networkName)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -949,7 +949,7 @@ func (daemon *Daemon) releaseNetwork(container *container.Container) {
 
 
 	var networks []libnetwork.Network
 	var networks []libnetwork.Network
 	for n, epSettings := range settings {
 	for n, epSettings := range settings {
-		if nw, err := daemon.FindNetwork(n); err == nil {
+		if nw, err := daemon.FindUniqueNetwork(getNetworkID(n, epSettings.EndpointSettings)); err == nil {
 			networks = append(networks, nw)
 			networks = append(networks, nw)
 		}
 		}
 
 
@@ -993,7 +993,7 @@ func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName
 			return errRemovalContainer(container.ID)
 			return errRemovalContainer(container.ID)
 		}
 		}
 
 
-		n, err := daemon.FindNetwork(idOrName)
+		n, err := daemon.FindUniqueNetwork(idOrName)
 		if err == nil && n != nil {
 		if err == nil && n != nil {
 			if err := daemon.updateNetworkConfig(container, n, endpointConfig, true); err != nil {
 			if err := daemon.updateNetworkConfig(container, n, endpointConfig, true); err != nil {
 				return err
 				return err
@@ -1016,7 +1016,7 @@ func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName
 
 
 // DisconnectFromNetwork disconnects container from network n.
 // DisconnectFromNetwork disconnects container from network n.
 func (daemon *Daemon) DisconnectFromNetwork(container *container.Container, networkName string, force bool) error {
 func (daemon *Daemon) DisconnectFromNetwork(container *container.Container, networkName string, force bool) error {
-	n, err := daemon.FindNetwork(networkName)
+	n, err := daemon.FindUniqueNetwork(networkName)
 	container.Lock()
 	container.Lock()
 	defer container.Unlock()
 	defer container.Unlock()
 
 
@@ -1087,3 +1087,12 @@ func (daemon *Daemon) DeactivateContainerServiceBinding(containerName string) er
 	}
 	}
 	return sb.DisableService()
 	return sb.DisableService()
 }
 }
+
+func getNetworkID(name string, endpointSettings *networktypes.EndpointSettings) string {
+	// We only want to prefer NetworkID for user defined networks.
+	// For systems like bridge, none, etc. the name is preferred (otherwise restart may cause issues)
+	if containertypes.NetworkMode(name).IsUserDefined() && endpointSettings != nil && endpointSettings.NetworkID != "" {
+		return endpointSettings.NetworkID
+	}
+	return name
+}

+ 1 - 1
daemon/container_operations_windows.go

@@ -170,7 +170,7 @@ func (daemon *Daemon) initializeNetworkingPaths(container *container.Container,
 
 
 	if nc.NetworkSettings != nil {
 	if nc.NetworkSettings != nil {
 		for n := range nc.NetworkSettings.Networks {
 		for n := range nc.NetworkSettings.Networks {
-			sn, err := daemon.FindNetwork(n)
+			sn, err := daemon.FindUniqueNetwork(n)
 			if err != nil {
 			if err != nil {
 				continue
 				continue
 			}
 			}

+ 1 - 1
daemon/daemon_test.go

@@ -317,7 +317,7 @@ func TestValidateContainerIsolation(t *testing.T) {
 
 
 func TestFindNetworkErrorType(t *testing.T) {
 func TestFindNetworkErrorType(t *testing.T) {
 	d := Daemon{}
 	d := Daemon{}
-	_, err := d.FindNetwork("fakeNet")
+	_, err := d.FindUniqueNetwork("fakeNet")
 	_, ok := errors.Cause(err).(libnetwork.ErrNoSuchNetwork)
 	_, ok := errors.Cause(err).(libnetwork.ErrNoSuchNetwork)
 	if !errdefs.IsNotFound(err) || !ok {
 	if !errdefs.IsNotFound(err) || !ok {
 		assert.Fail(t, "The FindNetwork method MUST always return an error that implements the NotFound interface and is ErrNoSuchNetwork")
 		assert.Fail(t, "The FindNetwork method MUST always return an error that implements the NotFound interface and is ErrNoSuchNetwork")

+ 0 - 17
daemon/errors.go

@@ -226,20 +226,3 @@ func translateContainerdStartErr(cmd string, setExitCode func(int), err error) e
 	// TODO: it would be nice to get some better errors from containerd so we can return better errors here
 	// TODO: it would be nice to get some better errors from containerd so we can return better errors here
 	return retErr
 	return retErr
 }
 }
-
-// TODO: cpuguy83 take care of it once the new library is ready
-type errNotFound struct{ error }
-
-func (errNotFound) NotFound() {}
-
-func (e errNotFound) Cause() error {
-	return e.error
-}
-
-// notFound is a helper to create an error of the class with the same name from any error type
-func notFound(err error) error {
-	if err == nil {
-		return nil
-	}
-	return errNotFound{err}
-}

+ 76 - 39
daemon/network.go

@@ -29,36 +29,36 @@ func (daemon *Daemon) NetworkControllerEnabled() bool {
 	return daemon.netController != nil
 	return daemon.netController != nil
 }
 }
 
 
-// FindNetwork function finds a network for a given string that can represent network name or id
-func (daemon *Daemon) FindNetwork(idName string) (libnetwork.Network, error) {
-	// 1. match by full ID.
-	n, err := daemon.GetNetworkByID(idName)
-	if err == nil || !isNoSuchNetworkError(err) {
-		return n, err
-	}
-
-	// 2. match by full name
-	n, err = daemon.GetNetworkByName(idName)
-	if err == nil || !isNoSuchNetworkError(err) {
-		return n, err
-	}
-
-	// 3. match by ID prefix
-	list := daemon.GetNetworksByIDPrefix(idName)
-	if len(list) == 0 {
-		// Be very careful to change the error type here, the libnetwork.ErrNoSuchNetwork error is used by the controller
-		// to retry the creation of the network as managed through the swarm manager
-		return nil, errors.WithStack(notFound(libnetwork.ErrNoSuchNetwork(idName)))
-	}
-	if len(list) > 1 {
-		return nil, errors.WithStack(invalidIdentifier(idName))
+// FindUniqueNetwork returns a network based on:
+// 1. Full ID
+// 2. Full Name
+// 3. Partial ID
+// as long as there is no ambiguity
+func (daemon *Daemon) FindUniqueNetwork(term string) (libnetwork.Network, error) {
+	listByFullName := []libnetwork.Network{}
+	listByPartialID := []libnetwork.Network{}
+	for _, nw := range daemon.GetNetworks() {
+		if nw.ID() == term {
+			return nw, nil
+		}
+		if nw.Name() == term {
+			listByFullName = append(listByFullName, nw)
+		}
+		if strings.HasPrefix(nw.ID(), term) {
+			listByPartialID = append(listByPartialID, nw)
+		}
 	}
 	}
-	return list[0], nil
-}
-
-func isNoSuchNetworkError(err error) bool {
-	_, ok := err.(libnetwork.ErrNoSuchNetwork)
-	return ok
+	switch {
+	case len(listByFullName) == 1:
+		return listByFullName[0], nil
+	case len(listByFullName) > 1:
+		return nil, fmt.Errorf("network %s is ambiguous (%d matches found based on name)", term, len(listByFullName))
+	case len(listByPartialID) == 1:
+		return listByPartialID[0], nil
+	case len(listByPartialID) > 1:
+		return nil, fmt.Errorf("network %s is ambiguous (%d matches found based on ID prefix)", term, len(listByPartialID))
+	}
+	return nil, libnetwork.ErrNoSuchNetwork(term)
 }
 }
 
 
 // GetNetworkByID function returns a network whose ID matches the given ID.
 // GetNetworkByID function returns a network whose ID matches the given ID.
@@ -104,7 +104,11 @@ func (daemon *Daemon) GetNetworksByIDPrefix(partialID string) []libnetwork.Netwo
 
 
 // getAllNetworks returns a list containing all networks
 // getAllNetworks returns a list containing all networks
 func (daemon *Daemon) getAllNetworks() []libnetwork.Network {
 func (daemon *Daemon) getAllNetworks() []libnetwork.Network {
-	return daemon.netController.Networks()
+	c := daemon.netController
+	if c == nil {
+		return nil
+	}
+	return c.Networks()
 }
 }
 
 
 type ingressJob struct {
 type ingressJob struct {
@@ -274,7 +278,9 @@ func (daemon *Daemon) createNetwork(create types.NetworkCreateRequest, id string
 		// check if user defined CheckDuplicate, if set true, return err
 		// check if user defined CheckDuplicate, if set true, return err
 		// otherwise prepare a warning message
 		// otherwise prepare a warning message
 		if create.CheckDuplicate {
 		if create.CheckDuplicate {
-			return nil, libnetwork.NetworkNameError(create.Name)
+			if !agent || nw.Info().Dynamic() {
+				return nil, libnetwork.NetworkNameError(create.Name)
+			}
 		}
 		}
 		warning = fmt.Sprintf("Network with name %s (id : %s) already exists", nw.Name(), nw.ID())
 		warning = fmt.Sprintf("Network with name %s (id : %s) already exists", nw.Name(), nw.ID())
 	}
 	}
@@ -464,25 +470,56 @@ func (daemon *Daemon) GetNetworkDriverList() []string {
 }
 }
 
 
 // DeleteManagedNetwork deletes an agent network.
 // DeleteManagedNetwork deletes an agent network.
+// The requirement of networkID is enforced.
 func (daemon *Daemon) DeleteManagedNetwork(networkID string) error {
 func (daemon *Daemon) DeleteManagedNetwork(networkID string) error {
-	return daemon.deleteNetwork(networkID, true)
+	n, err := daemon.GetNetworkByID(networkID)
+	if err != nil {
+		return err
+	}
+	return daemon.deleteNetwork(n, true)
 }
 }
 
 
 // DeleteNetwork destroys a network unless it's one of docker's predefined networks.
 // DeleteNetwork destroys a network unless it's one of docker's predefined networks.
 func (daemon *Daemon) DeleteNetwork(networkID string) error {
 func (daemon *Daemon) DeleteNetwork(networkID string) error {
-	return daemon.deleteNetwork(networkID, false)
-}
-
-func (daemon *Daemon) deleteNetwork(networkID string, dynamic bool) error {
-	nw, err := daemon.FindNetwork(networkID)
+	n, err := daemon.GetNetworkByID(networkID)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
+	return daemon.deleteNetwork(n, false)
+}
 
 
-	if nw.Info().Ingress() {
-		return nil
+func (daemon *Daemon) deleteLoadBalancerSandbox(n libnetwork.Network) {
+	controller := daemon.netController
+
+	//The only endpoint left should be the LB endpoint (nw.Name() + "-endpoint")
+	endpoints := n.Endpoints()
+	if len(endpoints) == 1 {
+		sandboxName := n.Name() + "-sbox"
+
+		info := endpoints[0].Info()
+		if info != nil {
+			sb := info.Sandbox()
+			if sb != nil {
+				if err := sb.DisableService(); err != nil {
+					logrus.Warnf("Failed to disable service on sandbox %s: %v", sandboxName, err)
+					//Ignore error and attempt to delete the load balancer endpoint
+				}
+			}
+		}
+
+		if err := endpoints[0].Delete(true); err != nil {
+			logrus.Warnf("Failed to delete endpoint %s (%s) in %s: %v", endpoints[0].Name(), endpoints[0].ID(), sandboxName, err)
+			//Ignore error and attempt to delete the sandbox.
+		}
+
+		if err := controller.SandboxDestroy(sandboxName); err != nil {
+			logrus.Warnf("Failed to delete %s sandbox: %v", sandboxName, err)
+			//Ignore error and attempt to delete the network.
+		}
 	}
 	}
+}
 
 
+func (daemon *Daemon) deleteNetwork(nw libnetwork.Network, dynamic bool) error {
 	if runconfig.IsPreDefinedNetwork(nw.Name()) && !dynamic {
 	if runconfig.IsPreDefinedNetwork(nw.Name()) && !dynamic {
 		err := fmt.Errorf("%s is a pre-defined network and cannot be removed", nw.Name())
 		err := fmt.Errorf("%s is a pre-defined network and cannot be removed", nw.Name())
 		return notAllowedError{err}
 		return notAllowedError{err}

+ 1 - 1
daemon/oci_windows.go

@@ -159,7 +159,7 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
 	gwHNSID := ""
 	gwHNSID := ""
 	if c.NetworkSettings != nil {
 	if c.NetworkSettings != nil {
 		for n := range c.NetworkSettings.Networks {
 		for n := range c.NetworkSettings.Networks {
-			sn, err := daemon.FindNetwork(n)
+			sn, err := daemon.FindUniqueNetwork(n)
 			if err != nil {
 			if err != nil {
 				continue
 				continue
 			}
 			}

+ 63 - 0
integration/service/create_test.go

@@ -11,6 +11,7 @@ import (
 	"github.com/docker/docker/client"
 	"github.com/docker/docker/client"
 	"github.com/docker/docker/integration-cli/request"
 	"github.com/docker/docker/integration-cli/request"
 	"github.com/gotestyourself/gotestyourself/poll"
 	"github.com/gotestyourself/gotestyourself/poll"
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
@@ -80,6 +81,68 @@ func TestCreateServiceMultipleTimes(t *testing.T) {
 	poll.WaitOn(t, networkIsRemoved(client, overlayID), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
 	poll.WaitOn(t, networkIsRemoved(client, overlayID), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
 }
 }
 
 
+func TestCreateWithDuplicateNetworkNames(t *testing.T) {
+	defer setupTest(t)()
+	d := newSwarm(t)
+	defer d.Stop(t)
+	client, err := request.NewClientForHost(d.Sock())
+	require.NoError(t, err)
+
+	name := "foo"
+	networkCreate := types.NetworkCreate{
+		CheckDuplicate: false,
+		Driver:         "bridge",
+	}
+
+	n1, err := client.NetworkCreate(context.Background(), name, networkCreate)
+	require.NoError(t, err)
+
+	n2, err := client.NetworkCreate(context.Background(), name, networkCreate)
+	require.NoError(t, err)
+
+	// Dupliates with name but with different driver
+	networkCreate.Driver = "overlay"
+	n3, err := client.NetworkCreate(context.Background(), name, networkCreate)
+	require.NoError(t, err)
+
+	// Create Service with the same name
+	var instances uint64 = 1
+	serviceSpec := swarmServiceSpec("top", instances)
+
+	serviceSpec.TaskTemplate.Networks = append(serviceSpec.TaskTemplate.Networks, swarm.NetworkAttachmentConfig{Target: name})
+
+	service, err := client.ServiceCreate(context.Background(), serviceSpec, types.ServiceCreateOptions{})
+	require.NoError(t, err)
+
+	poll.WaitOn(t, serviceRunningTasksCount(client, service.ID, instances))
+
+	resp, _, err := client.ServiceInspectWithRaw(context.Background(), service.ID, types.ServiceInspectOptions{})
+	require.NoError(t, err)
+	assert.Equal(t, n3.ID, resp.Spec.TaskTemplate.Networks[0].Target)
+
+	// Remove Service
+	err = client.ServiceRemove(context.Background(), service.ID)
+	require.NoError(t, err)
+
+	// Make sure task has been destroyed.
+	poll.WaitOn(t, serviceIsRemoved(client, service.ID))
+
+	// Remove networks
+	err = client.NetworkRemove(context.Background(), n3.ID)
+	require.NoError(t, err)
+
+	err = client.NetworkRemove(context.Background(), n2.ID)
+	require.NoError(t, err)
+
+	err = client.NetworkRemove(context.Background(), n1.ID)
+	require.NoError(t, err)
+
+	// Make sure networks have been destroyed.
+	poll.WaitOn(t, networkIsRemoved(client, n3.ID), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
+	poll.WaitOn(t, networkIsRemoved(client, n2.ID), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
+	poll.WaitOn(t, networkIsRemoved(client, n1.ID), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
+}
+
 func swarmServiceSpec(name string, replicas uint64) swarm.ServiceSpec {
 func swarmServiceSpec(name string, replicas uint64) swarm.ServiceSpec {
 	return swarm.ServiceSpec{
 	return swarm.ServiceSpec{
 		Annotations: swarm.Annotations{
 		Annotations: swarm.Annotations{