Browse code

Add `scope` filter in `/networks/<id>`

This fix tries to add a `scope` in the query of `/networks/<id>`
(`NetworkInspect`) so that in case of duplicate network names,
it is possible to locate the network ID based on the network
scope (`local`, 'swarm', or `global`).

Multiple networks might exist in different scopes, which is a legitimate case.
For example, a network name `foo` might exists locally and in swarm network.

However, before this PR it was not possible to query a network name `foo`
in a specific scope like swarm.

This fix fixes the issue by allowing a `scope` query in `/networks/<id>`.

Additional test cases have been added to unit tests and integration tests.

This fix is related to docker/cli#167, moby/moby#30897, moby/moby#33561, moby/moby#30242

This fix fixes docker/cli#167

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

Yong Tang authored on 2017/06/12 03:04:35
Showing 8 changed files
... ...
@@ -98,6 +98,14 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
98 98
 			return errors.NewBadRequestError(err)
99 99
 		}
100 100
 	}
101
+	scope := r.URL.Query().Get("scope")
102
+
103
+	isMatchingScope := func(scope, term string) bool {
104
+		if term != "" {
105
+			return scope == term
106
+		}
107
+		return true
108
+	}
101 109
 
102 110
 	// In case multiple networks have duplicate names, return error.
103 111
 	// TODO (yongtang): should we wrap with version here for backward compatibility?
... ...
@@ -112,15 +120,15 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
112 112
 
113 113
 	nw := n.backend.GetNetworks()
114 114
 	for _, network := range nw {
115
-		if network.ID() == term {
115
+		if network.ID() == term && isMatchingScope(network.Info().Scope(), scope) {
116 116
 			return httputils.WriteJSON(w, http.StatusOK, *n.buildDetailedNetworkResources(network, verbose))
117 117
 		}
118
-		if network.Name() == term {
118
+		if network.Name() == term && isMatchingScope(network.Info().Scope(), scope) {
119 119
 			// No need to check the ID collision here as we are still in
120 120
 			// local scope and the network ID is unique in this scope.
121 121
 			listByFullName[network.ID()] = *n.buildDetailedNetworkResources(network, verbose)
122 122
 		}
123
-		if strings.HasPrefix(network.ID(), term) {
123
+		if strings.HasPrefix(network.ID(), term) && isMatchingScope(network.Info().Scope(), scope) {
124 124
 			// No need to check the ID collision here as we are still in
125 125
 			// local scope and the network ID is unique in this scope.
126 126
 			listByPartialID[network.ID()] = *n.buildDetailedNetworkResources(network, verbose)
... ...
@@ -129,10 +137,10 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
129 129
 
130 130
 	nr, _ := n.cluster.GetNetworks()
131 131
 	for _, network := range nr {
132
-		if network.ID == term {
132
+		if network.ID == term && isMatchingScope(network.Scope, scope) {
133 133
 			return httputils.WriteJSON(w, http.StatusOK, network)
134 134
 		}
135
-		if network.Name == term {
135
+		if network.Name == term && isMatchingScope(network.Scope, scope) {
136 136
 			// Check the ID collision as we are in swarm scope here, and
137 137
 			// the map (of the listByFullName) may have already had a
138 138
 			// network with the same ID (from local scope previously)
... ...
@@ -140,7 +148,7 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
140 140
 				listByFullName[network.ID] = network
141 141
 			}
142 142
 		}
143
-		if strings.HasPrefix(network.ID, term) {
143
+		if strings.HasPrefix(network.ID, term) && isMatchingScope(network.Scope, scope) {
144 144
 			// Check the ID collision as we are in swarm scope here, and
145 145
 			// the map (of the listByPartialID) may have already had a
146 146
 			// network with the same ID (from local scope previously)
... ...
@@ -6437,6 +6437,10 @@ paths:
6437 6437
           description: "Detailed inspect output for troubleshooting"
6438 6438
           type: "boolean"
6439 6439
           default: false
6440
+        - name: "scope"
6441
+          in: "query"
6442
+          description: "Filter the network by scope (swarm, global, or local)"
6443
+          type: "string"
6440 6444
       tags: ["Network"]
6441 6445
 
6442 6446
     delete:
... ...
@@ -468,6 +468,12 @@ type NetworkDisconnect struct {
468 468
 	Force     bool
469 469
 }
470 470
 
471
+// NetworkInspectOptions holds parameters to inspect network
472
+type NetworkInspectOptions struct {
473
+	Scope   string
474
+	Verbose bool
475
+}
476
+
471 477
 // Checkpoint represents the details of a checkpoint
472 478
 type Checkpoint struct {
473 479
 	Name string // Name is the name of the checkpoint
... ...
@@ -99,8 +99,8 @@ type NetworkAPIClient interface {
99 99
 	NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error
100 100
 	NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
101 101
 	NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error
102
-	NetworkInspect(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error)
103
-	NetworkInspectWithRaw(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, []byte, error)
102
+	NetworkInspect(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error)
103
+	NetworkInspectWithRaw(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, []byte, error)
104 104
 	NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
105 105
 	NetworkRemove(ctx context.Context, networkID string) error
106 106
 	NetworksPrune(ctx context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error)
... ...
@@ -12,22 +12,25 @@ import (
12 12
 )
13 13
 
14 14
 // NetworkInspect returns the information for a specific network configured in the docker host.
15
-func (cli *Client) NetworkInspect(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error) {
16
-	networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID, verbose)
15
+func (cli *Client) NetworkInspect(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) {
16
+	networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID, options)
17 17
 	return networkResource, err
18 18
 }
19 19
 
20 20
 // NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation.
21
-func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, []byte, error) {
21
+func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, []byte, error) {
22 22
 	var (
23 23
 		networkResource types.NetworkResource
24 24
 		resp            serverResponse
25 25
 		err             error
26 26
 	)
27 27
 	query := url.Values{}
28
-	if verbose {
28
+	if options.Verbose {
29 29
 		query.Set("verbose", "true")
30 30
 	}
31
+	if options.Scope != "" {
32
+		query.Set("scope", options.Scope)
33
+	}
31 34
 	resp, err = cli.get(ctx, "/networks/"+networkID, query, nil)
32 35
 	if err != nil {
33 36
 		if resp.statusCode == http.StatusNotFound {
... ...
@@ -11,6 +11,7 @@ import (
11 11
 
12 12
 	"github.com/docker/docker/api/types"
13 13
 	"github.com/docker/docker/api/types/network"
14
+	"github.com/stretchr/testify/assert"
14 15
 	"golang.org/x/net/context"
15 16
 )
16 17
 
... ...
@@ -19,7 +20,7 @@ func TestNetworkInspectError(t *testing.T) {
19 19
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
20 20
 	}
21 21
 
22
-	_, err := client.NetworkInspect(context.Background(), "nothing", false)
22
+	_, err := client.NetworkInspect(context.Background(), "nothing", types.NetworkInspectOptions{})
23 23
 	if err == nil || err.Error() != "Error response from daemon: Server error" {
24 24
 		t.Fatalf("expected a Server Error, got %v", err)
25 25
 	}
... ...
@@ -30,7 +31,7 @@ func TestNetworkInspectContainerNotFound(t *testing.T) {
30 30
 		client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
31 31
 	}
32 32
 
33
-	_, err := client.NetworkInspect(context.Background(), "unknown", false)
33
+	_, err := client.NetworkInspect(context.Background(), "unknown", types.NetworkInspectOptions{})
34 34
 	if err == nil || !IsErrNetworkNotFound(err) {
35 35
 		t.Fatalf("expected a networkNotFound error, got %v", err)
36 36
 	}
... ...
@@ -51,7 +52,14 @@ func TestNetworkInspect(t *testing.T) {
51 51
 				content []byte
52 52
 				err     error
53 53
 			)
54
-			if strings.HasPrefix(req.URL.RawQuery, "verbose=true") {
54
+			if strings.Contains(req.URL.RawQuery, "scope=global") {
55
+				return &http.Response{
56
+					StatusCode: http.StatusNotFound,
57
+					Body:       ioutil.NopCloser(bytes.NewReader(content)),
58
+				}, nil
59
+			}
60
+
61
+			if strings.Contains(req.URL.RawQuery, "verbose=true") {
55 62
 				s := map[string]network.ServiceInfo{
56 63
 					"web": {},
57 64
 				}
... ...
@@ -74,7 +82,7 @@ func TestNetworkInspect(t *testing.T) {
74 74
 		}),
75 75
 	}
76 76
 
77
-	r, err := client.NetworkInspect(context.Background(), "network_id", false)
77
+	r, err := client.NetworkInspect(context.Background(), "network_id", types.NetworkInspectOptions{})
78 78
 	if err != nil {
79 79
 		t.Fatal(err)
80 80
 	}
... ...
@@ -82,7 +90,7 @@ func TestNetworkInspect(t *testing.T) {
82 82
 		t.Fatalf("expected `mynetwork`, got %s", r.Name)
83 83
 	}
84 84
 
85
-	r, err = client.NetworkInspect(context.Background(), "network_id", true)
85
+	r, err = client.NetworkInspect(context.Background(), "network_id", types.NetworkInspectOptions{Verbose: true})
86 86
 	if err != nil {
87 87
 		t.Fatal(err)
88 88
 	}
... ...
@@ -93,4 +101,7 @@ func TestNetworkInspect(t *testing.T) {
93 93
 	if !ok {
94 94
 		t.Fatalf("expected service `web` missing in the verbose output")
95 95
 	}
96
+
97
+	_, err = client.NetworkInspect(context.Background(), "network_id", types.NetworkInspectOptions{Scope: "global"})
98
+	assert.EqualError(t, err, "Error: No such network: network_id")
96 99
 }
... ...
@@ -21,6 +21,7 @@ keywords: "API, Docker, rcli, REST, documentation"
21 21
 * `POST /secrets/create` now returns status code 409 instead of 500 when creating an already existing secret.
22 22
 * `POST /secrets/(name)/update` now returns status code 400 instead of 500 when updating a secret's content which is not the labels.
23 23
 * `POST /nodes/(name)/update` now returns status code 400 instead of 500 when demoting last node fails.
24
+* `GET /networks/(id or name)` now takes an optional query parameter `scope` that will filter the network based on the scope (`local`, `swarm`, or `global`).
24 25
 
25 26
 ## v1.30 API changes
26 27
 
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"io/ioutil"
9 9
 	"net"
10 10
 	"net/http"
11
+	"net/url"
11 12
 	"os"
12 13
 	"path/filepath"
13 14
 	"strings"
... ...
@@ -1002,3 +1003,36 @@ func (s *DockerSwarmSuite) TestSwarmRepeatedRootRotation(c *check.C) {
1002 1002
 		currentTrustRoot = clusterTLSInfo.TrustRoot
1003 1003
 	}
1004 1004
 }
1005
+
1006
+func (s *DockerSwarmSuite) TestAPINetworkInspectWithScope(c *check.C) {
1007
+	d := s.AddDaemon(c, true, true)
1008
+
1009
+	name := "foo"
1010
+	networkCreateRequest := types.NetworkCreateRequest{
1011
+		Name: name,
1012
+	}
1013
+
1014
+	var n types.NetworkCreateResponse
1015
+	networkCreateRequest.NetworkCreate.Driver = "overlay"
1016
+
1017
+	status, out, err := d.SockRequest("POST", "/networks/create", networkCreateRequest)
1018
+	c.Assert(err, checker.IsNil, check.Commentf(string(out)))
1019
+	c.Assert(status, checker.Equals, http.StatusCreated, check.Commentf(string(out)))
1020
+	c.Assert(json.Unmarshal(out, &n), checker.IsNil)
1021
+
1022
+	var r types.NetworkResource
1023
+
1024
+	status, body, err := d.SockRequest("GET", "/networks/"+name, nil)
1025
+	c.Assert(err, checker.IsNil, check.Commentf(string(out)))
1026
+	c.Assert(status, checker.Equals, http.StatusOK, check.Commentf(string(out)))
1027
+	c.Assert(json.Unmarshal(body, &r), checker.IsNil)
1028
+	c.Assert(r.Scope, checker.Equals, "swarm")
1029
+	c.Assert(r.ID, checker.Equals, n.ID)
1030
+
1031
+	v := url.Values{}
1032
+	v.Set("scope", "local")
1033
+
1034
+	status, body, err = d.SockRequest("GET", "/networks/"+name+"?"+v.Encode(), nil)
1035
+	c.Assert(err, checker.IsNil, check.Commentf(string(out)))
1036
+	c.Assert(status, checker.Equals, http.StatusNotFound, check.Commentf(string(out)))
1037
+}