Browse code

Move network conversions out of API router

This stuff doesn't belong here and is causing imports of libnetwork into
the router, which is not what we want.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2018/05/23 23:03:02
Showing 12 changed files
... ...
@@ -13,7 +13,7 @@ import (
13 13
 // to provide network specific functionality.
14 14
 type Backend interface {
15 15
 	FindNetwork(idName string) (libnetwork.Network, error)
16
-	GetNetworks() []libnetwork.Network
16
+	GetNetworks(filters.Args, types.NetworkListConfig) ([]types.NetworkResource, error)
17 17
 	CreateNetwork(nc types.NetworkCreateRequest) (*types.NetworkCreateResponse, error)
18 18
 	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
19 19
 	DisconnectContainerFromNetwork(containerName string, networkName string, force bool) error
... ...
@@ -24,7 +24,7 @@ type Backend interface {
24 24
 // ClusterBackend is all the methods that need to be implemented
25 25
 // to provide cluster network specific functionality.
26 26
 type ClusterBackend interface {
27
-	GetNetworks() ([]types.NetworkResource, error)
27
+	GetNetworks(filters.Args) ([]types.NetworkResource, error)
28 28
 	GetNetwork(name string) (types.NetworkResource, error)
29 29
 	GetNetworksByName(name string) ([]types.NetworkResource, error)
30 30
 	CreateNetwork(nc types.NetworkCreateRequest) (string, error)
... ...
@@ -1,93 +1 @@
1 1
 package network // import "github.com/docker/docker/api/server/router/network"
2
-
3
-import (
4
-	"github.com/docker/docker/api/types"
5
-	"github.com/docker/docker/api/types/filters"
6
-	"github.com/docker/docker/runconfig"
7
-)
8
-
9
-func filterNetworkByType(nws []types.NetworkResource, netType string) ([]types.NetworkResource, error) {
10
-	retNws := []types.NetworkResource{}
11
-	switch netType {
12
-	case "builtin":
13
-		for _, nw := range nws {
14
-			if runconfig.IsPreDefinedNetwork(nw.Name) {
15
-				retNws = append(retNws, nw)
16
-			}
17
-		}
18
-	case "custom":
19
-		for _, nw := range nws {
20
-			if !runconfig.IsPreDefinedNetwork(nw.Name) {
21
-				retNws = append(retNws, nw)
22
-			}
23
-		}
24
-	default:
25
-		return nil, invalidFilter(netType)
26
-	}
27
-	return retNws, nil
28
-}
29
-
30
-type invalidFilter string
31
-
32
-func (e invalidFilter) Error() string {
33
-	return "Invalid filter: 'type'='" + string(e) + "'"
34
-}
35
-
36
-func (e invalidFilter) InvalidParameter() {}
37
-
38
-// filterNetworks filters network list according to user specified filter
39
-// and returns user chosen networks
40
-func filterNetworks(nws []types.NetworkResource, filter filters.Args) ([]types.NetworkResource, error) {
41
-	// if filter is empty, return original network list
42
-	if filter.Len() == 0 {
43
-		return nws, nil
44
-	}
45
-
46
-	displayNet := []types.NetworkResource{}
47
-	for _, nw := range nws {
48
-		if filter.Contains("driver") {
49
-			if !filter.ExactMatch("driver", nw.Driver) {
50
-				continue
51
-			}
52
-		}
53
-		if filter.Contains("name") {
54
-			if !filter.Match("name", nw.Name) {
55
-				continue
56
-			}
57
-		}
58
-		if filter.Contains("id") {
59
-			if !filter.Match("id", nw.ID) {
60
-				continue
61
-			}
62
-		}
63
-		if filter.Contains("label") {
64
-			if !filter.MatchKVList("label", nw.Labels) {
65
-				continue
66
-			}
67
-		}
68
-		if filter.Contains("scope") {
69
-			if !filter.ExactMatch("scope", nw.Scope) {
70
-				continue
71
-			}
72
-		}
73
-		displayNet = append(displayNet, nw)
74
-	}
75
-
76
-	if filter.Contains("type") {
77
-		typeNet := []types.NetworkResource{}
78
-		errFilter := filter.WalkValues("type", func(fval string) error {
79
-			passList, err := filterNetworkByType(displayNet, fval)
80
-			if err != nil {
81
-				return err
82
-			}
83
-			typeNet = append(typeNet, passList...)
84
-			return nil
85
-		})
86
-		if errFilter != nil {
87
-			return nil, errFilter
88
-		}
89
-		displayNet = typeNet
90
-	}
91
-
92
-	return displayNet, nil
93
-}
94 2
deleted file mode 100644
... ...
@@ -1,149 +0,0 @@
1
-// +build !windows
2
-
3
-package network // import "github.com/docker/docker/api/server/router/network"
4
-
5
-import (
6
-	"strings"
7
-	"testing"
8
-
9
-	"github.com/docker/docker/api/types"
10
-	"github.com/docker/docker/api/types/filters"
11
-)
12
-
13
-func TestFilterNetworks(t *testing.T) {
14
-	networks := []types.NetworkResource{
15
-		{
16
-			Name:   "host",
17
-			Driver: "host",
18
-			Scope:  "local",
19
-		},
20
-		{
21
-			Name:   "bridge",
22
-			Driver: "bridge",
23
-			Scope:  "local",
24
-		},
25
-		{
26
-			Name:   "none",
27
-			Driver: "null",
28
-			Scope:  "local",
29
-		},
30
-		{
31
-			Name:   "myoverlay",
32
-			Driver: "overlay",
33
-			Scope:  "swarm",
34
-		},
35
-		{
36
-			Name:   "mydrivernet",
37
-			Driver: "mydriver",
38
-			Scope:  "local",
39
-		},
40
-		{
41
-			Name:   "mykvnet",
42
-			Driver: "mykvdriver",
43
-			Scope:  "global",
44
-		},
45
-	}
46
-
47
-	bridgeDriverFilters := filters.NewArgs()
48
-	bridgeDriverFilters.Add("driver", "bridge")
49
-
50
-	overlayDriverFilters := filters.NewArgs()
51
-	overlayDriverFilters.Add("driver", "overlay")
52
-
53
-	nonameDriverFilters := filters.NewArgs()
54
-	nonameDriverFilters.Add("driver", "noname")
55
-
56
-	customDriverFilters := filters.NewArgs()
57
-	customDriverFilters.Add("type", "custom")
58
-
59
-	builtinDriverFilters := filters.NewArgs()
60
-	builtinDriverFilters.Add("type", "builtin")
61
-
62
-	invalidDriverFilters := filters.NewArgs()
63
-	invalidDriverFilters.Add("type", "invalid")
64
-
65
-	localScopeFilters := filters.NewArgs()
66
-	localScopeFilters.Add("scope", "local")
67
-
68
-	swarmScopeFilters := filters.NewArgs()
69
-	swarmScopeFilters.Add("scope", "swarm")
70
-
71
-	globalScopeFilters := filters.NewArgs()
72
-	globalScopeFilters.Add("scope", "global")
73
-
74
-	testCases := []struct {
75
-		filter      filters.Args
76
-		resultCount int
77
-		err         string
78
-	}{
79
-		{
80
-			filter:      bridgeDriverFilters,
81
-			resultCount: 1,
82
-			err:         "",
83
-		},
84
-		{
85
-			filter:      overlayDriverFilters,
86
-			resultCount: 1,
87
-			err:         "",
88
-		},
89
-		{
90
-			filter:      nonameDriverFilters,
91
-			resultCount: 0,
92
-			err:         "",
93
-		},
94
-		{
95
-			filter:      customDriverFilters,
96
-			resultCount: 3,
97
-			err:         "",
98
-		},
99
-		{
100
-			filter:      builtinDriverFilters,
101
-			resultCount: 3,
102
-			err:         "",
103
-		},
104
-		{
105
-			filter:      invalidDriverFilters,
106
-			resultCount: 0,
107
-			err:         "Invalid filter: 'type'='invalid'",
108
-		},
109
-		{
110
-			filter:      localScopeFilters,
111
-			resultCount: 4,
112
-			err:         "",
113
-		},
114
-		{
115
-			filter:      swarmScopeFilters,
116
-			resultCount: 1,
117
-			err:         "",
118
-		},
119
-		{
120
-			filter:      globalScopeFilters,
121
-			resultCount: 1,
122
-			err:         "",
123
-		},
124
-	}
125
-
126
-	for _, testCase := range testCases {
127
-		result, err := filterNetworks(networks, testCase.filter)
128
-		if testCase.err != "" {
129
-			if err == nil {
130
-				t.Fatalf("expect error '%s', got no error", testCase.err)
131
-
132
-			} else if !strings.Contains(err.Error(), testCase.err) {
133
-				t.Fatalf("expect error '%s', got '%s'", testCase.err, err)
134
-			}
135
-		} else {
136
-			if err != nil {
137
-				t.Fatalf("expect no error, got error '%s'", err)
138
-			}
139
-			// Make sure result is not nil
140
-			if result == nil {
141
-				t.Fatal("filterNetworks should not return nil")
142
-			}
143
-
144
-			if len(result) != testCase.resultCount {
145
-				t.Fatalf("expect '%d' networks, got '%d' networks", testCase.resultCount, len(result))
146
-			}
147
-		}
148
-	}
149
-}
... ...
@@ -15,70 +15,54 @@ import (
15 15
 	"github.com/docker/docker/errdefs"
16 16
 	"github.com/docker/libnetwork"
17 17
 	netconst "github.com/docker/libnetwork/datastore"
18
-	"github.com/docker/libnetwork/networkdb"
19 18
 	"github.com/pkg/errors"
20 19
 )
21 20
 
22
-var (
23
-	// acceptedNetworkFilters is a list of acceptable filters
24
-	acceptedNetworkFilters = map[string]bool{
25
-		"driver": true,
26
-		"type":   true,
27
-		"name":   true,
28
-		"id":     true,
29
-		"label":  true,
30
-		"scope":  true,
31
-	}
32
-)
33
-
34 21
 func (n *networkRouter) getNetworksList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
35 22
 	if err := httputils.ParseForm(r); err != nil {
36 23
 		return err
37 24
 	}
38 25
 
39
-	filter := r.Form.Get("filters")
40
-	netFilters, err := filters.FromJSON(filter)
26
+	filter, err := filters.FromJSON(r.Form.Get("filters"))
41 27
 	if err != nil {
42 28
 		return err
43 29
 	}
44 30
 
45
-	if err := netFilters.Validate(acceptedNetworkFilters); err != nil {
31
+	if err := network.ValidateFilters(filter); err != nil {
46 32
 		return err
47 33
 	}
48 34
 
49
-	list := []types.NetworkResource{}
50
-
51
-	if nr, err := n.cluster.GetNetworks(); err == nil {
52
-		list = append(list, nr...)
35
+	var list []types.NetworkResource
36
+	nr, err := n.cluster.GetNetworks(filter)
37
+	if err == nil {
38
+		list = nr
53 39
 	}
54 40
 
55 41
 	// Combine the network list returned by Docker daemon if it is not already
56 42
 	// returned by the cluster manager
57
-SKIP:
58
-	for _, nw := range n.backend.GetNetworks() {
59
-		for _, nl := range list {
60
-			if nl.ID == nw.ID() {
61
-				continue SKIP
62
-			}
63
-		}
43
+	localNetworks, err := n.backend.GetNetworks(filter, types.NetworkListConfig{Detailed: versions.LessThan(httputils.VersionFromContext(ctx), "1.28")})
44
+	if err != nil {
45
+		return err
46
+	}
64 47
 
65
-		var nr *types.NetworkResource
66
-		// Versions < 1.28 fetches all the containers attached to a network
67
-		// in a network list api call. It is a heavy weight operation when
68
-		// run across all the networks. Starting API version 1.28, this detailed
69
-		// info is available for network specific GET API (equivalent to inspect)
70
-		if versions.LessThan(httputils.VersionFromContext(ctx), "1.28") {
71
-			nr = n.buildDetailedNetworkResources(nw, false)
72
-		} else {
73
-			nr = n.buildNetworkResource(nw)
48
+	var idx map[string]bool
49
+	if len(list) > 0 {
50
+		idx = make(map[string]bool, len(list))
51
+		for _, n := range list {
52
+			idx[n.ID] = true
74 53
 		}
75
-		list = append(list, *nr)
54
+	}
55
+	for _, n := range localNetworks {
56
+		if idx[n.ID] {
57
+			continue
58
+		}
59
+		list = append(list, n)
76 60
 	}
77 61
 
78
-	list, err = filterNetworks(list, netFilters)
79
-	if err != nil {
80
-		return err
62
+	if list == nil {
63
+		list = []types.NetworkResource{}
81 64
 	}
65
+
82 66
 	return httputils.WriteJSON(w, http.StatusOK, list)
83 67
 }
84 68
 
... ...
@@ -121,13 +105,6 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
121 121
 	}
122 122
 	scope := r.URL.Query().Get("scope")
123 123
 
124
-	isMatchingScope := func(scope, term string) bool {
125
-		if term != "" {
126
-			return scope == term
127
-		}
128
-		return true
129
-	}
130
-
131 124
 	// In case multiple networks have duplicate names, return error.
132 125
 	// TODO (yongtang): should we wrap with version here for backward compatibility?
133 126
 
... ...
@@ -139,20 +116,26 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
139 139
 	listByFullName := map[string]types.NetworkResource{}
140 140
 	listByPartialID := map[string]types.NetworkResource{}
141 141
 
142
-	nw := n.backend.GetNetworks()
142
+	// TODO(@cpuguy83): All this logic for figuring out which network to return does not belong here
143
+	// Instead there should be a backend function to just get one network.
144
+	filter := filters.NewArgs(filters.Arg("idOrName", term))
145
+	if scope != "" {
146
+		filter.Add("scope", scope)
147
+	}
148
+	nw, _ := n.backend.GetNetworks(filter, types.NetworkListConfig{Detailed: true, Verbose: verbose})
143 149
 	for _, network := range nw {
144
-		if network.ID() == term && isMatchingScope(network.Info().Scope(), scope) {
145
-			return httputils.WriteJSON(w, http.StatusOK, *n.buildDetailedNetworkResources(network, verbose))
150
+		if network.ID == term {
151
+			return httputils.WriteJSON(w, http.StatusOK, network)
146 152
 		}
147
-		if network.Name() == term && isMatchingScope(network.Info().Scope(), scope) {
153
+		if network.Name == term {
148 154
 			// No need to check the ID collision here as we are still in
149 155
 			// local scope and the network ID is unique in this scope.
150
-			listByFullName[network.ID()] = *n.buildDetailedNetworkResources(network, verbose)
156
+			listByFullName[network.ID] = network
151 157
 		}
152
-		if strings.HasPrefix(network.ID(), term) && isMatchingScope(network.Info().Scope(), scope) {
158
+		if strings.HasPrefix(network.ID, term) {
153 159
 			// No need to check the ID collision here as we are still in
154 160
 			// local scope and the network ID is unique in this scope.
155
-			listByPartialID[network.ID()] = *n.buildDetailedNetworkResources(network, verbose)
161
+			listByPartialID[network.ID] = network
156 162
 		}
157 163
 	}
158 164
 
... ...
@@ -174,12 +157,12 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
174 174
 		}
175 175
 	}
176 176
 
177
-	nr, _ := n.cluster.GetNetworks()
177
+	nr, _ := n.cluster.GetNetworks(filter)
178 178
 	for _, network := range nr {
179
-		if network.ID == term && isMatchingScope(network.Scope, scope) {
179
+		if network.ID == term {
180 180
 			return httputils.WriteJSON(w, http.StatusOK, network)
181 181
 		}
182
-		if network.Name == term && isMatchingScope(network.Scope, scope) {
182
+		if network.Name == term {
183 183
 			// Check the ID collision as we are in swarm scope here, and
184 184
 			// the map (of the listByFullName) may have already had a
185 185
 			// network with the same ID (from local scope previously)
... ...
@@ -187,7 +170,7 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
187 187
 				listByFullName[network.ID] = network
188 188
 			}
189 189
 		}
190
-		if strings.HasPrefix(network.ID, term) && isMatchingScope(network.Scope, scope) {
190
+		if strings.HasPrefix(network.ID, term) {
191 191
 			// Check the ID collision as we are in swarm scope here, and
192 192
 			// the map (of the listByPartialID) may have already had a
193 193
 			// network with the same ID (from local scope previously)
... ...
@@ -327,182 +310,6 @@ func (n *networkRouter) deleteNetwork(ctx context.Context, w http.ResponseWriter
327 327
 	return nil
328 328
 }
329 329
 
330
-func (n *networkRouter) buildNetworkResource(nw libnetwork.Network) *types.NetworkResource {
331
-	r := &types.NetworkResource{}
332
-	if nw == nil {
333
-		return r
334
-	}
335
-
336
-	info := nw.Info()
337
-	r.Name = nw.Name()
338
-	r.ID = nw.ID()
339
-	r.Created = info.Created()
340
-	r.Scope = info.Scope()
341
-	r.Driver = nw.Type()
342
-	r.EnableIPv6 = info.IPv6Enabled()
343
-	r.Internal = info.Internal()
344
-	r.Attachable = info.Attachable()
345
-	r.Ingress = info.Ingress()
346
-	r.Options = info.DriverOptions()
347
-	r.Containers = make(map[string]types.EndpointResource)
348
-	buildIpamResources(r, info)
349
-	r.Labels = info.Labels()
350
-	r.ConfigOnly = info.ConfigOnly()
351
-
352
-	if cn := info.ConfigFrom(); cn != "" {
353
-		r.ConfigFrom = network.ConfigReference{Network: cn}
354
-	}
355
-
356
-	peers := info.Peers()
357
-	if len(peers) != 0 {
358
-		r.Peers = buildPeerInfoResources(peers)
359
-	}
360
-
361
-	return r
362
-}
363
-
364
-func (n *networkRouter) buildDetailedNetworkResources(nw libnetwork.Network, verbose bool) *types.NetworkResource {
365
-	if nw == nil {
366
-		return &types.NetworkResource{}
367
-	}
368
-
369
-	r := n.buildNetworkResource(nw)
370
-	epl := nw.Endpoints()
371
-	for _, e := range epl {
372
-		ei := e.Info()
373
-		if ei == nil {
374
-			continue
375
-		}
376
-		sb := ei.Sandbox()
377
-		tmpID := e.ID()
378
-		key := "ep-" + tmpID
379
-		if sb != nil {
380
-			key = sb.ContainerID()
381
-		}
382
-
383
-		r.Containers[key] = buildEndpointResource(tmpID, e.Name(), ei)
384
-	}
385
-	if !verbose {
386
-		return r
387
-	}
388
-	services := nw.Info().Services()
389
-	r.Services = make(map[string]network.ServiceInfo)
390
-	for name, service := range services {
391
-		tasks := []network.Task{}
392
-		for _, t := range service.Tasks {
393
-			tasks = append(tasks, network.Task{
394
-				Name:       t.Name,
395
-				EndpointID: t.EndpointID,
396
-				EndpointIP: t.EndpointIP,
397
-				Info:       t.Info,
398
-			})
399
-		}
400
-		r.Services[name] = network.ServiceInfo{
401
-			VIP:          service.VIP,
402
-			Ports:        service.Ports,
403
-			Tasks:        tasks,
404
-			LocalLBIndex: service.LocalLBIndex,
405
-		}
406
-	}
407
-	return r
408
-}
409
-
410
-func buildPeerInfoResources(peers []networkdb.PeerInfo) []network.PeerInfo {
411
-	peerInfo := make([]network.PeerInfo, 0, len(peers))
412
-	for _, peer := range peers {
413
-		peerInfo = append(peerInfo, network.PeerInfo{
414
-			Name: peer.Name,
415
-			IP:   peer.IP,
416
-		})
417
-	}
418
-	return peerInfo
419
-}
420
-
421
-func buildIpamResources(r *types.NetworkResource, nwInfo libnetwork.NetworkInfo) {
422
-	id, opts, ipv4conf, ipv6conf := nwInfo.IpamConfig()
423
-
424
-	ipv4Info, ipv6Info := nwInfo.IpamInfo()
425
-
426
-	r.IPAM.Driver = id
427
-
428
-	r.IPAM.Options = opts
429
-
430
-	r.IPAM.Config = []network.IPAMConfig{}
431
-	for _, ip4 := range ipv4conf {
432
-		if ip4.PreferredPool == "" {
433
-			continue
434
-		}
435
-		iData := network.IPAMConfig{}
436
-		iData.Subnet = ip4.PreferredPool
437
-		iData.IPRange = ip4.SubPool
438
-		iData.Gateway = ip4.Gateway
439
-		iData.AuxAddress = ip4.AuxAddresses
440
-		r.IPAM.Config = append(r.IPAM.Config, iData)
441
-	}
442
-
443
-	if len(r.IPAM.Config) == 0 {
444
-		for _, ip4Info := range ipv4Info {
445
-			iData := network.IPAMConfig{}
446
-			iData.Subnet = ip4Info.IPAMData.Pool.String()
447
-			if ip4Info.IPAMData.Gateway != nil {
448
-				iData.Gateway = ip4Info.IPAMData.Gateway.IP.String()
449
-			}
450
-			r.IPAM.Config = append(r.IPAM.Config, iData)
451
-		}
452
-	}
453
-
454
-	hasIpv6Conf := false
455
-	for _, ip6 := range ipv6conf {
456
-		if ip6.PreferredPool == "" {
457
-			continue
458
-		}
459
-		hasIpv6Conf = true
460
-		iData := network.IPAMConfig{}
461
-		iData.Subnet = ip6.PreferredPool
462
-		iData.IPRange = ip6.SubPool
463
-		iData.Gateway = ip6.Gateway
464
-		iData.AuxAddress = ip6.AuxAddresses
465
-		r.IPAM.Config = append(r.IPAM.Config, iData)
466
-	}
467
-
468
-	if !hasIpv6Conf {
469
-		for _, ip6Info := range ipv6Info {
470
-			if ip6Info.IPAMData.Pool == nil {
471
-				continue
472
-			}
473
-			iData := network.IPAMConfig{}
474
-			iData.Subnet = ip6Info.IPAMData.Pool.String()
475
-			iData.Gateway = ip6Info.IPAMData.Gateway.String()
476
-			r.IPAM.Config = append(r.IPAM.Config, iData)
477
-		}
478
-	}
479
-}
480
-
481
-func buildEndpointResource(id string, name string, info libnetwork.EndpointInfo) types.EndpointResource {
482
-	er := types.EndpointResource{}
483
-
484
-	er.EndpointID = id
485
-	er.Name = name
486
-	ei := info
487
-	if ei == nil {
488
-		return er
489
-	}
490
-
491
-	if iface := ei.Iface(); iface != nil {
492
-		if mac := iface.MacAddress(); mac != nil {
493
-			er.MacAddress = mac.String()
494
-		}
495
-		if ip := iface.Address(); ip != nil && len(ip.IP) > 0 {
496
-			er.IPv4Address = ip.String()
497
-		}
498
-
499
-		if ipv6 := iface.AddressIPv6(); ipv6 != nil && len(ipv6.IP) > 0 {
500
-			er.IPv6Address = ipv6.String()
501
-		}
502
-	}
503
-	return er
504
-}
505
-
506 330
 func (n *networkRouter) postNetworksPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
507 331
 	if err := httputils.ParseForm(r); err != nil {
508 332
 		return err
... ...
@@ -532,25 +339,25 @@ func (n *networkRouter) findUniqueNetwork(term string) (types.NetworkResource, e
532 532
 	listByFullName := map[string]types.NetworkResource{}
533 533
 	listByPartialID := map[string]types.NetworkResource{}
534 534
 
535
-	nw := n.backend.GetNetworks()
535
+	filter := filters.NewArgs(filters.Arg("idOrName", term))
536
+	nw, _ := n.backend.GetNetworks(filter, types.NetworkListConfig{Detailed: true})
536 537
 	for _, network := range nw {
537
-		if network.ID() == term {
538
-			return *n.buildDetailedNetworkResources(network, false), nil
539
-
538
+		if network.ID == term {
539
+			return network, nil
540 540
 		}
541
-		if network.Name() == term && !network.Info().Ingress() {
541
+		if network.Name == term && !network.Ingress {
542 542
 			// No need to check the ID collision here as we are still in
543 543
 			// local scope and the network ID is unique in this scope.
544
-			listByFullName[network.ID()] = *n.buildDetailedNetworkResources(network, false)
544
+			listByFullName[network.ID] = network
545 545
 		}
546
-		if strings.HasPrefix(network.ID(), term) {
546
+		if strings.HasPrefix(network.ID, term) {
547 547
 			// No need to check the ID collision here as we are still in
548 548
 			// local scope and the network ID is unique in this scope.
549
-			listByPartialID[network.ID()] = *n.buildDetailedNetworkResources(network, false)
549
+			listByPartialID[network.ID] = network
550 550
 		}
551 551
 	}
552 552
 
553
-	nr, _ := n.cluster.GetNetworks()
553
+	nr, _ := n.cluster.GetNetworks(filter)
554 554
 	for _, network := range nr {
555 555
 		if network.ID == term {
556 556
 			return network, nil
... ...
@@ -55,3 +55,10 @@ type PluginEnableConfig struct {
55 55
 type PluginDisableConfig struct {
56 56
 	ForceDisable bool
57 57
 }
58
+
59
+// NetworkListConfig stores the options available for listing networks
60
+type NetworkListConfig struct {
61
+	// TODO(@cpuguy83): naming is hard, this is pulled from what was being used in the router before moving here
62
+	Detailed bool
63
+	Verbose  bool
64
+}
... ...
@@ -1,4 +1,8 @@
1 1
 package network // import "github.com/docker/docker/api/types/network"
2
+import (
3
+	"github.com/docker/docker/api/types/filters"
4
+	"github.com/docker/docker/errdefs"
5
+)
2 6
 
3 7
 // Address represents an IP address
4 8
 type Address struct {
... ...
@@ -106,3 +110,17 @@ type NetworkingConfig struct {
106 106
 type ConfigReference struct {
107 107
 	Network string
108 108
 }
109
+
110
+var acceptedFilters = map[string]bool{
111
+	"driver": true,
112
+	"type":   true,
113
+	"name":   true,
114
+	"id":     true,
115
+	"label":  true,
116
+	"scope":  true,
117
+}
118
+
119
+// ValidateFilters validates the list of filter args with the available filters.
120
+func ValidateFilters(filter filters.Args) error {
121
+	return errdefs.InvalidParameter(filter.Validate(acceptedFilters))
122
+}
... ...
@@ -2,6 +2,7 @@ package daemon // import "github.com/docker/docker/daemon"
2 2
 
3 3
 import (
4 4
 	apitypes "github.com/docker/docker/api/types"
5
+	"github.com/docker/docker/api/types/filters"
5 6
 	lncluster "github.com/docker/libnetwork/cluster"
6 7
 )
7 8
 
... ...
@@ -21,6 +22,6 @@ type ClusterStatus interface {
21 21
 // NetworkManager provides methods to manage networks
22 22
 type NetworkManager interface {
23 23
 	GetNetwork(input string) (apitypes.NetworkResource, error)
24
-	GetNetworks() ([]apitypes.NetworkResource, error)
24
+	GetNetworks(filters.Args) ([]apitypes.NetworkResource, error)
25 25
 	RemoveNetwork(input string) error
26 26
 }
... ...
@@ -5,9 +5,11 @@ import (
5 5
 	"fmt"
6 6
 
7 7
 	apitypes "github.com/docker/docker/api/types"
8
+	"github.com/docker/docker/api/types/filters"
8 9
 	"github.com/docker/docker/api/types/network"
9 10
 	types "github.com/docker/docker/api/types/swarm"
10 11
 	"github.com/docker/docker/daemon/cluster/convert"
12
+	internalnetwork "github.com/docker/docker/daemon/network"
11 13
 	"github.com/docker/docker/errdefs"
12 14
 	"github.com/docker/docker/runconfig"
13 15
 	swarmapi "github.com/docker/swarmkit/api"
... ...
@@ -16,16 +18,32 @@ import (
16 16
 )
17 17
 
18 18
 // GetNetworks returns all current cluster managed networks.
19
-func (c *Cluster) GetNetworks() ([]apitypes.NetworkResource, error) {
20
-	list, err := c.getNetworks(nil)
19
+func (c *Cluster) GetNetworks(filter filters.Args) ([]apitypes.NetworkResource, error) {
20
+	var f *swarmapi.ListNetworksRequest_Filters
21
+
22
+	if filter.Len() > 0 {
23
+		f = &swarmapi.ListNetworksRequest_Filters{}
24
+
25
+		if filter.Contains("name") {
26
+			f.Names = filter.Get("name")
27
+			f.NamePrefixes = filter.Get("name")
28
+		}
29
+
30
+		if filter.Contains("id") {
31
+			f.IDPrefixes = filter.Get("id")
32
+		}
33
+	}
34
+
35
+	list, err := c.getNetworks(f)
21 36
 	if err != nil {
22 37
 		return nil, err
23 38
 	}
24
-	removePredefinedNetworks(&list)
25
-	return list, nil
39
+	filterPredefinedNetworks(&list)
40
+
41
+	return internalnetwork.FilterNetworks(list, filter)
26 42
 }
27 43
 
28
-func removePredefinedNetworks(networks *[]apitypes.NetworkResource) {
44
+func filterPredefinedNetworks(networks *[]apitypes.NetworkResource) {
29 45
 	if networks == nil {
30 46
 		return
31 47
 	}
... ...
@@ -12,6 +12,7 @@ import (
12 12
 
13 13
 	"github.com/docker/docker/api/types"
14 14
 	containertypes "github.com/docker/docker/api/types/container"
15
+	"github.com/docker/docker/api/types/filters"
15 16
 	"github.com/docker/docker/api/types/network"
16 17
 	"github.com/docker/docker/container"
17 18
 	clustertypes "github.com/docker/docker/daemon/cluster/provider"
... ...
@@ -26,6 +27,7 @@ import (
26 26
 	"github.com/docker/libnetwork/driverapi"
27 27
 	"github.com/docker/libnetwork/ipamapi"
28 28
 	"github.com/docker/libnetwork/netlabel"
29
+	"github.com/docker/libnetwork/networkdb"
29 30
 	"github.com/docker/libnetwork/options"
30 31
 	networktypes "github.com/docker/libnetwork/types"
31 32
 	"github.com/pkg/errors"
... ...
@@ -89,7 +91,7 @@ func (daemon *Daemon) FindNetwork(term string) (libnetwork.Network, error) {
89 89
 func (daemon *Daemon) GetNetworkByID(id string) (libnetwork.Network, error) {
90 90
 	c := daemon.netController
91 91
 	if c == nil {
92
-		return nil, libnetwork.ErrNoSuchNetwork(id)
92
+		return nil, errors.Wrap(libnetwork.ErrNoSuchNetwork(id), "netcontroller is nil")
93 93
 	}
94 94
 	return c.NetworkByID(id)
95 95
 }
... ...
@@ -507,7 +509,7 @@ func (daemon *Daemon) DeleteManagedNetwork(networkID string) error {
507 507
 func (daemon *Daemon) DeleteNetwork(networkID string) error {
508 508
 	n, err := daemon.GetNetworkByID(networkID)
509 509
 	if err != nil {
510
-		return err
510
+		return errors.Wrap(err, "could not find network by ID")
511 511
 	}
512 512
 	return daemon.deleteNetwork(n, false)
513 513
 }
... ...
@@ -560,7 +562,7 @@ func (daemon *Daemon) deleteNetwork(nw libnetwork.Network, dynamic bool) error {
560 560
 	}
561 561
 
562 562
 	if err := nw.Delete(); err != nil {
563
-		return err
563
+		return errors.Wrap(err, "error while removing network")
564 564
 	}
565 565
 
566 566
 	// If this is not a configuration only network, we need to
... ...
@@ -576,8 +578,212 @@ func (daemon *Daemon) deleteNetwork(nw libnetwork.Network, dynamic bool) error {
576 576
 }
577 577
 
578 578
 // GetNetworks returns a list of all networks
579
-func (daemon *Daemon) GetNetworks() []libnetwork.Network {
580
-	return daemon.getAllNetworks()
579
+func (daemon *Daemon) GetNetworks(filter filters.Args, config types.NetworkListConfig) ([]types.NetworkResource, error) {
580
+	networks := daemon.getAllNetworks()
581
+
582
+	list := make([]types.NetworkResource, 0, len(networks))
583
+	var idx map[string]libnetwork.Network
584
+	if config.Detailed {
585
+		idx = make(map[string]libnetwork.Network)
586
+	}
587
+
588
+	for _, n := range networks {
589
+		nr := buildNetworkResource(n)
590
+		list = append(list, nr)
591
+		if config.Detailed {
592
+			idx[nr.ID] = n
593
+		}
594
+	}
595
+
596
+	var err error
597
+	list, err = internalnetwork.FilterNetworks(list, filter)
598
+	if err != nil {
599
+		return nil, err
600
+	}
601
+
602
+	if config.Detailed {
603
+		for i, n := range list {
604
+			np := &n
605
+			buildDetailedNetworkResources(np, idx[n.ID], config.Verbose)
606
+			list[i] = *np
607
+		}
608
+	}
609
+
610
+	return list, nil
611
+}
612
+
613
+func buildNetworkResource(nw libnetwork.Network) types.NetworkResource {
614
+	r := types.NetworkResource{}
615
+	if nw == nil {
616
+		return r
617
+	}
618
+
619
+	info := nw.Info()
620
+	r.Name = nw.Name()
621
+	r.ID = nw.ID()
622
+	r.Created = info.Created()
623
+	r.Scope = info.Scope()
624
+	r.Driver = nw.Type()
625
+	r.EnableIPv6 = info.IPv6Enabled()
626
+	r.Internal = info.Internal()
627
+	r.Attachable = info.Attachable()
628
+	r.Ingress = info.Ingress()
629
+	r.Options = info.DriverOptions()
630
+	r.Containers = make(map[string]types.EndpointResource)
631
+	buildIpamResources(&r, info)
632
+	r.Labels = info.Labels()
633
+	r.ConfigOnly = info.ConfigOnly()
634
+
635
+	if cn := info.ConfigFrom(); cn != "" {
636
+		r.ConfigFrom = network.ConfigReference{Network: cn}
637
+	}
638
+
639
+	peers := info.Peers()
640
+	if len(peers) != 0 {
641
+		r.Peers = buildPeerInfoResources(peers)
642
+	}
643
+
644
+	return r
645
+}
646
+
647
+func buildDetailedNetworkResources(r *types.NetworkResource, nw libnetwork.Network, verbose bool) {
648
+	if nw == nil {
649
+		return
650
+	}
651
+
652
+	epl := nw.Endpoints()
653
+	for _, e := range epl {
654
+		ei := e.Info()
655
+		if ei == nil {
656
+			continue
657
+		}
658
+		sb := ei.Sandbox()
659
+		tmpID := e.ID()
660
+		key := "ep-" + tmpID
661
+		if sb != nil {
662
+			key = sb.ContainerID()
663
+		}
664
+
665
+		r.Containers[key] = buildEndpointResource(tmpID, e.Name(), ei)
666
+	}
667
+	if !verbose {
668
+		return
669
+	}
670
+	services := nw.Info().Services()
671
+	r.Services = make(map[string]network.ServiceInfo)
672
+	for name, service := range services {
673
+		tasks := []network.Task{}
674
+		for _, t := range service.Tasks {
675
+			tasks = append(tasks, network.Task{
676
+				Name:       t.Name,
677
+				EndpointID: t.EndpointID,
678
+				EndpointIP: t.EndpointIP,
679
+				Info:       t.Info,
680
+			})
681
+		}
682
+		r.Services[name] = network.ServiceInfo{
683
+			VIP:          service.VIP,
684
+			Ports:        service.Ports,
685
+			Tasks:        tasks,
686
+			LocalLBIndex: service.LocalLBIndex,
687
+		}
688
+	}
689
+}
690
+
691
+func buildPeerInfoResources(peers []networkdb.PeerInfo) []network.PeerInfo {
692
+	peerInfo := make([]network.PeerInfo, 0, len(peers))
693
+	for _, peer := range peers {
694
+		peerInfo = append(peerInfo, network.PeerInfo{
695
+			Name: peer.Name,
696
+			IP:   peer.IP,
697
+		})
698
+	}
699
+	return peerInfo
700
+}
701
+
702
+func buildIpamResources(r *types.NetworkResource, nwInfo libnetwork.NetworkInfo) {
703
+	id, opts, ipv4conf, ipv6conf := nwInfo.IpamConfig()
704
+
705
+	ipv4Info, ipv6Info := nwInfo.IpamInfo()
706
+
707
+	r.IPAM.Driver = id
708
+
709
+	r.IPAM.Options = opts
710
+
711
+	r.IPAM.Config = []network.IPAMConfig{}
712
+	for _, ip4 := range ipv4conf {
713
+		if ip4.PreferredPool == "" {
714
+			continue
715
+		}
716
+		iData := network.IPAMConfig{}
717
+		iData.Subnet = ip4.PreferredPool
718
+		iData.IPRange = ip4.SubPool
719
+		iData.Gateway = ip4.Gateway
720
+		iData.AuxAddress = ip4.AuxAddresses
721
+		r.IPAM.Config = append(r.IPAM.Config, iData)
722
+	}
723
+
724
+	if len(r.IPAM.Config) == 0 {
725
+		for _, ip4Info := range ipv4Info {
726
+			iData := network.IPAMConfig{}
727
+			iData.Subnet = ip4Info.IPAMData.Pool.String()
728
+			if ip4Info.IPAMData.Gateway != nil {
729
+				iData.Gateway = ip4Info.IPAMData.Gateway.IP.String()
730
+			}
731
+			r.IPAM.Config = append(r.IPAM.Config, iData)
732
+		}
733
+	}
734
+
735
+	hasIpv6Conf := false
736
+	for _, ip6 := range ipv6conf {
737
+		if ip6.PreferredPool == "" {
738
+			continue
739
+		}
740
+		hasIpv6Conf = true
741
+		iData := network.IPAMConfig{}
742
+		iData.Subnet = ip6.PreferredPool
743
+		iData.IPRange = ip6.SubPool
744
+		iData.Gateway = ip6.Gateway
745
+		iData.AuxAddress = ip6.AuxAddresses
746
+		r.IPAM.Config = append(r.IPAM.Config, iData)
747
+	}
748
+
749
+	if !hasIpv6Conf {
750
+		for _, ip6Info := range ipv6Info {
751
+			if ip6Info.IPAMData.Pool == nil {
752
+				continue
753
+			}
754
+			iData := network.IPAMConfig{}
755
+			iData.Subnet = ip6Info.IPAMData.Pool.String()
756
+			iData.Gateway = ip6Info.IPAMData.Gateway.String()
757
+			r.IPAM.Config = append(r.IPAM.Config, iData)
758
+		}
759
+	}
760
+}
761
+
762
+func buildEndpointResource(id string, name string, info libnetwork.EndpointInfo) types.EndpointResource {
763
+	er := types.EndpointResource{}
764
+
765
+	er.EndpointID = id
766
+	er.Name = name
767
+	ei := info
768
+	if ei == nil {
769
+		return er
770
+	}
771
+
772
+	if iface := ei.Iface(); iface != nil {
773
+		if mac := iface.MacAddress(); mac != nil {
774
+			er.MacAddress = mac.String()
775
+		}
776
+		if ip := iface.Address(); ip != nil && len(ip.IP) > 0 {
777
+			er.IPv4Address = ip.String()
778
+		}
779
+
780
+		if ipv6 := iface.AddressIPv6(); ipv6 != nil && len(ipv6.IP) > 0 {
781
+			er.IPv6Address = ipv6.String()
782
+		}
783
+	}
784
+	return er
581 785
 }
582 786
 
583 787
 // clearAttachableNetworks removes the attachable networks
584 788
new file mode 100644
... ...
@@ -0,0 +1,92 @@
0
+package network // import "github.com/docker/docker/daemon/network"
1
+
2
+import (
3
+	"github.com/docker/docker/api/types"
4
+	"github.com/docker/docker/api/types/filters"
5
+	"github.com/docker/docker/runconfig"
6
+	"github.com/pkg/errors"
7
+)
8
+
9
+// FilterNetworks filters network list according to user specified filter
10
+// and returns user chosen networks
11
+func FilterNetworks(nws []types.NetworkResource, filter filters.Args) ([]types.NetworkResource, error) {
12
+	// if filter is empty, return original network list
13
+	if filter.Len() == 0 {
14
+		return nws, nil
15
+	}
16
+
17
+	displayNet := nws[:0]
18
+	for _, nw := range nws {
19
+		if filter.Contains("driver") {
20
+			if !filter.ExactMatch("driver", nw.Driver) {
21
+				continue
22
+			}
23
+		}
24
+		if filter.Contains("name") {
25
+			if !filter.Match("name", nw.Name) {
26
+				continue
27
+			}
28
+		}
29
+		if filter.Contains("id") {
30
+			if !filter.Match("id", nw.ID) {
31
+				continue
32
+			}
33
+		}
34
+		if filter.Contains("label") {
35
+			if !filter.MatchKVList("label", nw.Labels) {
36
+				continue
37
+			}
38
+		}
39
+		if filter.Contains("scope") {
40
+			if !filter.ExactMatch("scope", nw.Scope) {
41
+				continue
42
+			}
43
+		}
44
+
45
+		if filter.Contains("idOrName") {
46
+			if !filter.Match("name", nw.Name) && !filter.Match("id", nw.Name) {
47
+				continue
48
+			}
49
+		}
50
+		displayNet = append(displayNet, nw)
51
+	}
52
+
53
+	if filter.Contains("type") {
54
+		typeNet := []types.NetworkResource{}
55
+		errFilter := filter.WalkValues("type", func(fval string) error {
56
+			passList, err := filterNetworkByType(displayNet, fval)
57
+			if err != nil {
58
+				return err
59
+			}
60
+			typeNet = append(typeNet, passList...)
61
+			return nil
62
+		})
63
+		if errFilter != nil {
64
+			return nil, errFilter
65
+		}
66
+		displayNet = typeNet
67
+	}
68
+
69
+	return displayNet, nil
70
+}
71
+
72
+func filterNetworkByType(nws []types.NetworkResource, netType string) ([]types.NetworkResource, error) {
73
+	retNws := []types.NetworkResource{}
74
+	switch netType {
75
+	case "builtin":
76
+		for _, nw := range nws {
77
+			if runconfig.IsPreDefinedNetwork(nw.Name) {
78
+				retNws = append(retNws, nw)
79
+			}
80
+		}
81
+	case "custom":
82
+		for _, nw := range nws {
83
+			if !runconfig.IsPreDefinedNetwork(nw.Name) {
84
+				retNws = append(retNws, nw)
85
+			}
86
+		}
87
+	default:
88
+		return nil, errors.Errorf("invalid filter: 'type'='%s'", netType)
89
+	}
90
+	return retNws, nil
91
+}
0 92
new file mode 100644
... ...
@@ -0,0 +1,163 @@
0
+// +build !windows
1
+
2
+package network // import "github.com/docker/docker/daemon/network"
3
+
4
+import (
5
+	"strings"
6
+	"testing"
7
+
8
+	"github.com/docker/docker/api/types"
9
+	"github.com/docker/docker/api/types/filters"
10
+)
11
+
12
+func TestFilterNetworks(t *testing.T) {
13
+	networks := []types.NetworkResource{
14
+		{
15
+			Name:   "host",
16
+			Driver: "host",
17
+			Scope:  "local",
18
+		},
19
+		{
20
+			Name:   "bridge",
21
+			Driver: "bridge",
22
+			Scope:  "local",
23
+		},
24
+		{
25
+			Name:   "none",
26
+			Driver: "null",
27
+			Scope:  "local",
28
+		},
29
+		{
30
+			Name:   "myoverlay",
31
+			Driver: "overlay",
32
+			Scope:  "swarm",
33
+		},
34
+		{
35
+			Name:   "mydrivernet",
36
+			Driver: "mydriver",
37
+			Scope:  "local",
38
+		},
39
+		{
40
+			Name:   "mykvnet",
41
+			Driver: "mykvdriver",
42
+			Scope:  "global",
43
+		},
44
+	}
45
+
46
+	bridgeDriverFilters := filters.NewArgs()
47
+	bridgeDriverFilters.Add("driver", "bridge")
48
+
49
+	overlayDriverFilters := filters.NewArgs()
50
+	overlayDriverFilters.Add("driver", "overlay")
51
+
52
+	nonameDriverFilters := filters.NewArgs()
53
+	nonameDriverFilters.Add("driver", "noname")
54
+
55
+	customDriverFilters := filters.NewArgs()
56
+	customDriverFilters.Add("type", "custom")
57
+
58
+	builtinDriverFilters := filters.NewArgs()
59
+	builtinDriverFilters.Add("type", "builtin")
60
+
61
+	invalidDriverFilters := filters.NewArgs()
62
+	invalidDriverFilters.Add("type", "invalid")
63
+
64
+	localScopeFilters := filters.NewArgs()
65
+	localScopeFilters.Add("scope", "local")
66
+
67
+	swarmScopeFilters := filters.NewArgs()
68
+	swarmScopeFilters.Add("scope", "swarm")
69
+
70
+	globalScopeFilters := filters.NewArgs()
71
+	globalScopeFilters.Add("scope", "global")
72
+
73
+	testCases := []struct {
74
+		filter      filters.Args
75
+		resultCount int
76
+		err         string
77
+		name        string
78
+	}{
79
+		{
80
+			filter:      bridgeDriverFilters,
81
+			resultCount: 1,
82
+			err:         "",
83
+			name:        "bridge driver filters",
84
+		},
85
+		{
86
+			filter:      overlayDriverFilters,
87
+			resultCount: 1,
88
+			err:         "",
89
+			name:        "overlay driver filters",
90
+		},
91
+		{
92
+			filter:      nonameDriverFilters,
93
+			resultCount: 0,
94
+			err:         "",
95
+			name:        "no name driver filters",
96
+		},
97
+		{
98
+			filter:      customDriverFilters,
99
+			resultCount: 3,
100
+			err:         "",
101
+			name:        "custom driver filters",
102
+		},
103
+		{
104
+			filter:      builtinDriverFilters,
105
+			resultCount: 3,
106
+			err:         "",
107
+			name:        "builtin driver filters",
108
+		},
109
+		{
110
+			filter:      invalidDriverFilters,
111
+			resultCount: 0,
112
+			err:         "invalid filter: 'type'='invalid'",
113
+			name:        "invalid driver filters",
114
+		},
115
+		{
116
+			filter:      localScopeFilters,
117
+			resultCount: 4,
118
+			err:         "",
119
+			name:        "local scope filters",
120
+		},
121
+		{
122
+			filter:      swarmScopeFilters,
123
+			resultCount: 1,
124
+			err:         "",
125
+			name:        "swarm scope filters",
126
+		},
127
+		{
128
+			filter:      globalScopeFilters,
129
+			resultCount: 1,
130
+			err:         "",
131
+			name:        "global scope filters",
132
+		},
133
+	}
134
+
135
+	for _, testCase := range testCases {
136
+		t.Run(testCase.name, func(t *testing.T) {
137
+			ls := make([]types.NetworkResource, 0, len(networks))
138
+			ls = append(ls, networks...)
139
+			result, err := FilterNetworks(ls, testCase.filter)
140
+			if testCase.err != "" {
141
+				if err == nil {
142
+					t.Fatalf("expect error '%s', got no error", testCase.err)
143
+
144
+				} else if !strings.Contains(err.Error(), testCase.err) {
145
+					t.Fatalf("expect error '%s', got '%s'", testCase.err, err)
146
+				}
147
+			} else {
148
+				if err != nil {
149
+					t.Fatalf("expect no error, got error '%s'", err)
150
+				}
151
+				// Make sure result is not nil
152
+				if result == nil {
153
+					t.Fatal("filterNetworks should not return nil")
154
+				}
155
+
156
+				if len(result) != testCase.resultCount {
157
+					t.Fatalf("expect '%d' networks, got '%d' networks", testCase.resultCount, len(result))
158
+				}
159
+			}
160
+		})
161
+	}
162
+}
... ...
@@ -141,7 +141,7 @@ func (daemon *Daemon) clusterNetworksPrune(ctx context.Context, pruneFilters fil
141 141
 		return rep, nil
142 142
 	}
143 143
 
144
-	networks, err := cluster.GetNetworks()
144
+	networks, err := cluster.GetNetworks(pruneFilters)
145 145
 	if err != nil {
146 146
 		return rep, err
147 147
 	}