Browse code

Promote Service cli

- To the same level of Network cli
and to make use of the new service
rest apis

Signed-off-by: Alessandro Boch <aboch@docker.com>

Alessandro Boch authored on 2015/06/12 00:30:45
Showing 10 changed files
... ...
@@ -96,6 +96,7 @@ func (h *httpHandler) initRouter() {
96 96
 			{"/services", []string{"partial-id", epPID}, procGetServices},
97 97
 			{"/services", nil, procGetServices},
98 98
 			{"/services/" + epID, nil, procGetService},
99
+			{"/services/" + epID + "/backend", nil, procGetContainers},
99 100
 		},
100 101
 		"POST": {
101 102
 			{"/networks", nil, procCreateNetwork},
... ...
@@ -184,6 +185,14 @@ func buildEndpointResource(ep libnetwork.Endpoint) *endpointResource {
184 184
 	return r
185 185
 }
186 186
 
187
+func buildContainerResource(ci libnetwork.ContainerInfo) *containerResource {
188
+	r := &containerResource{}
189
+	if ci != nil {
190
+		r.ID = ci.ID()
191
+	}
192
+	return r
193
+}
194
+
187 195
 /****************
188 196
  Options Parsers
189 197
 *****************/
... ...
@@ -527,6 +536,19 @@ func procGetService(c libnetwork.NetworkController, vars map[string]string, body
527 527
 	return buildEndpointResource(sv), &successResponse
528 528
 }
529 529
 
530
+func procGetContainers(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
531
+	epT, epBy := detectEndpointTarget(vars)
532
+	sv, errRsp := findService(c, epT, epBy)
533
+	if !errRsp.isOK() {
534
+		return nil, endpointToService(errRsp)
535
+	}
536
+	var list []*containerResource
537
+	if sv.ContainerInfo() != nil {
538
+		list = append(list, buildContainerResource(sv.ContainerInfo()))
539
+	}
540
+	return list, &successResponse
541
+}
542
+
530 543
 func procPublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
531 544
 	var sp servicePublish
532 545
 
... ...
@@ -70,6 +70,14 @@ func i2nL(i interface{}) []*networkResource {
70 70
 	return s
71 71
 }
72 72
 
73
+func i2cL(i interface{}) []*containerResource {
74
+	s, ok := i.([]*containerResource)
75
+	if !ok {
76
+		panic(fmt.Sprintf("Failed i2cL for %v", i))
77
+	}
78
+	return s
79
+}
80
+
73 81
 func createTestNetwork(t *testing.T, network string) (libnetwork.NetworkController, libnetwork.Network) {
74 82
 	c, err := libnetwork.New("")
75 83
 	if err != nil {
... ...
@@ -904,6 +912,14 @@ func TestAttachDetachBackend(t *testing.T) {
904 904
 		t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
905 905
 	}
906 906
 
907
+	_, errRsp = procGetContainers(c, vars, nil)
908
+	if errRsp.isOK() {
909
+		t.Fatalf("Expected failure. Got %v", errRsp)
910
+	}
911
+	if errRsp.StatusCode != http.StatusNotFound {
912
+		t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
913
+	}
914
+
907 915
 	vars[urlEpName] = "db"
908 916
 	_, errRsp = procAttachBackend(c, vars, bad)
909 917
 	if errRsp == &successResponse {
... ...
@@ -925,6 +941,26 @@ func TestAttachDetachBackend(t *testing.T) {
925 925
 		t.Fatalf("Unexpected failure, got: %v", errRsp)
926 926
 	}
927 927
 
928
+	cli, errRsp := procGetContainers(c, vars, nil)
929
+	if errRsp != &successResponse {
930
+		t.Fatalf("Unexpected failure, got: %v", errRsp)
931
+	}
932
+	cl := i2cL(cli)
933
+	if len(cl) != 1 {
934
+		t.Fatalf("Did not find expected number of containers attached to the service: %d", len(cl))
935
+	}
936
+	if cl[0].ID != cid {
937
+		t.Fatalf("Did not find expected container attached to the service: %v", cl[0])
938
+	}
939
+
940
+	_, errRsp = procUnpublishService(c, vars, nil)
941
+	if errRsp.isOK() {
942
+		t.Fatalf("Expected failure but succeeded")
943
+	}
944
+	if errRsp.StatusCode != http.StatusForbidden {
945
+		t.Fatalf("Expected %d. Got: %v", http.StatusForbidden, errRsp)
946
+	}
947
+
928 948
 	vars[urlEpName] = "endpoint"
929 949
 	_, errRsp = procDetachBackend(c, vars, nil)
930 950
 	if errRsp == &successResponse {
... ...
@@ -949,6 +985,15 @@ func TestAttachDetachBackend(t *testing.T) {
949 949
 		t.Fatalf("Unexpected failure, got: %v", errRsp)
950 950
 	}
951 951
 
952
+	cli, errRsp = procGetContainers(c, vars, nil)
953
+	if errRsp != &successResponse {
954
+		t.Fatalf("Unexpected failure, got: %v", errRsp)
955
+	}
956
+	cl = i2cL(cli)
957
+	if len(cl) != 0 {
958
+		t.Fatalf("Did not find expected number of containers attached to the service: %d", len(cl))
959
+	}
960
+
952 961
 	err = ep1.Delete()
953 962
 	if err != nil {
954 963
 		t.Fatal(err)
... ...
@@ -21,6 +21,12 @@ type endpointResource struct {
21 21
 	Network string `json:"network"`
22 22
 }
23 23
 
24
+// containerResource is the body of "get service backend" response message
25
+type containerResource struct {
26
+	ID string `json:"id"`
27
+	// will add more fields once labels change is in
28
+}
29
+
24 30
 /***********
25 31
   Body types
26 32
   ************/
... ...
@@ -55,7 +61,7 @@ type endpointJoin struct {
55 55
 // servicePublish represents the body of the "publish service" http request message
56 56
 type servicePublish struct {
57 57
 	Name         string                `json:"name"`
58
-	Network      string                `json:"network"`
58
+	Network      string                `json:"network_name"`
59 59
 	ExposedPorts []types.TransportPort `json:"exposed_ports"`
60 60
 	PortMapping  []types.PortBinding   `json:"port_mapping"`
61 61
 }
... ...
@@ -7,7 +7,7 @@ import (
7 7
 	_ "github.com/docker/libnetwork/netutils"
8 8
 )
9 9
 
10
-func TestClientNetworkServiceInvalidCommand(t *testing.T) {
10
+func TestClientServiceInvalidCommand(t *testing.T) {
11 11
 	var out, errOut bytes.Buffer
12 12
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
13 13
 
... ...
@@ -17,73 +17,73 @@ func TestClientNetworkServiceInvalidCommand(t *testing.T) {
17 17
 	}
18 18
 }
19 19
 
20
-func TestClientNetworkServiceCreate(t *testing.T) {
20
+func TestClientServiceCreate(t *testing.T) {
21 21
 	var out, errOut bytes.Buffer
22 22
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
23 23
 
24
-	err := cli.Cmd("docker", "service", "create", mockServiceName, mockNwName)
24
+	err := cli.Cmd("docker", "service", "publish", "-net="+mockNwName, mockServiceName)
25 25
 	if err != nil {
26
-		t.Fatal(err.Error())
26
+		t.Fatal(err)
27 27
 	}
28 28
 }
29 29
 
30
-func TestClientNetworkServiceRm(t *testing.T) {
30
+func TestClientServiceRm(t *testing.T) {
31 31
 	var out, errOut bytes.Buffer
32 32
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
33 33
 
34
-	err := cli.Cmd("docker", "service", "rm", mockServiceName, mockNwName)
34
+	err := cli.Cmd("docker", "service", "unpublish", "-net="+mockNwName, mockServiceName)
35 35
 	if err != nil {
36
-		t.Fatal(err.Error())
36
+		t.Fatal(err)
37 37
 	}
38 38
 }
39 39
 
40
-func TestClientNetworkServiceLs(t *testing.T) {
40
+func TestClientServiceLs(t *testing.T) {
41 41
 	var out, errOut bytes.Buffer
42 42
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
43 43
 
44
-	err := cli.Cmd("docker", "service", "ls", mockNwName)
44
+	err := cli.Cmd("docker", "service", "ls")
45 45
 	if err != nil {
46
-		t.Fatal(err.Error())
46
+		t.Fatal(err)
47 47
 	}
48 48
 }
49 49
 
50
-func TestClientNetworkServiceInfo(t *testing.T) {
50
+func TestClientServiceInfo(t *testing.T) {
51 51
 	var out, errOut bytes.Buffer
52 52
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
53 53
 
54
-	err := cli.Cmd("docker", "service", "info", mockServiceName, mockNwName)
54
+	err := cli.Cmd("docker", "service", "info", "-net="+mockNwName, mockServiceName)
55 55
 	if err != nil {
56
-		t.Fatal(err.Error())
56
+		t.Fatal(err)
57 57
 	}
58 58
 }
59 59
 
60
-func TestClientNetworkServiceInfoById(t *testing.T) {
60
+func TestClientServiceInfoById(t *testing.T) {
61 61
 	var out, errOut bytes.Buffer
62 62
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
63 63
 
64
-	err := cli.Cmd("docker", "service", "info", mockServiceID, mockNwID)
64
+	err := cli.Cmd("docker", "service", "info", "-net="+mockNwName, mockServiceID)
65 65
 	if err != nil {
66
-		t.Fatal(err.Error())
66
+		t.Fatal(err)
67 67
 	}
68 68
 }
69 69
 
70
-func TestClientNetworkServiceJoin(t *testing.T) {
70
+func TestClientServiceJoin(t *testing.T) {
71 71
 	var out, errOut bytes.Buffer
72 72
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
73 73
 
74
-	err := cli.Cmd("docker", "service", "join", mockContainerID, mockServiceName, mockNwName)
74
+	err := cli.Cmd("docker", "service", "attach", "-net="+mockNwName, mockContainerID, mockServiceName)
75 75
 	if err != nil {
76
-		t.Fatal(err.Error())
76
+		t.Fatal(err)
77 77
 	}
78 78
 }
79 79
 
80
-func TestClientNetworkServiceLeave(t *testing.T) {
80
+func TestClientServiceLeave(t *testing.T) {
81 81
 	var out, errOut bytes.Buffer
82 82
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
83 83
 
84
-	err := cli.Cmd("docker", "service", "leave", mockContainerID, mockServiceName, mockNwName)
84
+	err := cli.Cmd("docker", "service", "detach", "-net="+mockNwName, mockContainerID, mockServiceName)
85 85
 	if err != nil {
86
-		t.Fatal(err.Error())
86
+		t.Fatal(err)
87 87
 	}
88 88
 }
89 89
 
... ...
@@ -39,8 +39,8 @@ func setupMockHTTPCallback() {
39 39
 	list = append(list, nw)
40 40
 	mockNwListJSON, _ = json.Marshal(list)
41 41
 
42
-	var srvList []endpointResource
43
-	ep := endpointResource{Name: mockServiceName, ID: mockServiceID, Network: mockNwName}
42
+	var srvList []serviceResource
43
+	ep := serviceResource{Name: mockServiceName, ID: mockServiceID, Network: mockNwName}
44 44
 	mockServiceJSON, _ = json.Marshal(ep)
45 45
 	srvList = append(srvList, ep)
46 46
 	mockServiceListJSON, _ = json.Marshal(srvList)
... ...
@@ -61,26 +61,26 @@ func setupMockHTTPCallback() {
61 61
 				rsp = string(mockNwListJSON)
62 62
 			} else if strings.HasSuffix(path, "networks/"+mockNwID) {
63 63
 				rsp = string(mockNwJSON)
64
-			} else if strings.Contains(path, fmt.Sprintf("endpoints?name=%s", mockServiceName)) {
64
+			} else if strings.Contains(path, fmt.Sprintf("services?name=%s", mockServiceName)) {
65 65
 				rsp = string(mockServiceListJSON)
66
-			} else if strings.Contains(path, "endpoints?name=") {
66
+			} else if strings.Contains(path, "services?name=") {
67 67
 				rsp = "[]"
68
-			} else if strings.Contains(path, fmt.Sprintf("endpoints?partial-id=%s", mockServiceID)) {
68
+			} else if strings.Contains(path, fmt.Sprintf("services?partial-id=%s", mockServiceID)) {
69 69
 				rsp = string(mockServiceListJSON)
70
-			} else if strings.Contains(path, "endpoints?partial-id=") {
70
+			} else if strings.Contains(path, "services?partial-id=") {
71 71
 				rsp = "[]"
72
-			} else if strings.HasSuffix(path, "endpoints") {
72
+			} else if strings.HasSuffix(path, "services") {
73 73
 				rsp = string(mockServiceListJSON)
74
-			} else if strings.HasSuffix(path, "endpoints/"+mockServiceID) {
74
+			} else if strings.HasSuffix(path, "services/"+mockServiceID) {
75 75
 				rsp = string(mockServiceJSON)
76 76
 			}
77 77
 		case "POST":
78 78
 			var data []byte
79 79
 			if strings.HasSuffix(path, "networks") {
80 80
 				data, _ = json.Marshal(mockNwID)
81
-			} else if strings.HasSuffix(path, "endpoints") {
81
+			} else if strings.HasSuffix(path, "services") {
82 82
 				data, _ = json.Marshal(mockServiceID)
83
-			} else if strings.HasSuffix(path, "containers") {
83
+			} else if strings.HasSuffix(path, "backend") {
84 84
 				data, _ = json.Marshal(mockContainerID)
85 85
 			}
86 86
 			rsp = string(data)
... ...
@@ -177,10 +177,10 @@ func (cli *NetworkCli) CmdNetworkInfo(chain string, args ...string) error {
177 177
 	fmt.Fprintf(cli.out, "Network Id: %s\n", networkResource.ID)
178 178
 	fmt.Fprintf(cli.out, "Name: %s\n", networkResource.Name)
179 179
 	fmt.Fprintf(cli.out, "Type: %s\n", networkResource.Type)
180
-	if networkResource.Endpoints != nil {
181
-		for _, endpointResource := range networkResource.Endpoints {
182
-			fmt.Fprintf(cli.out, "  Service Id: %s\n", endpointResource.ID)
183
-			fmt.Fprintf(cli.out, "\tName: %s\n", endpointResource.Name)
180
+	if networkResource.Services != nil {
181
+		for _, serviceResource := range networkResource.Services {
182
+			fmt.Fprintf(cli.out, "  Service Id: %s\n", serviceResource.ID)
183
+			fmt.Fprintf(cli.out, "\tName: %s\n", serviceResource.Name)
184 184
 		}
185 185
 	}
186 186
 
... ...
@@ -13,64 +13,77 @@ import (
13 13
 
14 14
 var (
15 15
 	serviceCommands = []command{
16
-		{"create", "Create a service endpoint"},
17
-		{"rm", "Remove a service endpoint"},
18
-		{"join", "Join a container to a service endpoint"},
19
-		{"leave", "Leave a container from a service endpoint"},
20
-		{"ls", "Lists all service endpoints on a network"},
21
-		{"info", "Display information of a service endpoint"},
16
+		{"publish", "Publish a service"},
17
+		{"unpublish", "Remove a service"},
18
+		{"attach", "Attach a provider (container) to the service"},
19
+		{"detach", "Detach the provider from the service"},
20
+		{"ls", "Lists all services"},
21
+		{"info", "Display information about a service"},
22 22
 	}
23 23
 )
24 24
 
25
-func lookupServiceID(cli *NetworkCli, networkID string, nameID string) (string, error) {
26
-	obj, statusCode, err := readBody(cli.call("GET", fmt.Sprintf("/networks/%s/endpoints?name=%s", networkID, nameID), nil, nil))
25
+func lookupServiceID(cli *NetworkCli, nwName, svNameID string) (string, error) {
26
+	// Sanity Check
27
+	obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/networks?name=%s", nwName), nil, nil))
28
+	if err != nil {
29
+		return "", err
30
+	}
31
+	var nwList []networkResource
32
+	if err = json.Unmarshal(obj, &nwList); err != nil {
33
+		return "", err
34
+	}
35
+	if len(nwList) == 0 {
36
+		return "", fmt.Errorf("Network %s does not exist", nwName)
37
+	}
38
+
39
+	// Query service by name
40
+	obj, statusCode, err := readBody(cli.call("GET", fmt.Sprintf("/services?name=%s", svNameID), nil, nil))
27 41
 	if err != nil {
28 42
 		return "", err
29 43
 	}
30 44
 
31 45
 	if statusCode != http.StatusOK {
32
-		return "", fmt.Errorf("name query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj))
46
+		return "", fmt.Errorf("name query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
33 47
 	}
34 48
 
35
-	var list []*networkResource
36
-	err = json.Unmarshal(obj, &list)
37
-	if err != nil {
49
+	var list []*serviceResource
50
+	if err = json.Unmarshal(obj, &list); err != nil {
38 51
 		return "", err
39 52
 	}
40
-	if len(list) > 0 {
41
-		// name query filter will always return a single-element collection
42
-		return list[0].ID, nil
53
+	for _, sr := range list {
54
+		if sr.Network == nwName {
55
+			return sr.ID, nil
56
+		}
43 57
 	}
44 58
 
45
-	// Check for Partial-id
46
-	obj, statusCode, err = readBody(cli.call("GET", fmt.Sprintf("/networks/%s/endpoints?partial-id=%s", networkID, nameID), nil, nil))
59
+	// Query service by Partial-id (this covers full id as well)
60
+	obj, statusCode, err = readBody(cli.call("GET", fmt.Sprintf("/services?partial-id=%s", svNameID), nil, nil))
47 61
 	if err != nil {
48 62
 		return "", err
49 63
 	}
50 64
 
51 65
 	if statusCode != http.StatusOK {
52
-		return "", fmt.Errorf("partial-id match query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj))
66
+		return "", fmt.Errorf("partial-id match query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
53 67
 	}
54 68
 
55
-	err = json.Unmarshal(obj, &list)
56
-	if err != nil {
69
+	if err = json.Unmarshal(obj, &list); err != nil {
57 70
 		return "", err
58 71
 	}
59
-	if len(list) == 0 {
60
-		return "", fmt.Errorf("resource not found %s", nameID)
61
-	}
62
-	if len(list) > 1 {
63
-		return "", fmt.Errorf("multiple services matching the partial identifier (%s). Please use full identifier", nameID)
72
+	for _, sr := range list {
73
+		if sr.Network == nwName {
74
+			return sr.ID, nil
75
+		}
64 76
 	}
65
-	return list[0].ID, nil
77
+
78
+	return "", fmt.Errorf("Service %s not found on network %s", svNameID, nwName)
66 79
 }
67 80
 
68
-func lookupContainerID(cli *NetworkCli, nameID string) (string, error) {
81
+func lookupContainerID(cli *NetworkCli, cnNameID string) (string, error) {
69 82
 	// TODO : containerID to sandbox-key ?
70
-	return nameID, nil
83
+	return cnNameID, nil
71 84
 }
72 85
 
73
-// CmdService handles the network service UI
86
+// CmdService handles the service UI
74 87
 func (cli *NetworkCli) CmdService(chain string, args ...string) error {
75 88
 	cmd := cli.Subcmd(chain, "service", "COMMAND [OPTIONS] [arg...]", serviceUsage(chain), false)
76 89
 	cmd.Require(flag.Min, 1)
... ...
@@ -82,23 +95,25 @@ func (cli *NetworkCli) CmdService(chain string, args ...string) error {
82 82
 	return err
83 83
 }
84 84
 
85
-// CmdServiceCreate handles service create UI
86
-func (cli *NetworkCli) CmdServiceCreate(chain string, args ...string) error {
87
-	cmd := cli.Subcmd(chain, "create", "SERVICE NETWORK", "Creates a new service on a network", false)
88
-	cmd.Require(flag.Min, 2)
85
+// CmdServicePublish handles service create UI
86
+func (cli *NetworkCli) CmdServicePublish(chain string, args ...string) error {
87
+	cmd := cli.Subcmd(chain, "publish", "SERVICE", "Publish a new service on a network", false)
88
+	flNetwork := cmd.String([]string{"net", "-network"}, "", "Network where to publish the service")
89
+	cmd.Require(flag.Min, 1)
90
+
89 91
 	err := cmd.ParseFlags(args, true)
90 92
 	if err != nil {
91 93
 		return err
92 94
 	}
93 95
 
94
-	networkID, err := lookupNetworkID(cli, cmd.Arg(1))
95
-	if err != nil {
96
-		return err
96
+	// Default network changes will come later
97
+	nw := "docker0"
98
+	if *flNetwork != "" {
99
+		nw = *flNetwork
97 100
 	}
98 101
 
99
-	ec := endpointCreate{Name: cmd.Arg(0), NetworkID: networkID}
100
-
101
-	obj, _, err := readBody(cli.call("POST", "/networks/"+networkID+"/endpoints", ec, nil))
102
+	sc := serviceCreate{Name: cmd.Arg(0), Network: nw}
103
+	obj, _, err := readBody(cli.call("POST", "/services", sc, nil))
102 104
 	if err != nil {
103 105
 		return err
104 106
 	}
... ...
@@ -113,39 +128,40 @@ func (cli *NetworkCli) CmdServiceCreate(chain string, args ...string) error {
113 113
 	return nil
114 114
 }
115 115
 
116
-// CmdServiceRm handles service delete UI
117
-func (cli *NetworkCli) CmdServiceRm(chain string, args ...string) error {
118
-	cmd := cli.Subcmd(chain, "rm", "SERVICE NETWORK", "Deletes a service", false)
119
-	cmd.Require(flag.Min, 2)
116
+// CmdServiceUnpublish handles service delete UI
117
+func (cli *NetworkCli) CmdServiceUnpublish(chain string, args ...string) error {
118
+	cmd := cli.Subcmd(chain, "unpublish", "SERVICE", "Removes a service", false)
119
+	flNetwork := cmd.String([]string{"net", "-network"}, "", "Network where to publish the service")
120
+	cmd.Require(flag.Min, 1)
121
+
120 122
 	err := cmd.ParseFlags(args, true)
121 123
 	if err != nil {
122 124
 		return err
123 125
 	}
124 126
 
125
-	networkID, err := lookupNetworkID(cli, cmd.Arg(1))
126
-	if err != nil {
127
-		return err
127
+	// Default network changes will come later
128
+	nw := "docker0"
129
+	if *flNetwork != "" {
130
+		nw = *flNetwork
128 131
 	}
129 132
 
130
-	serviceID, err := lookupServiceID(cli, networkID, cmd.Arg(0))
133
+	serviceID, err := lookupServiceID(cli, nw, cmd.Arg(0))
131 134
 	if err != nil {
132 135
 		return err
133 136
 	}
134 137
 
135
-	_, _, err = readBody(cli.call("DELETE", "/networks/"+networkID+"/endpoints/"+serviceID, nil, nil))
136
-	if err != nil {
137
-		return err
138
-	}
139
-	return nil
138
+	_, _, err = readBody(cli.call("DELETE", "/services/"+serviceID, nil, nil))
139
+
140
+	return err
140 141
 }
141 142
 
142 143
 // CmdServiceLs handles service list UI
143 144
 func (cli *NetworkCli) CmdServiceLs(chain string, args ...string) error {
144
-	cmd := cli.Subcmd(chain, "ls", "NETWORK", "Lists all the services on a network", false)
145
+	cmd := cli.Subcmd(chain, "ls", "SERVICE", "Lists all the services on a network", false)
146
+	flNetwork := cmd.String([]string{"net", "-network"}, "", "Only show the services that are published on the specified network")
145 147
 	quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
146 148
 	noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output")
147
-	nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show the latest network created")
148
-	last := cmd.Int([]string{"n"}, -1, "Show n last created networks")
149
+
149 150
 	err := cmd.ParseFlags(args, true)
150 151
 	if err != nil {
151 152
 		return err
... ...
@@ -153,151 +169,174 @@ func (cli *NetworkCli) CmdServiceLs(chain string, args ...string) error {
153 153
 
154 154
 	cmd.Require(flag.Min, 1)
155 155
 
156
-	networkID, err := lookupNetworkID(cli, cmd.Arg(0))
157
-	if err != nil {
158
-		return err
156
+	var obj []byte
157
+	if *flNetwork == "" {
158
+		obj, _, err = readBody(cli.call("GET", "/services", nil, nil))
159
+	} else {
160
+		obj, _, err = readBody(cli.call("GET", "/services?network="+*flNetwork, nil, nil))
159 161
 	}
160
-
161
-	obj, _, err := readBody(cli.call("GET", "/networks/"+networkID+"/endpoints", nil, nil))
162 162
 	if err != nil {
163
-		fmt.Fprintf(cli.err, "%s", err.Error())
164 163
 		return err
165 164
 	}
166
-	if *last == -1 && *nLatest {
167
-		*last = 1
168
-	}
169 165
 
170
-	var endpointResources []endpointResource
171
-	err = json.Unmarshal(obj, &endpointResources)
166
+	var serviceResources []serviceResource
167
+	err = json.Unmarshal(obj, &serviceResources)
172 168
 	if err != nil {
169
+		fmt.Println(err)
173 170
 		return err
174 171
 	}
175 172
 
176 173
 	wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
177 174
 	// unless quiet (-q) is specified, print field titles
178 175
 	if !*quiet {
179
-		fmt.Fprintln(wr, "NETWORK SERVICE ID\tNAME\tNETWORK")
176
+		fmt.Fprintln(wr, "SERVICE ID\tNAME\tNETWORK\tPROVIDER")
180 177
 	}
181 178
 
182
-	for _, networkResource := range endpointResources {
183
-		ID := networkResource.ID
184
-		netName := networkResource.Name
179
+	for _, sr := range serviceResources {
180
+		ID := sr.ID
181
+		bkID, err := getBackendID(cli, ID)
182
+		if err != nil {
183
+			return err
184
+		}
185 185
 		if !*noTrunc {
186 186
 			ID = stringid.TruncateID(ID)
187
+			bkID = stringid.TruncateID(bkID)
187 188
 		}
188
-		if *quiet {
189
+		if !*quiet {
190
+			fmt.Fprintf(wr, "%s\t%s\t%s\t%s\n", ID, sr.Name, sr.Network, bkID)
191
+		} else {
189 192
 			fmt.Fprintln(wr, ID)
190
-			continue
191 193
 		}
192
-		network := networkResource.Network
193
-		fmt.Fprintf(wr, "%s\t%s\t%s",
194
-			ID,
195
-			netName,
196
-			network)
197
-		fmt.Fprint(wr, "\n")
198 194
 	}
199 195
 	wr.Flush()
200 196
 
201 197
 	return nil
202 198
 }
203 199
 
200
+func getBackendID(cli *NetworkCli, servID string) (string, error) {
201
+	var (
202
+		obj []byte
203
+		err error
204
+		bk  string
205
+	)
206
+
207
+	if obj, _, err = readBody(cli.call("GET", "/services/"+servID+"/backend", nil, nil)); err == nil {
208
+		var bkl []backendResource
209
+		if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&bkl); err == nil {
210
+			if len(bkl) > 0 {
211
+				bk = bkl[0].ID
212
+			}
213
+		} else {
214
+			// Only print a message, don't make the caller cli fail for this
215
+			fmt.Fprintf(cli.out, "Failed to retrieve provider list for service %s (%v)", servID, err)
216
+		}
217
+	}
218
+
219
+	return bk, err
220
+}
221
+
204 222
 // CmdServiceInfo handles service info UI
205 223
 func (cli *NetworkCli) CmdServiceInfo(chain string, args ...string) error {
206
-	cmd := cli.Subcmd(chain, "info", "SERVICE NETWORK", "Displays detailed information on a service", false)
207
-	cmd.Require(flag.Min, 2)
224
+	cmd := cli.Subcmd(chain, "info", "SERVICE", "Displays detailed information about a service", false)
225
+	flNetwork := cmd.String([]string{"net", "-network"}, "", "Network where to publish the service")
226
+	cmd.Require(flag.Min, 1)
227
+
208 228
 	err := cmd.ParseFlags(args, true)
209 229
 	if err != nil {
210 230
 		return err
211 231
 	}
212 232
 
213
-	networkID, err := lookupNetworkID(cli, cmd.Arg(1))
214
-	if err != nil {
215
-		return err
233
+	// Default network changes will come later
234
+	nw := "docker0"
235
+	if *flNetwork != "" {
236
+		nw = *flNetwork
216 237
 	}
217 238
 
218
-	serviceID, err := lookupServiceID(cli, networkID, cmd.Arg(0))
239
+	serviceID, err := lookupServiceID(cli, nw, cmd.Arg(0))
219 240
 	if err != nil {
220 241
 		return err
221 242
 	}
222 243
 
223
-	obj, _, err := readBody(cli.call("GET", "/networks/"+networkID+"/endpoints/"+serviceID, nil, nil))
244
+	obj, _, err := readBody(cli.call("GET", "/services/"+serviceID, nil, nil))
224 245
 	if err != nil {
225
-		fmt.Fprintf(cli.err, "%s", err.Error())
226 246
 		return err
227 247
 	}
228 248
 
229
-	endpointResource := &endpointResource{}
230
-	if err := json.NewDecoder(bytes.NewReader(obj)).Decode(endpointResource); err != nil {
249
+	sr := &serviceResource{}
250
+	if err := json.NewDecoder(bytes.NewReader(obj)).Decode(sr); err != nil {
231 251
 		return err
232 252
 	}
233
-	fmt.Fprintf(cli.out, "Service Id: %s\n", endpointResource.ID)
234
-	fmt.Fprintf(cli.out, "\tName: %s\n", endpointResource.Name)
235
-	fmt.Fprintf(cli.out, "\tNetwork: %s\n", endpointResource.Network)
253
+
254
+	fmt.Fprintf(cli.out, "Service Id: %s\n", sr.ID)
255
+	fmt.Fprintf(cli.out, "\tName: %s\n", sr.Name)
256
+	fmt.Fprintf(cli.out, "\tNetwork: %s\n", sr.Network)
236 257
 
237 258
 	return nil
238 259
 }
239 260
 
240
-// CmdServiceJoin handles service join UI
241
-func (cli *NetworkCli) CmdServiceJoin(chain string, args ...string) error {
242
-	cmd := cli.Subcmd(chain, "join", "CONTAINER SERVICE NETWORK", "Sets a container as a service backend", false)
243
-	cmd.Require(flag.Min, 3)
261
+// CmdServiceAttach handles service attach UI
262
+func (cli *NetworkCli) CmdServiceAttach(chain string, args ...string) error {
263
+	cmd := cli.Subcmd(chain, "attach", "CONTAINER SERVICE", "Sets a container as a service backend", false)
264
+	flNetwork := cmd.String([]string{"net", "-network"}, "", "Network where to publish the service")
265
+	cmd.Require(flag.Min, 2)
266
+
244 267
 	err := cmd.ParseFlags(args, true)
245 268
 	if err != nil {
246 269
 		return err
247 270
 	}
248 271
 
249
-	containerID, err := lookupContainerID(cli, cmd.Arg(0))
250
-	if err != nil {
251
-		return err
272
+	// Default network changes will come later
273
+	nw := "docker0"
274
+	if *flNetwork != "" {
275
+		nw = *flNetwork
252 276
 	}
253 277
 
254
-	networkID, err := lookupNetworkID(cli, cmd.Arg(2))
278
+	containerID, err := lookupContainerID(cli, cmd.Arg(0))
255 279
 	if err != nil {
256 280
 		return err
257 281
 	}
258 282
 
259
-	serviceID, err := lookupServiceID(cli, networkID, cmd.Arg(1))
283
+	serviceID, err := lookupServiceID(cli, nw, cmd.Arg(1))
260 284
 	if err != nil {
261 285
 		return err
262 286
 	}
263 287
 
264
-	nc := endpointJoin{ContainerID: containerID}
288
+	nc := serviceAttach{ContainerID: containerID}
265 289
 
266
-	_, _, err = readBody(cli.call("POST", "/networks/"+networkID+"/endpoints/"+serviceID+"/containers", nc, nil))
267
-	if err != nil {
268
-		fmt.Fprintf(cli.err, "%s", err.Error())
269
-		return err
270
-	}
271
-	return nil
290
+	_, _, err = readBody(cli.call("POST", "/services/"+serviceID+"/backend", nc, nil))
291
+
292
+	return err
272 293
 }
273 294
 
274
-// CmdServiceLeave handles service leave UI
275
-func (cli *NetworkCli) CmdServiceLeave(chain string, args ...string) error {
276
-	cmd := cli.Subcmd(chain, "leave", "CONTAINER SERVICE NETWORK", "Removes a container from service backend", false)
277
-	cmd.Require(flag.Min, 3)
295
+// CmdServiceDetach handles service detach UI
296
+func (cli *NetworkCli) CmdServiceDetach(chain string, args ...string) error {
297
+	cmd := cli.Subcmd(chain, "detach", "CONTAINER SERVICE", "Removes a container from service backend", false)
298
+	flNetwork := cmd.String([]string{"net", "-network"}, "", "Network where to publish the service")
299
+	cmd.Require(flag.Min, 2)
300
+
278 301
 	err := cmd.ParseFlags(args, true)
279 302
 	if err != nil {
280 303
 		return err
281 304
 	}
282 305
 
283
-	containerID, err := lookupContainerID(cli, cmd.Arg(0))
284
-	if err != nil {
285
-		return err
306
+	// Default network changes will come later
307
+	nw := "docker0"
308
+	if *flNetwork != "" {
309
+		nw = *flNetwork
286 310
 	}
287 311
 
288
-	networkID, err := lookupNetworkID(cli, cmd.Arg(2))
312
+	containerID, err := lookupContainerID(cli, cmd.Arg(0))
289 313
 	if err != nil {
290 314
 		return err
291 315
 	}
292 316
 
293
-	serviceID, err := lookupServiceID(cli, networkID, cmd.Arg(1))
317
+	serviceID, err := lookupServiceID(cli, nw, cmd.Arg(1))
294 318
 	if err != nil {
295 319
 		return err
296 320
 	}
297 321
 
298
-	_, _, err = readBody(cli.call("DELETE", "/networks/"+networkID+"/endpoints/"+serviceID+"/containers/"+containerID, nil, nil))
322
+	_, _, err = readBody(cli.call("DELETE", "/services/"+serviceID+"/backend/"+containerID, nil, nil))
299 323
 	if err != nil {
300
-		fmt.Fprintf(cli.err, "%s", err.Error())
301 324
 		return err
302 325
 	}
303 326
 	return nil
... ...
@@ -8,19 +8,24 @@ import "github.com/docker/libnetwork/types"
8 8
 
9 9
 // networkResource is the body of the "get network" http response message
10 10
 type networkResource struct {
11
-	Name      string              `json:"name"`
12
-	ID        string              `json:"id"`
13
-	Type      string              `json:"type"`
14
-	Endpoints []*endpointResource `json:"endpoints"`
11
+	Name     string             `json:"name"`
12
+	ID       string             `json:"id"`
13
+	Type     string             `json:"type"`
14
+	Services []*serviceResource `json:"services"`
15 15
 }
16 16
 
17
-// endpointResource is the body of the "get endpoint" http response message
18
-type endpointResource struct {
17
+// serviceResource is the body of the "get service" http response message
18
+type serviceResource struct {
19 19
 	Name    string `json:"name"`
20 20
 	ID      string `json:"id"`
21 21
 	Network string `json:"network"`
22 22
 }
23 23
 
24
+// backendResource is the body of "get service backend" response message
25
+type backendResource struct {
26
+	ID string `json:"id"`
27
+}
28
+
24 29
 /***********
25 30
   Body types
26 31
   ************/
... ...
@@ -32,37 +37,37 @@ type networkCreate struct {
32 32
 	Options     map[string]interface{} `json:"options"`
33 33
 }
34 34
 
35
-// endpointCreate represents the body of the "create endpoint" http request message
36
-type endpointCreate struct {
35
+// serviceCreate represents the body of the "publish service" http request message
36
+type serviceCreate struct {
37 37
 	Name         string                `json:"name"`
38
-	NetworkID    string                `json:"network_id"`
38
+	Network      string                `json:"network_name"`
39 39
 	ExposedPorts []types.TransportPort `json:"exposed_ports"`
40 40
 	PortMapping  []types.PortBinding   `json:"port_mapping"`
41 41
 }
42 42
 
43
-// endpointJoin represents the expected body of the "join endpoint" or "leave endpoint" http request messages
44
-type endpointJoin struct {
45
-	ContainerID       string                 `json:"container_id"`
46
-	HostName          string                 `json:"host_name"`
47
-	DomainName        string                 `json:"domain_name"`
48
-	HostsPath         string                 `json:"hosts_path"`
49
-	ResolvConfPath    string                 `json:"resolv_conf_path"`
50
-	DNS               []string               `json:"dns"`
51
-	ExtraHosts        []endpointExtraHost    `json:"extra_hosts"`
52
-	ParentUpdates     []endpointParentUpdate `json:"parent_updates"`
53
-	UseDefaultSandbox bool                   `json:"use_default_sandbox"`
43
+// serviceAttach represents the expected body of the "attach/detach backend to/from service" http request messages
44
+type serviceAttach struct {
45
+	ContainerID       string                `json:"container_id"`
46
+	HostName          string                `json:"host_name"`
47
+	DomainName        string                `json:"domain_name"`
48
+	HostsPath         string                `json:"hosts_path"`
49
+	ResolvConfPath    string                `json:"resolv_conf_path"`
50
+	DNS               []string              `json:"dns"`
51
+	ExtraHosts        []serviceExtraHost    `json:"extra_hosts"`
52
+	ParentUpdates     []serviceParentUpdate `json:"parent_updates"`
53
+	UseDefaultSandbox bool                  `json:"use_default_sandbox"`
54 54
 }
55 55
 
56
-// EndpointExtraHost represents the extra host object
57
-type endpointExtraHost struct {
56
+// serviceExtraHost represents the extra host object
57
+type serviceExtraHost struct {
58 58
 	Name    string `json:"name"`
59 59
 	Address string `json:"address"`
60 60
 }
61 61
 
62 62
 // EndpointParentUpdate is the object carrying the information about the
63 63
 // endpoint parent that needs to be updated
64
-type endpointParentUpdate struct {
65
-	EndpointID string `json:"endpoint_id"`
64
+type serviceParentUpdate struct {
65
+	EndpointID string `json:"service_id"`
66 66
 	Name       string `json:"name"`
67 67
 	Address    string `json:"address"`
68 68
 }
69 69
new file mode 100755
70 70
Binary files /dev/null and b/libnetwork/cmd/dnet/dnet differ
... ...
@@ -122,6 +122,10 @@ func (d *dnetConnection) dnetDaemon() error {
122 122
 	post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
123 123
 	post = r.PathPrefix("/networks").Subrouter()
124 124
 	post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
125
+	post = r.PathPrefix("/{.*}/services").Subrouter()
126
+	post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
127
+	post = r.PathPrefix("/services").Subrouter()
128
+	post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
125 129
 	return http.ListenAndServe(d.addr, r)
126 130
 }
127 131