From 5ee5e2452f7fd1c184eab31c7a5f10389c827375 Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Thu, 21 May 2015 03:58:30 -0700 Subject: [PATCH] Modified Client to make use of the corrected REST API Also supporting name, id & partial-id lookups for all the network commands Signed-off-by: Madhu Venugopal --- libnetwork/client/client_test.go | 112 ++++++++++++++++++++----------- libnetwork/client/network.go | 75 ++++++++++++++++++--- libnetwork/cmd/dnet/dnet.go | 4 +- 3 files changed, 139 insertions(+), 52 deletions(-) diff --git a/libnetwork/client/client_test.go b/libnetwork/client/client_test.go index fbb5ba240a..582c9b9a21 100644 --- a/libnetwork/client/client_test.go +++ b/libnetwork/client/client_test.go @@ -2,7 +2,10 @@ package client import ( "bytes" + "encoding/json" "io" + "os" + "strings" "testing" _ "github.com/docker/libnetwork/netutils" @@ -15,12 +18,46 @@ type nopCloser struct { func (nopCloser) Close() error { return nil } +func TestMain(m *testing.M) { + setupMockHTTPCallback() + os.Exit(m.Run()) +} + +var callbackFunc func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) +var mockNwJSON, mockNwListJSON []byte +var mockNwName = "test" +var mockNwID = "23456789" + +func setupMockHTTPCallback() { + var list []networkResource + nw := networkResource{Name: mockNwName, ID: mockNwID} + mockNwJSON, _ = json.Marshal(nw) + list = append(list, nw) + mockNwListJSON, _ = json.Marshal(list) + callbackFunc = func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { + var rsp string + switch method { + case "GET": + if strings.Contains(path, "networks?name=") { + rsp = string(mockNwListJSON) + } else if strings.HasSuffix(path, "networks") { + rsp = string(mockNwListJSON) + } else if strings.HasSuffix(path, "networks/"+mockNwID) { + rsp = string(mockNwJSON) + } + case "POST": + rsp = mockNwID + case "PUT": + case "DELETE": + rsp = "" + } + return nopCloser{bytes.NewBufferString(rsp)}, 200, nil + } +} + func TestClientDummyCommand(t *testing.T) { var out, errOut bytes.Buffer - cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { - return nopCloser{bytes.NewBufferString("")}, 200, nil - } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) err := cli.Cmd("docker", "dummy") if err == nil { @@ -30,10 +67,7 @@ func TestClientDummyCommand(t *testing.T) { func TestClientNetworkInvalidCommand(t *testing.T) { var out, errOut bytes.Buffer - cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { - return nopCloser{bytes.NewBufferString("")}, 200, nil - } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) err := cli.Cmd("docker", "network", "invalid") if err == nil { @@ -43,12 +77,9 @@ func TestClientNetworkInvalidCommand(t *testing.T) { func TestClientNetworkCreate(t *testing.T) { var out, errOut bytes.Buffer - cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { - return nopCloser{bytes.NewBufferString("")}, 200, nil - } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) - err := cli.Cmd("docker", "network", "create", "test") + err := cli.Cmd("docker", "network", "create", mockNwName) if err != nil { t.Fatal(err.Error()) } @@ -56,17 +87,14 @@ func TestClientNetworkCreate(t *testing.T) { func TestClientNetworkCreateWithDriver(t *testing.T) { var out, errOut bytes.Buffer - cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { - return nopCloser{bytes.NewBufferString("")}, 200, nil - } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) - err := cli.Cmd("docker", "network", "create", "-f=dummy", "test") + err := cli.Cmd("docker", "network", "create", "-f=dummy", mockNwName) if err == nil { t.Fatalf("Passing incorrect flags to the create command must fail") } - err = cli.Cmd("docker", "network", "create", "-d=dummy", "test") + err = cli.Cmd("docker", "network", "create", "-d=dummy", mockNwName) if err != nil { t.Fatalf(err.Error()) } @@ -74,12 +102,9 @@ func TestClientNetworkCreateWithDriver(t *testing.T) { func TestClientNetworkRm(t *testing.T) { var out, errOut bytes.Buffer - cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { - return nopCloser{bytes.NewBufferString("")}, 200, nil - } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) - err := cli.Cmd("docker", "network", "rm", "test") + err := cli.Cmd("docker", "network", "rm", mockNwName) if err != nil { t.Fatal(err.Error()) } @@ -87,35 +112,40 @@ func TestClientNetworkRm(t *testing.T) { func TestClientNetworkLs(t *testing.T) { var out, errOut bytes.Buffer - networks := "db,web,test" - cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { - return nopCloser{bytes.NewBufferString(networks)}, 200, nil - } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) err := cli.Cmd("docker", "network", "ls") if err != nil { t.Fatal(err.Error()) } - if out.String() != networks { - t.Fatal("Network List command fail to return the intended list") + if out.String() != string(mockNwListJSON) { + t.Fatal("Network List command fail to return the expected list") } } func TestClientNetworkInfo(t *testing.T) { var out, errOut bytes.Buffer - info := "dummy info" - cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { - return nopCloser{bytes.NewBufferString(info)}, 200, nil - } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) - err := cli.Cmd("docker", "network", "info", "test") + err := cli.Cmd("docker", "network", "info", mockNwName) if err != nil { t.Fatal(err.Error()) } - if out.String() != info { - t.Fatal("Network List command fail to return the intended list") + if out.String() != string(mockNwJSON) { + t.Fatal("Network info command fail to return the expected object") + } +} + +func TestClientNetworkInfoById(t *testing.T) { + var out, errOut bytes.Buffer + cli := NewNetworkCli(&out, &errOut, callbackFunc) + + err := cli.Cmd("docker", "network", "info", mockNwID) + if err != nil { + t.Fatal(err.Error()) + } + if out.String() != string(mockNwJSON) { + t.Fatal("Network info command fail to return the expected object") } } @@ -127,7 +157,7 @@ func TestClientNetworkCreateHelp(t *testing.T) { cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { return nil, 0, nil } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) err := cli.Cmd("docker", "network", "create", "--help") if err != nil { @@ -144,7 +174,7 @@ func TestClientNetworkCreateMissingArgument(t *testing.T) { cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { return nil, 0, nil } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) err := cli.Cmd("docker", "network", "create") if err != nil { diff --git a/libnetwork/client/network.go b/libnetwork/client/network.go index 3da71fe54e..97258277a5 100644 --- a/libnetwork/client/network.go +++ b/libnetwork/client/network.go @@ -2,8 +2,10 @@ package client import ( "bytes" + "encoding/json" "fmt" "io" + "net/http" flag "github.com/docker/docker/pkg/mflag" ) @@ -33,7 +35,7 @@ func (cli *NetworkCli) CmdNetwork(chain string, args ...string) error { err := cmd.ParseFlags(args, true) if err == nil { cmd.Usage() - return fmt.Errorf("Invalid command : %v", args) + return fmt.Errorf("invalid command : %v", args) } return err } @@ -55,7 +57,6 @@ func (cli *NetworkCli) CmdNetworkCreate(chain string, args ...string) error { obj, _, err := readBody(cli.call("POST", "/networks", nc, nil)) if err != nil { - fmt.Fprintf(cli.err, "%s", err.Error()) return err } if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { @@ -66,15 +67,18 @@ func (cli *NetworkCli) CmdNetworkCreate(chain string, args ...string) error { // CmdNetworkRm handles Network Delete UI func (cli *NetworkCli) CmdNetworkRm(chain string, args ...string) error { - cmd := cli.Subcmd(chain, "rm", "NETWORK-NAME", "Deletes a network", false) + cmd := cli.Subcmd(chain, "rm", "NETWORK", "Deletes a network", false) cmd.Require(flag.Min, 1) err := cmd.ParseFlags(args, true) if err != nil { return err } - obj, _, err := readBody(cli.call("DELETE", "/networks/name/"+cmd.Arg(0), nil, nil)) + id, err := lookupNetworkID(cli, cmd.Arg(0)) + if err != nil { + return err + } + obj, _, err := readBody(cli.call("DELETE", "/networks/"+id, nil, nil)) if err != nil { - fmt.Fprintf(cli.err, "%s", err.Error()) return err } if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { @@ -92,7 +96,6 @@ func (cli *NetworkCli) CmdNetworkLs(chain string, args ...string) error { } obj, _, err := readBody(cli.call("GET", "/networks", nil, nil)) if err != nil { - fmt.Fprintf(cli.err, "%s", err.Error()) return err } if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { @@ -103,15 +106,20 @@ func (cli *NetworkCli) CmdNetworkLs(chain string, args ...string) error { // CmdNetworkInfo handles Network Info UI func (cli *NetworkCli) CmdNetworkInfo(chain string, args ...string) error { - cmd := cli.Subcmd(chain, "info", "NETWORK-NAME", "Displays detailed information on a network", false) + cmd := cli.Subcmd(chain, "info", "NETWORK", "Displays detailed information on a network", false) cmd.Require(flag.Min, 1) err := cmd.ParseFlags(args, true) if err != nil { return err } - obj, _, err := readBody(cli.call("GET", "/networks/name/"+cmd.Arg(0), nil, nil)) + + id, err := lookupNetworkID(cli, cmd.Arg(0)) + if err != nil { + return err + } + + obj, _, err := readBody(cli.call("GET", "/networks/"+id, nil, nil)) if err != nil { - fmt.Fprintf(cli.err, "%s", err.Error()) return err } if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { @@ -120,6 +128,55 @@ func (cli *NetworkCli) CmdNetworkInfo(chain string, args ...string) error { return nil } +// Helper function to predict if a string is a name or id or partial-id +// This provides a best-effort mechanism to identify a id with the help of GET Filter APIs +// Being a UI, its most likely that name will be used by the user, which is used to lookup +// the corresponding ID. If ID is not found, this function will assume that the passed string +// is an ID by itself. + +func lookupNetworkID(cli *NetworkCli, nameID string) (string, error) { + obj, statusCode, err := readBody(cli.call("GET", "/networks?name="+nameID, 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)) + } + + var list []*networkResource + err = json.Unmarshal(obj, &list) + if err != nil { + return "", err + } + if len(list) > 0 { + // name query filter will always return a single-element collection + return list[0].ID, nil + } + + // Check for Partial-id + obj, statusCode, err = readBody(cli.call("GET", "/networks?partial-id="+nameID, 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)) + } + + err = json.Unmarshal(obj, &list) + if err != nil { + return "", err + } + if len(list) == 0 { + return "", fmt.Errorf("resource not found %s", nameID) + } + if len(list) > 1 { + return "", fmt.Errorf("multiple Networks matching the partial identifier (%s). Please use full identifier", nameID) + } + return list[0].ID, nil +} + func networkUsage(chain string) string { help := "Commands:\n" diff --git a/libnetwork/cmd/dnet/dnet.go b/libnetwork/cmd/dnet/dnet.go index c34c38c442..41448db168 100644 --- a/libnetwork/cmd/dnet/dnet.go +++ b/libnetwork/cmd/dnet/dnet.go @@ -158,7 +158,7 @@ func (d *dnetConnection) httpCall(method, path string, data interface{}, headers statusCode = resp.StatusCode } if err != nil { - return nil, statusCode, fmt.Errorf("An error occurred trying to connect: %v", err) + return nil, statusCode, fmt.Errorf("error when trying to connect: %v", err) } if statusCode < 200 || statusCode >= 400 { @@ -166,7 +166,7 @@ func (d *dnetConnection) httpCall(method, path string, data interface{}, headers if err != nil { return nil, statusCode, err } - return nil, statusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body)) + return nil, statusCode, fmt.Errorf("error : %s", bytes.TrimSpace(body)) } return resp.Body, statusCode, nil