瀏覽代碼

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 10 年之前
父節點
當前提交
7de9f71eb5

+ 22 - 0
libnetwork/api/api.go

@@ -96,6 +96,7 @@ func (h *httpHandler) initRouter() {
 			{"/services", []string{"partial-id", epPID}, procGetServices},
 			{"/services", nil, procGetServices},
 			{"/services/" + epID, nil, procGetService},
+			{"/services/" + epID + "/backend", nil, procGetContainers},
 		},
 		"POST": {
 			{"/networks", nil, procCreateNetwork},
@@ -184,6 +185,14 @@ func buildEndpointResource(ep libnetwork.Endpoint) *endpointResource {
 	return r
 }
 
+func buildContainerResource(ci libnetwork.ContainerInfo) *containerResource {
+	r := &containerResource{}
+	if ci != nil {
+		r.ID = ci.ID()
+	}
+	return r
+}
+
 /****************
  Options Parsers
 *****************/
@@ -527,6 +536,19 @@ func procGetService(c libnetwork.NetworkController, vars map[string]string, body
 	return buildEndpointResource(sv), &successResponse
 }
 
+func procGetContainers(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+	epT, epBy := detectEndpointTarget(vars)
+	sv, errRsp := findService(c, epT, epBy)
+	if !errRsp.isOK() {
+		return nil, endpointToService(errRsp)
+	}
+	var list []*containerResource
+	if sv.ContainerInfo() != nil {
+		list = append(list, buildContainerResource(sv.ContainerInfo()))
+	}
+	return list, &successResponse
+}
+
 func procPublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
 	var sp servicePublish
 

+ 45 - 0
libnetwork/api/api_test.go

@@ -70,6 +70,14 @@ func i2nL(i interface{}) []*networkResource {
 	return s
 }
 
+func i2cL(i interface{}) []*containerResource {
+	s, ok := i.([]*containerResource)
+	if !ok {
+		panic(fmt.Sprintf("Failed i2cL for %v", i))
+	}
+	return s
+}
+
 func createTestNetwork(t *testing.T, network string) (libnetwork.NetworkController, libnetwork.Network) {
 	c, err := libnetwork.New("")
 	if err != nil {
@@ -904,6 +912,14 @@ func TestAttachDetachBackend(t *testing.T) {
 		t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
 	}
 
+	_, errRsp = procGetContainers(c, vars, nil)
+	if errRsp.isOK() {
+		t.Fatalf("Expected failure. Got %v", errRsp)
+	}
+	if errRsp.StatusCode != http.StatusNotFound {
+		t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
+	}
+
 	vars[urlEpName] = "db"
 	_, errRsp = procAttachBackend(c, vars, bad)
 	if errRsp == &successResponse {
@@ -925,6 +941,26 @@ func TestAttachDetachBackend(t *testing.T) {
 		t.Fatalf("Unexpected failure, got: %v", errRsp)
 	}
 
+	cli, errRsp := procGetContainers(c, vars, nil)
+	if errRsp != &successResponse {
+		t.Fatalf("Unexpected failure, got: %v", errRsp)
+	}
+	cl := i2cL(cli)
+	if len(cl) != 1 {
+		t.Fatalf("Did not find expected number of containers attached to the service: %d", len(cl))
+	}
+	if cl[0].ID != cid {
+		t.Fatalf("Did not find expected container attached to the service: %v", cl[0])
+	}
+
+	_, errRsp = procUnpublishService(c, vars, nil)
+	if errRsp.isOK() {
+		t.Fatalf("Expected failure but succeeded")
+	}
+	if errRsp.StatusCode != http.StatusForbidden {
+		t.Fatalf("Expected %d. Got: %v", http.StatusForbidden, errRsp)
+	}
+
 	vars[urlEpName] = "endpoint"
 	_, errRsp = procDetachBackend(c, vars, nil)
 	if errRsp == &successResponse {
@@ -949,6 +985,15 @@ func TestAttachDetachBackend(t *testing.T) {
 		t.Fatalf("Unexpected failure, got: %v", errRsp)
 	}
 
+	cli, errRsp = procGetContainers(c, vars, nil)
+	if errRsp != &successResponse {
+		t.Fatalf("Unexpected failure, got: %v", errRsp)
+	}
+	cl = i2cL(cli)
+	if len(cl) != 0 {
+		t.Fatalf("Did not find expected number of containers attached to the service: %d", len(cl))
+	}
+
 	err = ep1.Delete()
 	if err != nil {
 		t.Fatal(err)

+ 7 - 1
libnetwork/api/types.go

@@ -21,6 +21,12 @@ type endpointResource struct {
 	Network string `json:"network"`
 }
 
+// containerResource is the body of "get service backend" response message
+type containerResource struct {
+	ID string `json:"id"`
+	// will add more fields once labels change is in
+}
+
 /***********
   Body types
   ************/
@@ -55,7 +61,7 @@ type endpointJoin struct {
 // servicePublish represents the body of the "publish service" http request message
 type servicePublish struct {
 	Name         string                `json:"name"`
-	Network      string                `json:"network"`
+	Network      string                `json:"network_name"`
 	ExposedPorts []types.TransportPort `json:"exposed_ports"`
 	PortMapping  []types.PortBinding   `json:"port_mapping"`
 }

+ 22 - 22
libnetwork/client/client_service_test.go

@@ -7,7 +7,7 @@ import (
 	_ "github.com/docker/libnetwork/netutils"
 )
 
-func TestClientNetworkServiceInvalidCommand(t *testing.T) {
+func TestClientServiceInvalidCommand(t *testing.T) {
 	var out, errOut bytes.Buffer
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
 
@@ -17,73 +17,73 @@ func TestClientNetworkServiceInvalidCommand(t *testing.T) {
 	}
 }
 
-func TestClientNetworkServiceCreate(t *testing.T) {
+func TestClientServiceCreate(t *testing.T) {
 	var out, errOut bytes.Buffer
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
 
-	err := cli.Cmd("docker", "service", "create", mockServiceName, mockNwName)
+	err := cli.Cmd("docker", "service", "publish", "-net="+mockNwName, mockServiceName)
 	if err != nil {
-		t.Fatal(err.Error())
+		t.Fatal(err)
 	}
 }
 
-func TestClientNetworkServiceRm(t *testing.T) {
+func TestClientServiceRm(t *testing.T) {
 	var out, errOut bytes.Buffer
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
 
-	err := cli.Cmd("docker", "service", "rm", mockServiceName, mockNwName)
+	err := cli.Cmd("docker", "service", "unpublish", "-net="+mockNwName, mockServiceName)
 	if err != nil {
-		t.Fatal(err.Error())
+		t.Fatal(err)
 	}
 }
 
-func TestClientNetworkServiceLs(t *testing.T) {
+func TestClientServiceLs(t *testing.T) {
 	var out, errOut bytes.Buffer
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
 
-	err := cli.Cmd("docker", "service", "ls", mockNwName)
+	err := cli.Cmd("docker", "service", "ls")
 	if err != nil {
-		t.Fatal(err.Error())
+		t.Fatal(err)
 	}
 }
 
-func TestClientNetworkServiceInfo(t *testing.T) {
+func TestClientServiceInfo(t *testing.T) {
 	var out, errOut bytes.Buffer
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
 
-	err := cli.Cmd("docker", "service", "info", mockServiceName, mockNwName)
+	err := cli.Cmd("docker", "service", "info", "-net="+mockNwName, mockServiceName)
 	if err != nil {
-		t.Fatal(err.Error())
+		t.Fatal(err)
 	}
 }
 
-func TestClientNetworkServiceInfoById(t *testing.T) {
+func TestClientServiceInfoById(t *testing.T) {
 	var out, errOut bytes.Buffer
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
 
-	err := cli.Cmd("docker", "service", "info", mockServiceID, mockNwID)
+	err := cli.Cmd("docker", "service", "info", "-net="+mockNwName, mockServiceID)
 	if err != nil {
-		t.Fatal(err.Error())
+		t.Fatal(err)
 	}
 }
 
-func TestClientNetworkServiceJoin(t *testing.T) {
+func TestClientServiceJoin(t *testing.T) {
 	var out, errOut bytes.Buffer
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
 
-	err := cli.Cmd("docker", "service", "join", mockContainerID, mockServiceName, mockNwName)
+	err := cli.Cmd("docker", "service", "attach", "-net="+mockNwName, mockContainerID, mockServiceName)
 	if err != nil {
-		t.Fatal(err.Error())
+		t.Fatal(err)
 	}
 }
 
-func TestClientNetworkServiceLeave(t *testing.T) {
+func TestClientServiceLeave(t *testing.T) {
 	var out, errOut bytes.Buffer
 	cli := NewNetworkCli(&out, &errOut, callbackFunc)
 
-	err := cli.Cmd("docker", "service", "leave", mockContainerID, mockServiceName, mockNwName)
+	err := cli.Cmd("docker", "service", "detach", "-net="+mockNwName, mockContainerID, mockServiceName)
 	if err != nil {
-		t.Fatal(err.Error())
+		t.Fatal(err)
 	}
 }
 

+ 10 - 10
libnetwork/client/client_test.go

@@ -39,8 +39,8 @@ func setupMockHTTPCallback() {
 	list = append(list, nw)
 	mockNwListJSON, _ = json.Marshal(list)
 
-	var srvList []endpointResource
-	ep := endpointResource{Name: mockServiceName, ID: mockServiceID, Network: mockNwName}
+	var srvList []serviceResource
+	ep := serviceResource{Name: mockServiceName, ID: mockServiceID, Network: mockNwName}
 	mockServiceJSON, _ = json.Marshal(ep)
 	srvList = append(srvList, ep)
 	mockServiceListJSON, _ = json.Marshal(srvList)
@@ -61,26 +61,26 @@ func setupMockHTTPCallback() {
 				rsp = string(mockNwListJSON)
 			} else if strings.HasSuffix(path, "networks/"+mockNwID) {
 				rsp = string(mockNwJSON)
-			} else if strings.Contains(path, fmt.Sprintf("endpoints?name=%s", mockServiceName)) {
+			} else if strings.Contains(path, fmt.Sprintf("services?name=%s", mockServiceName)) {
 				rsp = string(mockServiceListJSON)
-			} else if strings.Contains(path, "endpoints?name=") {
+			} else if strings.Contains(path, "services?name=") {
 				rsp = "[]"
-			} else if strings.Contains(path, fmt.Sprintf("endpoints?partial-id=%s", mockServiceID)) {
+			} else if strings.Contains(path, fmt.Sprintf("services?partial-id=%s", mockServiceID)) {
 				rsp = string(mockServiceListJSON)
-			} else if strings.Contains(path, "endpoints?partial-id=") {
+			} else if strings.Contains(path, "services?partial-id=") {
 				rsp = "[]"
-			} else if strings.HasSuffix(path, "endpoints") {
+			} else if strings.HasSuffix(path, "services") {
 				rsp = string(mockServiceListJSON)
-			} else if strings.HasSuffix(path, "endpoints/"+mockServiceID) {
+			} else if strings.HasSuffix(path, "services/"+mockServiceID) {
 				rsp = string(mockServiceJSON)
 			}
 		case "POST":
 			var data []byte
 			if strings.HasSuffix(path, "networks") {
 				data, _ = json.Marshal(mockNwID)
-			} else if strings.HasSuffix(path, "endpoints") {
+			} else if strings.HasSuffix(path, "services") {
 				data, _ = json.Marshal(mockServiceID)
-			} else if strings.HasSuffix(path, "containers") {
+			} else if strings.HasSuffix(path, "backend") {
 				data, _ = json.Marshal(mockContainerID)
 			}
 			rsp = string(data)

+ 4 - 4
libnetwork/client/network.go

@@ -177,10 +177,10 @@ func (cli *NetworkCli) CmdNetworkInfo(chain string, args ...string) error {
 	fmt.Fprintf(cli.out, "Network Id: %s\n", networkResource.ID)
 	fmt.Fprintf(cli.out, "Name: %s\n", networkResource.Name)
 	fmt.Fprintf(cli.out, "Type: %s\n", networkResource.Type)
-	if networkResource.Endpoints != nil {
-		for _, endpointResource := range networkResource.Endpoints {
-			fmt.Fprintf(cli.out, "  Service Id: %s\n", endpointResource.ID)
-			fmt.Fprintf(cli.out, "\tName: %s\n", endpointResource.Name)
+	if networkResource.Services != nil {
+		for _, serviceResource := range networkResource.Services {
+			fmt.Fprintf(cli.out, "  Service Id: %s\n", serviceResource.ID)
+			fmt.Fprintf(cli.out, "\tName: %s\n", serviceResource.Name)
 		}
 	}
 

+ 157 - 118
libnetwork/client/service.go

@@ -13,64 +13,77 @@ import (
 
 var (
 	serviceCommands = []command{
-		{"create", "Create a service endpoint"},
-		{"rm", "Remove a service endpoint"},
-		{"join", "Join a container to a service endpoint"},
-		{"leave", "Leave a container from a service endpoint"},
-		{"ls", "Lists all service endpoints on a network"},
-		{"info", "Display information of a service endpoint"},
+		{"publish", "Publish a service"},
+		{"unpublish", "Remove a service"},
+		{"attach", "Attach a provider (container) to the service"},
+		{"detach", "Detach the provider from the service"},
+		{"ls", "Lists all services"},
+		{"info", "Display information about a service"},
 	}
 )
 
-func lookupServiceID(cli *NetworkCli, networkID string, nameID string) (string, error) {
-	obj, statusCode, err := readBody(cli.call("GET", fmt.Sprintf("/networks/%s/endpoints?name=%s", networkID, nameID), nil, nil))
+func lookupServiceID(cli *NetworkCli, nwName, svNameID string) (string, error) {
+	// Sanity Check
+	obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/networks?name=%s", nwName), nil, nil))
+	if err != nil {
+		return "", err
+	}
+	var nwList []networkResource
+	if err = json.Unmarshal(obj, &nwList); err != nil {
+		return "", err
+	}
+	if len(nwList) == 0 {
+		return "", fmt.Errorf("Network %s does not exist", nwName)
+	}
+
+	// Query service by name
+	obj, statusCode, err := readBody(cli.call("GET", fmt.Sprintf("/services?name=%s", svNameID), nil, nil))
 	if err != nil {
 		return "", err
 	}
 
 	if statusCode != http.StatusOK {
-		return "", fmt.Errorf("name query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj))
+		return "", fmt.Errorf("name query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
 	}
 
-	var list []*networkResource
-	err = json.Unmarshal(obj, &list)
-	if err != nil {
+	var list []*serviceResource
+	if err = json.Unmarshal(obj, &list); err != nil {
 		return "", err
 	}
-	if len(list) > 0 {
-		// name query filter will always return a single-element collection
-		return list[0].ID, nil
+	for _, sr := range list {
+		if sr.Network == nwName {
+			return sr.ID, nil
+		}
 	}
 
-	// Check for Partial-id
-	obj, statusCode, err = readBody(cli.call("GET", fmt.Sprintf("/networks/%s/endpoints?partial-id=%s", networkID, nameID), nil, nil))
+	// Query service by Partial-id (this covers full id as well)
+	obj, statusCode, err = readBody(cli.call("GET", fmt.Sprintf("/services?partial-id=%s", svNameID), nil, nil))
 	if err != nil {
 		return "", err
 	}
 
 	if statusCode != http.StatusOK {
-		return "", fmt.Errorf("partial-id match query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj))
+		return "", fmt.Errorf("partial-id match query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
 	}
 
-	err = json.Unmarshal(obj, &list)
-	if err != nil {
+	if err = json.Unmarshal(obj, &list); err != nil {
 		return "", err
 	}
-	if len(list) == 0 {
-		return "", fmt.Errorf("resource not found %s", nameID)
-	}
-	if len(list) > 1 {
-		return "", fmt.Errorf("multiple services matching the partial identifier (%s). Please use full identifier", nameID)
+	for _, sr := range list {
+		if sr.Network == nwName {
+			return sr.ID, nil
+		}
 	}
-	return list[0].ID, nil
+
+	return "", fmt.Errorf("Service %s not found on network %s", svNameID, nwName)
 }
 
-func lookupContainerID(cli *NetworkCli, nameID string) (string, error) {
+func lookupContainerID(cli *NetworkCli, cnNameID string) (string, error) {
 	// TODO : containerID to sandbox-key ?
-	return nameID, nil
+	return cnNameID, nil
 }
 
-// CmdService handles the network service UI
+// CmdService handles the service UI
 func (cli *NetworkCli) CmdService(chain string, args ...string) error {
 	cmd := cli.Subcmd(chain, "service", "COMMAND [OPTIONS] [arg...]", serviceUsage(chain), false)
 	cmd.Require(flag.Min, 1)
@@ -82,23 +95,25 @@ func (cli *NetworkCli) CmdService(chain string, args ...string) error {
 	return err
 }
 
-// CmdServiceCreate handles service create UI
-func (cli *NetworkCli) CmdServiceCreate(chain string, args ...string) error {
-	cmd := cli.Subcmd(chain, "create", "SERVICE NETWORK", "Creates a new service on a network", false)
-	cmd.Require(flag.Min, 2)
+// CmdServicePublish handles service create UI
+func (cli *NetworkCli) CmdServicePublish(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "publish", "SERVICE", "Publish a new service on a network", false)
+	flNetwork := cmd.String([]string{"net", "-network"}, "", "Network where to publish the service")
+	cmd.Require(flag.Min, 1)
+
 	err := cmd.ParseFlags(args, true)
 	if err != nil {
 		return err
 	}
 
-	networkID, err := lookupNetworkID(cli, cmd.Arg(1))
-	if err != nil {
-		return err
+	// Default network changes will come later
+	nw := "docker0"
+	if *flNetwork != "" {
+		nw = *flNetwork
 	}
 
-	ec := endpointCreate{Name: cmd.Arg(0), NetworkID: networkID}
-
-	obj, _, err := readBody(cli.call("POST", "/networks/"+networkID+"/endpoints", ec, nil))
+	sc := serviceCreate{Name: cmd.Arg(0), Network: nw}
+	obj, _, err := readBody(cli.call("POST", "/services", sc, nil))
 	if err != nil {
 		return err
 	}
@@ -113,39 +128,40 @@ func (cli *NetworkCli) CmdServiceCreate(chain string, args ...string) error {
 	return nil
 }
 
-// CmdServiceRm handles service delete UI
-func (cli *NetworkCli) CmdServiceRm(chain string, args ...string) error {
-	cmd := cli.Subcmd(chain, "rm", "SERVICE NETWORK", "Deletes a service", false)
-	cmd.Require(flag.Min, 2)
+// CmdServiceUnpublish handles service delete UI
+func (cli *NetworkCli) CmdServiceUnpublish(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "unpublish", "SERVICE", "Removes a service", false)
+	flNetwork := cmd.String([]string{"net", "-network"}, "", "Network where to publish the service")
+	cmd.Require(flag.Min, 1)
+
 	err := cmd.ParseFlags(args, true)
 	if err != nil {
 		return err
 	}
 
-	networkID, err := lookupNetworkID(cli, cmd.Arg(1))
-	if err != nil {
-		return err
+	// Default network changes will come later
+	nw := "docker0"
+	if *flNetwork != "" {
+		nw = *flNetwork
 	}
 
-	serviceID, err := lookupServiceID(cli, networkID, cmd.Arg(0))
+	serviceID, err := lookupServiceID(cli, nw, cmd.Arg(0))
 	if err != nil {
 		return err
 	}
 
-	_, _, err = readBody(cli.call("DELETE", "/networks/"+networkID+"/endpoints/"+serviceID, nil, nil))
-	if err != nil {
-		return err
-	}
-	return nil
+	_, _, err = readBody(cli.call("DELETE", "/services/"+serviceID, nil, nil))
+
+	return err
 }
 
 // CmdServiceLs handles service list UI
 func (cli *NetworkCli) CmdServiceLs(chain string, args ...string) error {
-	cmd := cli.Subcmd(chain, "ls", "NETWORK", "Lists all the services on a network", false)
+	cmd := cli.Subcmd(chain, "ls", "SERVICE", "Lists all the services on a network", false)
+	flNetwork := cmd.String([]string{"net", "-network"}, "", "Only show the services that are published on the specified network")
 	quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
 	noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output")
-	nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show the latest network created")
-	last := cmd.Int([]string{"n"}, -1, "Show n last created networks")
+
 	err := cmd.ParseFlags(args, true)
 	if err != nil {
 		return err
@@ -153,151 +169,174 @@ func (cli *NetworkCli) CmdServiceLs(chain string, args ...string) error {
 
 	cmd.Require(flag.Min, 1)
 
-	networkID, err := lookupNetworkID(cli, cmd.Arg(0))
-	if err != nil {
-		return err
+	var obj []byte
+	if *flNetwork == "" {
+		obj, _, err = readBody(cli.call("GET", "/services", nil, nil))
+	} else {
+		obj, _, err = readBody(cli.call("GET", "/services?network="+*flNetwork, nil, nil))
 	}
-
-	obj, _, err := readBody(cli.call("GET", "/networks/"+networkID+"/endpoints", nil, nil))
 	if err != nil {
-		fmt.Fprintf(cli.err, "%s", err.Error())
 		return err
 	}
-	if *last == -1 && *nLatest {
-		*last = 1
-	}
 
-	var endpointResources []endpointResource
-	err = json.Unmarshal(obj, &endpointResources)
+	var serviceResources []serviceResource
+	err = json.Unmarshal(obj, &serviceResources)
 	if err != nil {
+		fmt.Println(err)
 		return err
 	}
 
 	wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
 	// unless quiet (-q) is specified, print field titles
 	if !*quiet {
-		fmt.Fprintln(wr, "NETWORK SERVICE ID\tNAME\tNETWORK")
+		fmt.Fprintln(wr, "SERVICE ID\tNAME\tNETWORK\tPROVIDER")
 	}
 
-	for _, networkResource := range endpointResources {
-		ID := networkResource.ID
-		netName := networkResource.Name
+	for _, sr := range serviceResources {
+		ID := sr.ID
+		bkID, err := getBackendID(cli, ID)
+		if err != nil {
+			return err
+		}
 		if !*noTrunc {
 			ID = stringid.TruncateID(ID)
+			bkID = stringid.TruncateID(bkID)
 		}
-		if *quiet {
+		if !*quiet {
+			fmt.Fprintf(wr, "%s\t%s\t%s\t%s\n", ID, sr.Name, sr.Network, bkID)
+		} else {
 			fmt.Fprintln(wr, ID)
-			continue
 		}
-		network := networkResource.Network
-		fmt.Fprintf(wr, "%s\t%s\t%s",
-			ID,
-			netName,
-			network)
-		fmt.Fprint(wr, "\n")
 	}
 	wr.Flush()
 
 	return nil
 }
 
+func getBackendID(cli *NetworkCli, servID string) (string, error) {
+	var (
+		obj []byte
+		err error
+		bk  string
+	)
+
+	if obj, _, err = readBody(cli.call("GET", "/services/"+servID+"/backend", nil, nil)); err == nil {
+		var bkl []backendResource
+		if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&bkl); err == nil {
+			if len(bkl) > 0 {
+				bk = bkl[0].ID
+			}
+		} else {
+			// Only print a message, don't make the caller cli fail for this
+			fmt.Fprintf(cli.out, "Failed to retrieve provider list for service %s (%v)", servID, err)
+		}
+	}
+
+	return bk, err
+}
+
 // CmdServiceInfo handles service info UI
 func (cli *NetworkCli) CmdServiceInfo(chain string, args ...string) error {
-	cmd := cli.Subcmd(chain, "info", "SERVICE NETWORK", "Displays detailed information on a service", false)
-	cmd.Require(flag.Min, 2)
+	cmd := cli.Subcmd(chain, "info", "SERVICE", "Displays detailed information about a service", false)
+	flNetwork := cmd.String([]string{"net", "-network"}, "", "Network where to publish the service")
+	cmd.Require(flag.Min, 1)
+
 	err := cmd.ParseFlags(args, true)
 	if err != nil {
 		return err
 	}
 
-	networkID, err := lookupNetworkID(cli, cmd.Arg(1))
-	if err != nil {
-		return err
+	// Default network changes will come later
+	nw := "docker0"
+	if *flNetwork != "" {
+		nw = *flNetwork
 	}
 
-	serviceID, err := lookupServiceID(cli, networkID, cmd.Arg(0))
+	serviceID, err := lookupServiceID(cli, nw, cmd.Arg(0))
 	if err != nil {
 		return err
 	}
 
-	obj, _, err := readBody(cli.call("GET", "/networks/"+networkID+"/endpoints/"+serviceID, nil, nil))
+	obj, _, err := readBody(cli.call("GET", "/services/"+serviceID, nil, nil))
 	if err != nil {
-		fmt.Fprintf(cli.err, "%s", err.Error())
 		return err
 	}
 
-	endpointResource := &endpointResource{}
-	if err := json.NewDecoder(bytes.NewReader(obj)).Decode(endpointResource); err != nil {
+	sr := &serviceResource{}
+	if err := json.NewDecoder(bytes.NewReader(obj)).Decode(sr); err != nil {
 		return err
 	}
-	fmt.Fprintf(cli.out, "Service Id: %s\n", endpointResource.ID)
-	fmt.Fprintf(cli.out, "\tName: %s\n", endpointResource.Name)
-	fmt.Fprintf(cli.out, "\tNetwork: %s\n", endpointResource.Network)
+
+	fmt.Fprintf(cli.out, "Service Id: %s\n", sr.ID)
+	fmt.Fprintf(cli.out, "\tName: %s\n", sr.Name)
+	fmt.Fprintf(cli.out, "\tNetwork: %s\n", sr.Network)
 
 	return nil
 }
 
-// CmdServiceJoin handles service join UI
-func (cli *NetworkCli) CmdServiceJoin(chain string, args ...string) error {
-	cmd := cli.Subcmd(chain, "join", "CONTAINER SERVICE NETWORK", "Sets a container as a service backend", false)
-	cmd.Require(flag.Min, 3)
+// CmdServiceAttach handles service attach UI
+func (cli *NetworkCli) CmdServiceAttach(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "attach", "CONTAINER SERVICE", "Sets a container as a service backend", false)
+	flNetwork := cmd.String([]string{"net", "-network"}, "", "Network where to publish the service")
+	cmd.Require(flag.Min, 2)
+
 	err := cmd.ParseFlags(args, true)
 	if err != nil {
 		return err
 	}
 
-	containerID, err := lookupContainerID(cli, cmd.Arg(0))
-	if err != nil {
-		return err
+	// Default network changes will come later
+	nw := "docker0"
+	if *flNetwork != "" {
+		nw = *flNetwork
 	}
 
-	networkID, err := lookupNetworkID(cli, cmd.Arg(2))
+	containerID, err := lookupContainerID(cli, cmd.Arg(0))
 	if err != nil {
 		return err
 	}
 
-	serviceID, err := lookupServiceID(cli, networkID, cmd.Arg(1))
+	serviceID, err := lookupServiceID(cli, nw, cmd.Arg(1))
 	if err != nil {
 		return err
 	}
 
-	nc := endpointJoin{ContainerID: containerID}
+	nc := serviceAttach{ContainerID: containerID}
 
-	_, _, err = readBody(cli.call("POST", "/networks/"+networkID+"/endpoints/"+serviceID+"/containers", nc, nil))
-	if err != nil {
-		fmt.Fprintf(cli.err, "%s", err.Error())
-		return err
-	}
-	return nil
+	_, _, err = readBody(cli.call("POST", "/services/"+serviceID+"/backend", nc, nil))
+
+	return err
 }
 
-// CmdServiceLeave handles service leave UI
-func (cli *NetworkCli) CmdServiceLeave(chain string, args ...string) error {
-	cmd := cli.Subcmd(chain, "leave", "CONTAINER SERVICE NETWORK", "Removes a container from service backend", false)
-	cmd.Require(flag.Min, 3)
+// CmdServiceDetach handles service detach UI
+func (cli *NetworkCli) CmdServiceDetach(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "detach", "CONTAINER SERVICE", "Removes a container from service backend", false)
+	flNetwork := cmd.String([]string{"net", "-network"}, "", "Network where to publish the service")
+	cmd.Require(flag.Min, 2)
+
 	err := cmd.ParseFlags(args, true)
 	if err != nil {
 		return err
 	}
 
-	containerID, err := lookupContainerID(cli, cmd.Arg(0))
-	if err != nil {
-		return err
+	// Default network changes will come later
+	nw := "docker0"
+	if *flNetwork != "" {
+		nw = *flNetwork
 	}
 
-	networkID, err := lookupNetworkID(cli, cmd.Arg(2))
+	containerID, err := lookupContainerID(cli, cmd.Arg(0))
 	if err != nil {
 		return err
 	}
 
-	serviceID, err := lookupServiceID(cli, networkID, cmd.Arg(1))
+	serviceID, err := lookupServiceID(cli, nw, cmd.Arg(1))
 	if err != nil {
 		return err
 	}
 
-	_, _, err = readBody(cli.call("DELETE", "/networks/"+networkID+"/endpoints/"+serviceID+"/containers/"+containerID, nil, nil))
+	_, _, err = readBody(cli.call("DELETE", "/services/"+serviceID+"/backend/"+containerID, nil, nil))
 	if err != nil {
-		fmt.Fprintf(cli.err, "%s", err.Error())
 		return err
 	}
 	return nil

+ 29 - 24
libnetwork/client/types.go

@@ -8,19 +8,24 @@ import "github.com/docker/libnetwork/types"
 
 // networkResource is the body of the "get network" http response message
 type networkResource struct {
-	Name      string              `json:"name"`
-	ID        string              `json:"id"`
-	Type      string              `json:"type"`
-	Endpoints []*endpointResource `json:"endpoints"`
+	Name     string             `json:"name"`
+	ID       string             `json:"id"`
+	Type     string             `json:"type"`
+	Services []*serviceResource `json:"services"`
 }
 
-// endpointResource is the body of the "get endpoint" http response message
-type endpointResource struct {
+// serviceResource is the body of the "get service" http response message
+type serviceResource struct {
 	Name    string `json:"name"`
 	ID      string `json:"id"`
 	Network string `json:"network"`
 }
 
+// backendResource is the body of "get service backend" response message
+type backendResource struct {
+	ID string `json:"id"`
+}
+
 /***********
   Body types
   ************/
@@ -32,37 +37,37 @@ type networkCreate struct {
 	Options     map[string]interface{} `json:"options"`
 }
 
-// endpointCreate represents the body of the "create endpoint" http request message
-type endpointCreate struct {
+// serviceCreate represents the body of the "publish service" http request message
+type serviceCreate struct {
 	Name         string                `json:"name"`
-	NetworkID    string                `json:"network_id"`
+	Network      string                `json:"network_name"`
 	ExposedPorts []types.TransportPort `json:"exposed_ports"`
 	PortMapping  []types.PortBinding   `json:"port_mapping"`
 }
 
-// endpointJoin represents the expected body of the "join endpoint" or "leave endpoint" http request messages
-type endpointJoin struct {
-	ContainerID       string                 `json:"container_id"`
-	HostName          string                 `json:"host_name"`
-	DomainName        string                 `json:"domain_name"`
-	HostsPath         string                 `json:"hosts_path"`
-	ResolvConfPath    string                 `json:"resolv_conf_path"`
-	DNS               []string               `json:"dns"`
-	ExtraHosts        []endpointExtraHost    `json:"extra_hosts"`
-	ParentUpdates     []endpointParentUpdate `json:"parent_updates"`
-	UseDefaultSandbox bool                   `json:"use_default_sandbox"`
+// serviceAttach represents the expected body of the "attach/detach backend to/from service" http request messages
+type serviceAttach struct {
+	ContainerID       string                `json:"container_id"`
+	HostName          string                `json:"host_name"`
+	DomainName        string                `json:"domain_name"`
+	HostsPath         string                `json:"hosts_path"`
+	ResolvConfPath    string                `json:"resolv_conf_path"`
+	DNS               []string              `json:"dns"`
+	ExtraHosts        []serviceExtraHost    `json:"extra_hosts"`
+	ParentUpdates     []serviceParentUpdate `json:"parent_updates"`
+	UseDefaultSandbox bool                  `json:"use_default_sandbox"`
 }
 
-// EndpointExtraHost represents the extra host object
-type endpointExtraHost struct {
+// serviceExtraHost represents the extra host object
+type serviceExtraHost struct {
 	Name    string `json:"name"`
 	Address string `json:"address"`
 }
 
 // EndpointParentUpdate is the object carrying the information about the
 // endpoint parent that needs to be updated
-type endpointParentUpdate struct {
-	EndpointID string `json:"endpoint_id"`
+type serviceParentUpdate struct {
+	EndpointID string `json:"service_id"`
 	Name       string `json:"name"`
 	Address    string `json:"address"`
 }

二進制
libnetwork/cmd/dnet/dnet


+ 4 - 0
libnetwork/cmd/dnet/dnet.go

@@ -122,6 +122,10 @@ func (d *dnetConnection) dnetDaemon() error {
 	post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
 	post = r.PathPrefix("/networks").Subrouter()
 	post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
+	post = r.PathPrefix("/{.*}/services").Subrouter()
+	post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
+	post = r.PathPrefix("/services").Subrouter()
+	post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
 	return http.ListenAndServe(d.addr, r)
 }