Network UX and integration tests

* Exiting experimental network UX
* removed experimental service UX
* integrated with the new network remote API

Signed-off-by: Madhu Venugopal <madhu@docker.com>
This commit is contained in:
Madhu Venugopal 2015-09-27 04:09:27 -07:00
parent 2ab94e11a2
commit 22a9ba090e
10 changed files with 296 additions and 1027 deletions

View file

@ -1,14 +1,207 @@
// +build experimental
package client
import (
nwclient "github.com/docker/libnetwork/client"
"bytes"
"encoding/json"
"fmt"
"io"
"text/tabwriter"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/stringid"
)
// CmdNetwork is used to create, display and configure network endpoints.
// CmdNetwork is the parent subcommand for all network commands
//
// Usage: docker network <COMMAND> [OPTIONS]
func (cli *DockerCli) CmdNetwork(args ...string) error {
nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.callWrapper))
args = append([]string{"network"}, args...)
return nCli.Cmd("docker", args...)
cmd := Cli.Subcmd("network", []string{"COMMAND [OPTIONS]"}, networkUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdNetworkCreate creates a new network with a given name
//
// Usage: docker network create [OPTIONS] <NETWORK-NAME>
func (cli *DockerCli) CmdNetworkCreate(args ...string) error {
cmd := Cli.Subcmd("network create", []string{"NETWORK-NAME"}, "Creates a new network with a name specified by the user", false)
flDriver := cmd.String([]string{"d", "-driver"}, "", "Driver to manage the Network")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
// Construct network create request body
nc := types.NetworkCreate{Name: cmd.Arg(0), Driver: *flDriver, CheckDuplicate: true}
obj, _, err := readBody(cli.call("POST", "/networks/create", nc, nil))
if err != nil {
return err
}
var resp types.NetworkCreateResponse
err = json.Unmarshal(obj, &resp)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", resp.ID)
return nil
}
// CmdNetworkRm deletes a network
//
// Usage: docker network rm <NETWORK-NAME | NETWORK-ID>
func (cli *DockerCli) CmdNetworkRm(args ...string) error {
cmd := Cli.Subcmd("network rm", []string{"NETWORK"}, "Deletes a network", false)
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
_, _, err = readBody(cli.call("DELETE", "/networks/"+cmd.Arg(0), nil, nil))
if err != nil {
return err
}
return nil
}
// CmdNetworkConnect connects a container to a network
//
// Usage: docker network connect <NETWORK> <CONTAINER>
func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
cmd.Require(flag.Exact, 2)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
nc := types.NetworkConnect{Container: cmd.Arg(1)}
_, _, err = readBody(cli.call("POST", "/networks/"+cmd.Arg(0)+"/connect", nc, nil))
return err
}
// CmdNetworkDisconnect disconnects a container from a network
//
// Usage: docker network disconnect <NETWORK> <CONTAINER>
func (cli *DockerCli) CmdNetworkDisconnect(args ...string) error {
cmd := Cli.Subcmd("network disconnect", []string{"NETWORK CONTAINER"}, "Disconnects container from a network", false)
cmd.Require(flag.Exact, 2)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
nc := types.NetworkConnect{Container: cmd.Arg(1)}
_, _, err = readBody(cli.call("POST", "/networks/"+cmd.Arg(0)+"/disconnect", nc, nil))
return err
}
// CmdNetworkLs lists all the netorks managed by docker daemon
//
// Usage: docker network ls [OPTIONS]
func (cli *DockerCli) CmdNetworkLs(args ...string) error {
cmd := Cli.Subcmd("network ls", []string{""}, "Lists all the networks created by the user", false)
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
noTrunc := cmd.Bool([]string{"", "-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
}
obj, _, err := readBody(cli.call("GET", "/networks", nil, nil))
if err != nil {
return err
}
if *last == -1 && *nLatest {
*last = 1
}
var networkResources []types.NetworkResource
err = json.Unmarshal(obj, &networkResources)
if err != nil {
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 ID\tNAME\tDRIVER")
}
for _, networkResource := range networkResources {
ID := networkResource.ID
netName := networkResource.Name
if !*noTrunc {
ID = stringid.TruncateID(ID)
}
if *quiet {
fmt.Fprintln(wr, ID)
continue
}
driver := networkResource.Driver
fmt.Fprintf(wr, "%s\t%s\t%s\t",
ID,
netName,
driver)
fmt.Fprint(wr, "\n")
}
wr.Flush()
return nil
}
// CmdNetworkInspect inspects the network object for more details
//
// Usage: docker network inspect <NETWORK>
// CmdNetworkInspect handles Network inspect UI
func (cli *DockerCli) CmdNetworkInspect(args ...string) error {
cmd := Cli.Subcmd("network inspect", []string{"NETWORK"}, "Displays detailed information on a network", false)
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
obj, _, err := readBody(cli.call("GET", "/networks/"+cmd.Arg(0), nil, nil))
if err != nil {
return err
}
networkResource := &types.NetworkResource{}
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
return err
}
indented := new(bytes.Buffer)
if err := json.Indent(indented, obj, "", " "); err != nil {
return err
}
if _, err := io.Copy(cli.out, indented); err != nil {
return err
}
return nil
}
func networkUsage() string {
networkCommands := map[string]string{
"create": "Create a network",
"connect": "Connect container to a network",
"disconnect": "Disconnect container from a network",
"inspect": "Display detailed network information",
"ls": "List all networks",
"rm": "Remove a network",
}
help := "Commands:\n"
for cmd, description := range networkCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd, description)
}
help += fmt.Sprintf("\nRun 'docker network COMMAND --help' for more information on a command.")
return help
}

View file

@ -1,17 +0,0 @@
// +build experimental
package client
import (
"os"
nwclient "github.com/docker/libnetwork/client"
)
// CmdService is used to manage network services.
// service command is user to publish, attach and list a service from a container.
func (cli *DockerCli) CmdService(args ...string) error {
nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.callWrapper))
args = append([]string{"service"}, args...)
return nCli.Cmd(os.Args[0], args...)
}

View file

@ -1,113 +0,0 @@
// +build experimental
package main
import (
"encoding/json"
"fmt"
"net/http"
"github.com/go-check/check"
)
func isServiceAvailable(c *check.C, name string, network string) bool {
status, body, err := sockRequest("GET", "/services", nil)
c.Assert(status, check.Equals, http.StatusOK)
c.Assert(err, check.IsNil)
var inspectJSON []struct {
Name string
ID string
Network string
}
if err = json.Unmarshal(body, &inspectJSON); err != nil {
c.Fatalf("unable to unmarshal response body: %v", err)
}
for _, s := range inspectJSON {
if s.Name == name && s.Network == network {
return true
}
}
return false
}
func isServiceNetworkAvailable(c *check.C, name string) bool {
status, body, err := sockRequest("GET", "/networks", nil)
c.Assert(status, check.Equals, http.StatusOK)
c.Assert(err, check.IsNil)
var inspectJSON []struct {
Name string
ID string
Type string
}
if err = json.Unmarshal(body, &inspectJSON); err != nil {
c.Fatalf("unable to unmarshal response body: %v", err)
}
for _, n := range inspectJSON {
if n.Name == name {
return true
}
}
return false
}
func (s *DockerSuite) TestServiceApiCreateDelete(c *check.C) {
name := "testnetwork"
config := map[string]interface{}{
"name": name,
"network_type": "bridge",
}
status, resp, err := sockRequest("POST", "/networks", config)
c.Assert(status, check.Equals, http.StatusCreated)
c.Assert(err, check.IsNil)
if !isServiceNetworkAvailable(c, name) {
c.Fatalf("Network %s not found", name)
}
var nid string
err = json.Unmarshal(resp, &nid)
if err != nil {
c.Fatal(err)
}
sname := "service1"
sconfig := map[string]interface{}{
"name": sname,
"network_name": name,
}
status, resp, err = sockRequest("POST", "/services", sconfig)
c.Assert(status, check.Equals, http.StatusCreated)
c.Assert(err, check.IsNil)
if !isServiceAvailable(c, sname, name) {
c.Fatalf("Service %s.%s not found", sname, name)
}
var id string
err = json.Unmarshal(resp, &id)
if err != nil {
c.Fatal(err)
}
status, _, err = sockRequest("DELETE", fmt.Sprintf("/services/%s", id), nil)
c.Assert(status, check.Equals, http.StatusOK)
c.Assert(err, check.IsNil)
if isServiceAvailable(c, sname, name) {
c.Fatalf("Service %s.%s not deleted", sname, name)
}
status, _, err = sockRequest("DELETE", fmt.Sprintf("/networks/%s", nid), nil)
c.Assert(status, check.Equals, http.StatusOK)
c.Assert(err, check.IsNil)
if isNetworkAvailable(c, name) {
c.Fatalf("Network %s not deleted", name)
}
}

View file

@ -1,10 +1,11 @@
// +build experimental
package main
import (
"encoding/json"
"net"
"strings"
"github.com/docker/docker/api/types"
"github.com/go-check/check"
)
@ -31,6 +32,14 @@ func isNwPresent(c *check.C, name string) bool {
return false
}
func getNwResource(c *check.C, name string) *types.NetworkResource {
out, _ := dockerCmd(c, "network", "inspect", name)
nr := types.NetworkResource{}
err := json.Unmarshal([]byte(out), &nr)
c.Assert(err, check.IsNil)
return &nr
}
func (s *DockerSuite) TestDockerNetworkLsDefault(c *check.C) {
defaults := []string{"bridge", "host", "none"}
for _, nn := range defaults {
@ -45,3 +54,45 @@ func (s *DockerSuite) TestDockerNetworkCreateDelete(c *check.C) {
dockerCmd(c, "network", "rm", "test")
assertNwNotAvailable(c, "test")
}
func (s *DockerSuite) TestDockerNetworkConnectDisconnect(c *check.C) {
dockerCmd(c, "network", "create", "test")
assertNwIsAvailable(c, "test")
nr := getNwResource(c, "test")
c.Assert(nr.Name, check.Equals, "test")
c.Assert(len(nr.Containers), check.Equals, 0)
// run a container
out, _ := dockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
c.Assert(waitRun("test"), check.IsNil)
containerID := strings.TrimSpace(out)
// connect the container to the test network
dockerCmd(c, "network", "connect", "test", containerID)
// inspect the network to make sure container is connected
nr = getNetworkResource(c, nr.ID)
c.Assert(len(nr.Containers), check.Equals, 1)
c.Assert(nr.Containers[containerID], check.NotNil)
// check if container IP matches network inspect
ip, _, err := net.ParseCIDR(nr.Containers[containerID].IPv4Address)
c.Assert(err, check.IsNil)
containerIP := findContainerIP(c, "test")
c.Assert(ip.String(), check.Equals, containerIP)
// disconnect container from the network
dockerCmd(c, "network", "disconnect", "test", containerID)
nr = getNwResource(c, "test")
c.Assert(nr.Name, check.Equals, "test")
c.Assert(len(nr.Containers), check.Equals, 0)
// check if network connect fails for inactive containers
dockerCmd(c, "stop", containerID)
_, _, err = dockerCmdWithError("network", "connect", "test", containerID)
c.Assert(err, check.NotNil)
dockerCmd(c, "network", "rm", "test")
assertNwNotAvailable(c, "test")
}

View file

@ -18,7 +18,9 @@ import (
"sync"
"time"
"github.com/docker/docker/pkg/integration/checker"
"github.com/docker/docker/pkg/nat"
"github.com/docker/docker/runconfig"
"github.com/docker/libnetwork/resolvconf"
"github.com/go-check/check"
)
@ -3408,6 +3410,7 @@ func (s *DockerSuite) TestContainersInUserDefinedNetwork(c *check.C) {
testRequires(c, DaemonIsLinux)
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork")
dockerCmd(c, "run", "-d", "--net=testnetwork", "--name=first", "busybox", "top")
c.Assert(waitRun("first"), check.IsNil)
dockerCmd(c, "run", "-t", "--net=testnetwork", "--name=second", "busybox", "ping", "-c", "1", "first")
dockerCmd(c, "stop", "first")
dockerCmd(c, "stop", "second")
@ -3421,7 +3424,9 @@ func (s *DockerSuite) TestContainersInMultipleNetworks(c *check.C) {
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork2")
// Run and connect containers to testnetwork1
dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=first", "busybox", "top")
c.Assert(waitRun("first"), check.IsNil)
dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=second", "busybox", "top")
c.Assert(waitRun("second"), check.IsNil)
// Check connectivity between containers in testnetwork2
dockerCmd(c, "exec", "first", "ping", "-c", "1", "second.testnetwork1")
// Connect containers to testnetwork2
@ -3440,9 +3445,11 @@ func (s *DockerSuite) TestContainersNetworkIsolation(c *check.C) {
// Create 2 networks using bridge driver
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork1")
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork2")
// Run 1 containers in testnetwork1 and another in testnetwork2
// Run 1 container in testnetwork1 and another in testnetwork2
dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=first", "busybox", "top")
c.Assert(waitRun("first"), check.IsNil)
dockerCmd(c, "run", "-d", "--net=testnetwork2", "--name=second", "busybox", "top")
c.Assert(waitRun("second"), check.IsNil)
// Check Isolation between containers : ping must fail
_, _, err := dockerCmdWithError("exec", "first", "ping", "-c", "1", "second")
@ -3471,7 +3478,9 @@ func (s *DockerSuite) TestNetworkRmWithActiveContainers(c *check.C) {
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork1")
// Run and connect containers to testnetwork1
dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=first", "busybox", "top")
c.Assert(waitRun("first"), check.IsNil)
dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=second", "busybox", "top")
c.Assert(waitRun("second"), check.IsNil)
// Network delete with active containers must fail
_, _, err := dockerCmdWithError("network", "rm", "testnetwork1")
c.Assert(err, check.NotNil)
@ -3492,7 +3501,9 @@ func (s *DockerSuite) TestContainerRestartInMultipleNetworks(c *check.C) {
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork2")
// Run and connect containers to testnetwork1
dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=first", "busybox", "top")
c.Assert(waitRun("first"), check.IsNil)
dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=second", "busybox", "top")
c.Assert(waitRun("second"), check.IsNil)
// Check connectivity between containers in testnetwork2
dockerCmd(c, "exec", "first", "ping", "-c", "1", "second.testnetwork1")
// Connect containers to testnetwork2
@ -3523,6 +3534,7 @@ func (s *DockerSuite) TestContainerWithConflictingHostNetworks(c *check.C) {
testRequires(c, DaemonIsLinux)
// Run a container with --net=host
dockerCmd(c, "run", "-d", "--net=host", "--name=first", "busybox", "top")
c.Assert(waitRun("first"), check.IsNil)
// Create a network using bridge driver
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork1")
@ -3537,14 +3549,43 @@ func (s *DockerSuite) TestContainerWithConflictingHostNetworks(c *check.C) {
func (s *DockerSuite) TestContainerWithConflictingSharedNetwork(c *check.C) {
testRequires(c, DaemonIsLinux)
dockerCmd(c, "run", "-d", "--name=first", "busybox", "top")
c.Assert(waitRun("first"), check.IsNil)
// Run second container in first container's network namespace
dockerCmd(c, "run", "-d", "--net=container:first", "--name=second", "busybox", "top")
c.Assert(waitRun("second"), check.IsNil)
// Create a network using bridge driver
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork1")
// Connecting to the user defined network must fail
_, _, err := dockerCmdWithError("network", "connect", "testnetwork1", "second")
out, _, err := dockerCmdWithError("network", "connect", "testnetwork1", "second")
c.Assert(err, check.NotNil)
c.Assert(out, checker.Contains, runconfig.ErrConflictSharedNetwork.Error())
dockerCmd(c, "stop", "first")
dockerCmd(c, "stop", "second")
dockerCmd(c, "network", "rm", "testnetwork1")
}
func (s *DockerSuite) TestContainerWithConflictingNoneNetwork(c *check.C) {
testRequires(c, DaemonIsLinux)
dockerCmd(c, "run", "-d", "--net=none", "--name=first", "busybox", "top")
c.Assert(waitRun("first"), check.IsNil)
// Create a network using bridge driver
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork1")
// Connecting to the user defined network must fail
out, _, err := dockerCmdWithError("network", "connect", "testnetwork1", "first")
c.Assert(err, check.NotNil)
c.Assert(out, checker.Contains, runconfig.ErrConflictNoNetwork.Error())
// create a container connected to testnetwork1
dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=second", "busybox", "top")
c.Assert(waitRun("second"), check.IsNil)
// Connect second container to none network. it must fail as well
_, _, err = dockerCmdWithError("network", "connect", "none", "second")
c.Assert(err, check.NotNil)
dockerCmd(c, "stop", "first")

View file

@ -1,69 +0,0 @@
// +build experimental
package main
import (
"fmt"
"strings"
"github.com/go-check/check"
)
func assertSrvIsAvailable(c *check.C, sname, name string) {
if !isSrvPresent(c, sname, name) {
c.Fatalf("Service %s on network %s not found in service ls o/p", sname, name)
}
}
func assertSrvNotAvailable(c *check.C, sname, name string) {
if isSrvPresent(c, sname, name) {
c.Fatalf("Found service %s on network %s in service ls o/p", sname, name)
}
}
func isSrvPresent(c *check.C, sname, name string) bool {
out, _, _ := dockerCmdWithStdoutStderr(c, "service", "ls")
lines := strings.Split(out, "\n")
for i := 1; i < len(lines)-1; i++ {
if strings.Contains(lines[i], sname) && strings.Contains(lines[i], name) {
return true
}
}
return false
}
func isCntPresent(c *check.C, cname, sname, name string) bool {
out, _, _ := dockerCmdWithStdoutStderr(c, "service", "ls", "--no-trunc")
lines := strings.Split(out, "\n")
for i := 1; i < len(lines)-1; i++ {
fmt.Println(lines)
if strings.Contains(lines[i], name) && strings.Contains(lines[i], sname) && strings.Contains(lines[i], cname) {
return true
}
}
return false
}
func (s *DockerSuite) TestDockerServiceCreateDelete(c *check.C) {
dockerCmdWithStdoutStderr(c, "network", "create", "test")
assertNwIsAvailable(c, "test")
dockerCmdWithStdoutStderr(c, "service", "publish", "s1.test")
assertSrvIsAvailable(c, "s1", "test")
dockerCmdWithStdoutStderr(c, "service", "unpublish", "s1.test")
assertSrvNotAvailable(c, "s1", "test")
dockerCmdWithStdoutStderr(c, "network", "rm", "test")
assertNwNotAvailable(c, "test")
}
func (s *DockerSuite) TestDockerPublishServiceFlag(c *check.C) {
// Run saying the container is the backend for the specified service on the specified network
out, _ := dockerCmd(c, "run", "-d", "--expose=23", "--publish-service", "telnet.production", "busybox", "top")
cid := strings.TrimSpace(out)
// Verify container is attached in service ps o/p
assertSrvIsAvailable(c, "telnet", "production")
dockerCmd(c, "rm", "-f", cid)
}

View file

@ -1,115 +0,0 @@
package client
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"reflect"
"strings"
flag "github.com/docker/docker/pkg/mflag"
)
// CallFunc provides environment specific call utility to invoke backend functions from UI
type CallFunc func(string, string, interface{}, map[string][]string) (io.ReadCloser, http.Header, int, error)
// NetworkCli is the UI object for network subcmds
type NetworkCli struct {
out io.Writer
err io.Writer
call CallFunc
}
// NewNetworkCli is a convenient function to create a NetworkCli object
func NewNetworkCli(out, err io.Writer, call CallFunc) *NetworkCli {
return &NetworkCli{
out: out,
err: err,
call: call,
}
}
// getMethod is Borrowed from Docker UI which uses reflection to identify the UI Handler
func (cli *NetworkCli) getMethod(args ...string) (func(string, ...string) error, bool) {
camelArgs := make([]string, len(args))
for i, s := range args {
if len(s) == 0 {
return nil, false
}
camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
}
methodName := "Cmd" + strings.Join(camelArgs, "")
method := reflect.ValueOf(cli).MethodByName(methodName)
if !method.IsValid() {
return nil, false
}
return method.Interface().(func(string, ...string) error), true
}
// Cmd is borrowed from Docker UI and acts as the entry point for network UI commands.
// network UI commands are designed to be invoked from multiple parent chains
func (cli *NetworkCli) Cmd(chain string, args ...string) error {
if len(args) > 2 {
method, exists := cli.getMethod(args[:3]...)
if exists {
return method(chain+" "+args[0]+" "+args[1], args[3:]...)
}
}
if len(args) > 1 {
method, exists := cli.getMethod(args[:2]...)
if exists {
return method(chain+" "+args[0], args[2:]...)
}
}
if len(args) > 0 {
method, exists := cli.getMethod(args[0])
if !exists {
return fmt.Errorf("%s: '%s' is not a %s command. See '%s --help'.\n", chain, args[0], chain, chain)
}
return method(chain, args[1:]...)
}
flag.Usage()
return nil
}
// Subcmd is borrowed from Docker UI and performs the same function of configuring the subCmds
func (cli *NetworkCli) Subcmd(chain, name, signature, description string, exitOnError bool) *flag.FlagSet {
var errorHandling flag.ErrorHandling
if exitOnError {
errorHandling = flag.ExitOnError
} else {
errorHandling = flag.ContinueOnError
}
flags := flag.NewFlagSet(name, errorHandling)
flags.Usage = func() {
flags.ShortUsage()
flags.PrintDefaults()
}
flags.ShortUsage = func() {
options := ""
if signature != "" {
signature = " " + signature
}
if flags.FlagCountUndeprecated() > 0 {
options = " [OPTIONS]"
}
fmt.Fprintf(cli.out, "\nUsage: %s %s%s%s\n\n%s\n\n", chain, name, options, signature, description)
flags.SetOutput(cli.out)
}
return flags
}
func readBody(stream io.ReadCloser, hdr http.Header, statusCode int, err error) ([]byte, int, error) {
if stream != nil {
defer stream.Close()
}
if err != nil {
return nil, statusCode, err
}
body, err := ioutil.ReadAll(stream)
if err != nil {
return nil, -1, err
}
return body, statusCode, nil
}

View file

@ -1,231 +0,0 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"text/tabwriter"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/stringid"
)
type command struct {
name string
description string
}
var (
networkCommands = []command{
{"create", "Create a network"},
{"rm", "Remove a network"},
{"ls", "List all networks"},
{"info", "Display information of a network"},
}
)
// CmdNetwork handles the root Network UI
func (cli *NetworkCli) CmdNetwork(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "network", "COMMAND [OPTIONS] [arg...]", networkUsage(chain), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
if err == nil {
cmd.Usage()
return fmt.Errorf("invalid command : %v", args)
}
return err
}
// CmdNetworkCreate handles Network Create UI
func (cli *NetworkCli) CmdNetworkCreate(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "create", "NETWORK-NAME", "Creates a new network with a name specified by the user", false)
flDriver := cmd.String([]string{"d", "-driver"}, "", "Driver to manage the Network")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
// Construct network create request body
ops := make(map[string]interface{})
nc := networkCreate{Name: cmd.Arg(0), NetworkType: *flDriver, Options: ops}
obj, _, err := readBody(cli.call("POST", "/networks", nc, nil))
if err != nil {
return err
}
var replyID string
err = json.Unmarshal(obj, &replyID)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", replyID)
return nil
}
// CmdNetworkRm handles Network Delete UI
func (cli *NetworkCli) CmdNetworkRm(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "rm", "NETWORK", "Deletes a network", false)
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
id, err := lookupNetworkID(cli, cmd.Arg(0))
if err != nil {
return err
}
_, _, err = readBody(cli.call("DELETE", "/networks/"+id, nil, nil))
if err != nil {
return err
}
return nil
}
// CmdNetworkLs handles Network List UI
func (cli *NetworkCli) CmdNetworkLs(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "ls", "", "Lists all the networks created by the user", false)
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
}
obj, _, err := readBody(cli.call("GET", "/networks", nil, nil))
if err != nil {
return err
}
if *last == -1 && *nLatest {
*last = 1
}
var networkResources []networkResource
err = json.Unmarshal(obj, &networkResources)
if err != nil {
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 ID\tNAME\tTYPE")
}
for _, networkResource := range networkResources {
ID := networkResource.ID
netName := networkResource.Name
if !*noTrunc {
ID = stringid.TruncateID(ID)
}
if *quiet {
fmt.Fprintln(wr, ID)
continue
}
netType := networkResource.Type
fmt.Fprintf(wr, "%s\t%s\t%s\t",
ID,
netName,
netType)
fmt.Fprint(wr, "\n")
}
wr.Flush()
return nil
}
// CmdNetworkInfo handles Network Info UI
func (cli *NetworkCli) CmdNetworkInfo(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "info", "NETWORK", "Displays detailed information on a network", false)
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
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 {
return err
}
networkResource := &networkResource{}
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
return err
}
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.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)
}
}
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"
for _, cmd := range networkCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd.name, cmd.description)
}
help += fmt.Sprintf("\nRun '%s network COMMAND --help' for more information on a command.", chain)
return help
}

View file

@ -1,392 +0,0 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"text/tabwriter"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/stringid"
)
var (
serviceCommands = []command{
{"publish", "Publish a service"},
{"unpublish", "Remove a service"},
{"attach", "Attach a backend (container) to the service"},
{"detach", "Detach the backend from the service"},
{"ls", "Lists all services"},
{"info", "Display information about a service"},
}
)
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)
}
if nwName == "" {
obj, _, err := readBody(cli.call("GET", "/networks/"+nwList[0].ID, nil, nil))
if err != nil {
return "", err
}
networkResource := &networkResource{}
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
return "", err
}
nwName = networkResource.Name
}
// 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: (%d) %s", svNameID, statusCode, string(obj))
}
var list []*serviceResource
if err = json.Unmarshal(obj, &list); err != nil {
return "", err
}
for _, sr := range list {
if sr.Network == nwName {
return sr.ID, 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: (%d) %s", svNameID, statusCode, string(obj))
}
if err = json.Unmarshal(obj, &list); err != nil {
return "", err
}
for _, sr := range list {
if sr.Network == nwName {
return sr.ID, nil
}
}
return "", fmt.Errorf("Service %s not found on network %s", svNameID, nwName)
}
func lookupContainerID(cli *NetworkCli, cnNameID string) (string, error) {
// Container is a Docker resource, ask docker about it.
// In case of connecton error, we assume we are running in dnet and return whatever was passed to us
obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/containers/%s/json", cnNameID), nil, nil))
if err != nil {
// We are probably running outside of docker
return cnNameID, nil
}
var x map[string]interface{}
err = json.Unmarshal(obj, &x)
if err != nil {
return "", err
}
if iid, ok := x["Id"]; ok {
if id, ok := iid.(string); ok {
return id, nil
}
return "", fmt.Errorf("Unexpected data type for container ID in json response")
}
return "", fmt.Errorf("Cannot find container ID in json response")
}
func lookupSandboxID(cli *NetworkCli, containerID string) (string, error) {
obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/sandboxes?partial-container-id=%s", containerID), nil, nil))
if err != nil {
return "", err
}
var sandboxList []SandboxResource
err = json.Unmarshal(obj, &sandboxList)
if err != nil {
return "", err
}
if len(sandboxList) == 0 {
return "", fmt.Errorf("cannot find sandbox for container: %s", containerID)
}
return sandboxList[0].ID, nil
}
// 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)
err := cmd.ParseFlags(args, true)
if err == nil {
cmd.Usage()
return fmt.Errorf("Invalid command : %v", args)
}
return err
}
// Parse service name for "SERVICE[.NETWORK]" format
func parseServiceName(name string) (string, string) {
s := strings.Split(name, ".")
var sName, nName string
if len(s) > 1 {
nName = s[len(s)-1]
sName = strings.Join(s[:len(s)-1], ".")
} else {
sName = s[0]
}
return sName, nName
}
// CmdServicePublish handles service create UI
func (cli *NetworkCli) CmdServicePublish(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "publish", "SERVICE[.NETWORK]", "Publish a new service on a network", false)
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
sn, nn := parseServiceName(cmd.Arg(0))
sc := serviceCreate{Name: sn, Network: nn}
obj, _, err := readBody(cli.call("POST", "/services", sc, nil))
if err != nil {
return err
}
var replyID string
err = json.Unmarshal(obj, &replyID)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", replyID)
return nil
}
// CmdServiceUnpublish handles service delete UI
func (cli *NetworkCli) CmdServiceUnpublish(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "unpublish", "SERVICE[.NETWORK]", "Removes a service", false)
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
sn, nn := parseServiceName(cmd.Arg(0))
serviceID, err := lookupServiceID(cli, nn, sn)
if err != nil {
return err
}
_, _, 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", "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")
err := cmd.ParseFlags(args, true)
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))
}
if err != nil {
return err
}
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, "SERVICE ID\tNAME\tNETWORK\tCONTAINER\tSANDBOX")
}
for _, sr := range serviceResources {
ID := sr.ID
bkID, sbID, err := getBackendID(cli, ID)
if err != nil {
return err
}
if !*noTrunc {
ID = stringid.TruncateID(ID)
bkID = stringid.TruncateID(bkID)
sbID = stringid.TruncateID(sbID)
}
if !*quiet {
fmt.Fprintf(wr, "%s\t%s\t%s\t%s\t%s\n", ID, sr.Name, sr.Network, bkID, sbID)
} else {
fmt.Fprintln(wr, ID)
}
}
wr.Flush()
return nil
}
func getBackendID(cli *NetworkCli, servID string) (string, string, error) {
var (
obj []byte
err error
bk string
sb string
)
if obj, _, err = readBody(cli.call("GET", "/services/"+servID+"/backend", nil, nil)); err == nil {
var sr SandboxResource
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&sr); err == nil {
bk = sr.ContainerID
sb = sr.ID
} else {
// Only print a message, don't make the caller cli fail for this
fmt.Fprintf(cli.out, "Failed to retrieve backend list for service %s (%v)\n", servID, err)
}
}
return bk, sb, 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 about a service", false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
sn, nn := parseServiceName(cmd.Arg(0))
serviceID, err := lookupServiceID(cli, nn, sn)
if err != nil {
return err
}
obj, _, err := readBody(cli.call("GET", "/services/"+serviceID, nil, nil))
if err != nil {
return err
}
sr := &serviceResource{}
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(sr); err != nil {
return err
}
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
}
// CmdServiceAttach handles service attach UI
func (cli *NetworkCli) CmdServiceAttach(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "attach", "CONTAINER SERVICE[.NETWORK]", "Sets a container as a service backend", false)
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
}
sandboxID, err := lookupSandboxID(cli, containerID)
if err != nil {
return err
}
sn, nn := parseServiceName(cmd.Arg(1))
serviceID, err := lookupServiceID(cli, nn, sn)
if err != nil {
return err
}
nc := serviceAttach{SandboxID: sandboxID}
_, _, err = readBody(cli.call("POST", "/services/"+serviceID+"/backend", nc, nil))
return err
}
// 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)
cmd.Require(flag.Min, 2)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
sn, nn := parseServiceName(cmd.Arg(1))
containerID, err := lookupContainerID(cli, cmd.Arg(0))
if err != nil {
return err
}
sandboxID, err := lookupSandboxID(cli, containerID)
if err != nil {
return err
}
serviceID, err := lookupServiceID(cli, nn, sn)
if err != nil {
return err
}
_, _, err = readBody(cli.call("DELETE", "/services/"+serviceID+"/backend/"+sandboxID, nil, nil))
if err != nil {
return err
}
return nil
}
func serviceUsage(chain string) string {
help := "Commands:\n"
for _, cmd := range serviceCommands {
help += fmt.Sprintf(" %-10.10s%s\n", cmd.name, cmd.description)
}
help += fmt.Sprintf("\nRun '%s service COMMAND --help' for more information on a command.", chain)
return help
}

View file

@ -1,79 +0,0 @@
package client
import "github.com/docker/libnetwork/types"
/***********
Resources
************/
// 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"`
Services []*serviceResource `json:"services"`
}
// 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"`
}
// SandboxResource is the body of "get service backend" response message
type SandboxResource struct {
ID string `json:"id"`
Key string `json:"key"`
ContainerID string `json:"container_id"`
}
/***********
Body types
************/
// networkCreate is the expected body of the "create network" http request message
type networkCreate struct {
Name string `json:"name"`
NetworkType string `json:"network_type"`
Options map[string]interface{} `json:"options"`
}
// serviceCreate represents the body of the "publish service" http request message
type serviceCreate struct {
Name string `json:"name"`
Network string `json:"network_name"`
ExposedPorts []types.TransportPort `json:"exposed_ports"`
PortMapping []types.PortBinding `json:"port_mapping"`
}
// serviceAttach represents the expected body of the "attach/detach sandbox to/from service" http request messages
type serviceAttach struct {
SandboxID string `json:"sandbox_id"`
}
// SandboxCreate is the body of the "post /sandboxes" http request message
type SandboxCreate 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 []extraHost `json:"extra_hosts"`
UseDefaultSandbox bool `json:"use_default_sandbox"`
}
// extraHost represents the extra host object
type extraHost struct {
Name string `json:"name"`
Address string `json:"address"`
}
// sandboxParentUpdate is the object carrying the information about the
// sanbox parent that needs to be updated
type sandboxParentUpdate struct {
ContainerID string `json:"container_id"`
Name string `json:"name"`
Address string `json:"address"`
}