diff --git a/libnetwork/endpoint.go b/libnetwork/endpoint.go index fa53450868..ce799e36ae 100644 --- a/libnetwork/endpoint.go +++ b/libnetwork/endpoint.go @@ -17,6 +17,47 @@ import ( "github.com/docker/docker/libnetwork/types" ) +// ByNetworkType sorts a [Endpoint] slice based on the network-type +// they're attached to. It implements [sort.Interface] and can be used +// with [sort.Stable] or [sort.Sort]. It is used by [Sandbox.ResolveName] +// when resolving names in swarm mode. In swarm mode, services with exposed +// ports are connected to user overlay network, ingress network, and local +// ("docker_gwbridge") networks. Name resolution should prioritize returning +// the VIP/IPs on user overlay network over ingress and local networks. +// +// ByNetworkType re-orders the endpoints based on the network-type they +// are attached to: +// +// 1. dynamic networks (user overlay networks) +// 2. ingress network(s) +// 3. local networks ("docker_gwbridge") +type ByNetworkType []*Endpoint + +func (ep ByNetworkType) Len() int { return len(ep) } +func (ep ByNetworkType) Swap(i, j int) { ep[i], ep[j] = ep[j], ep[i] } +func (ep ByNetworkType) Less(i, j int) bool { + return getNetworkType(ep[i].getNetwork()) < getNetworkType(ep[j].getNetwork()) +} + +// Define the order in which resolution should happen if an endpoint is +// attached to multiple network-types. It is used by [ByNetworkType]. +const ( + typeDynamic = iota + typeIngress + typeLocal +) + +func getNetworkType(nw *Network) int { + switch { + case nw.ingress: + return typeIngress + case nw.dynamic: + return typeDynamic + default: + return typeLocal + } +} + // EndpointOption is an option setter function type used to pass various options to Network // and Endpoint interfaces methods. The various setter functions of type EndpointOption are // provided by libnetwork, they look like Option[...](...) diff --git a/libnetwork/endpoint_test.go b/libnetwork/endpoint_test.go new file mode 100644 index 0000000000..098b29041a --- /dev/null +++ b/libnetwork/endpoint_test.go @@ -0,0 +1,43 @@ +package libnetwork + +import ( + "sort" + "testing" + + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +func TestSortByNetworkType(t *testing.T) { + nws := []*Network{ + {name: "local2"}, + {name: "ovl2", dynamic: true}, + {name: "local3"}, + {name: "ingress", ingress: true}, + {name: "ovl3", dynamic: true}, + {name: "local1"}, + {name: "ovl1", dynamic: true}, + } + eps := make([]*Endpoint, 0, len(nws)) + for _, nw := range nws { + eps = append(eps, &Endpoint{ + name: "ep-" + nw.name, + network: nw, + }) + } + sort.Sort(ByNetworkType(eps)) + actual := make([]string, 0, len(eps)) + for _, ep := range eps { + actual = append(actual, ep.name) + } + expected := []string{ + "ep-ovl2", + "ep-ovl3", + "ep-ovl1", + "ep-ingress", + "ep-local2", + "ep-local3", + "ep-local1", + } + assert.Check(t, is.DeepEqual(actual, expected)) +} diff --git a/libnetwork/sandbox.go b/libnetwork/sandbox.go index 81a0db1408..583747a469 100644 --- a/libnetwork/sandbox.go +++ b/libnetwork/sandbox.go @@ -392,38 +392,6 @@ func (sb *Sandbox) ResolveService(ctx context.Context, name string) ([]*net.SRV, return nil, nil } -func getDynamicNwEndpoints(epList []*Endpoint) []*Endpoint { - eps := []*Endpoint{} - for _, ep := range epList { - n := ep.getNetwork() - if n.dynamic && !n.ingress { - eps = append(eps, ep) - } - } - return eps -} - -func getIngressNwEndpoint(epList []*Endpoint) *Endpoint { - for _, ep := range epList { - n := ep.getNetwork() - if n.ingress { - return ep - } - } - return nil -} - -func getLocalNwEndpoints(epList []*Endpoint) []*Endpoint { - eps := []*Endpoint{} - for _, ep := range epList { - n := ep.getNetwork() - if !n.dynamic && !n.ingress { - eps = append(eps, ep) - } - } - return eps -} - func (sb *Sandbox) ResolveName(ctx context.Context, name string, ipType int) ([]net.IP, bool) { // Embedded server owns the docker network domain. Resolution should work // for both container_name and container_name.network_name @@ -455,18 +423,17 @@ func (sb *Sandbox) ResolveName(ctx context.Context, name string, ipType int) ([] epList := sb.Endpoints() - // In swarm mode services with exposed ports are connected to user overlay - // network, ingress network and docker_gwbridge network. Name resolution + // In swarm mode, services with exposed ports are connected to user overlay + // network, ingress network and docker_gwbridge networks. Name resolution // should prioritize returning the VIP/IPs on user overlay network. - newList := []*Endpoint{} + // + // Re-order the endpoints based on the network-type they're attached to; + // + // 1. dynamic networks (user overlay networks) + // 2. ingress network(s) + // 3. local networks ("docker_gwbridge") if sb.controller.isSwarmNode() { - newList = append(newList, getDynamicNwEndpoints(epList)...) - ingressEP := getIngressNwEndpoint(epList) - if ingressEP != nil { - newList = append(newList, ingressEP) - } - newList = append(newList, getLocalNwEndpoints(epList)...) - epList = newList + sort.Sort(ByNetworkType(epList)) } for i := 0; i < len(reqName); i++ {