diff --git a/libnetwork/client/client_test.go b/libnetwork/client/client_test.go index d7ad8a69f4..20203375e1 100644 --- a/libnetwork/client/client_test.go +++ b/libnetwork/client/client_test.go @@ -47,8 +47,8 @@ func setupMockHTTPCallback() { srvList = append(srvList, ep) mockServiceListJSON, _ = json.Marshal(srvList) - var sbxList []sandboxResource - sb := sandboxResource{ID: mockSandboxID, ContainerID: mockContainerID} + var sbxList []SandboxResource + sb := SandboxResource{ID: mockSandboxID, ContainerID: mockContainerID} mockSbJSON, _ = json.Marshal(sb) sbxList = append(sbxList, sb) mockSbListJSON, _ = json.Marshal(sbxList) diff --git a/libnetwork/client/service.go b/libnetwork/client/service.go index 36564977fb..6b37db4337 100644 --- a/libnetwork/client/service.go +++ b/libnetwork/client/service.go @@ -120,7 +120,7 @@ func lookupSandboxID(cli *NetworkCli, containerID string) (string, error) { return "", err } - var sandboxList []sandboxResource + var sandboxList []SandboxResource err = json.Unmarshal(obj, &sandboxList) if err != nil { return "", err @@ -268,7 +268,7 @@ func getBackendID(cli *NetworkCli, servID string) (string, error) { ) if obj, _, err = readBody(cli.call("GET", "/services/"+servID+"/backend", nil, nil)); err == nil { - var sr sandboxResource + var sr SandboxResource if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&sr); err == nil { bk = sr.ContainerID } else { diff --git a/libnetwork/client/types.go b/libnetwork/client/types.go index f215a4efed..1848884509 100644 --- a/libnetwork/client/types.go +++ b/libnetwork/client/types.go @@ -21,8 +21,8 @@ type serviceResource struct { Network string `json:"network"` } -// sandboxResource is the body of "get service backend" response message -type sandboxResource struct { +// 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"` @@ -52,7 +52,8 @@ type serviceAttach struct { SandboxID string `json:"sandbox_id"` } -type sandboxCreate struct { +// 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"` diff --git a/libnetwork/cmd/dnet/cmd.go b/libnetwork/cmd/dnet/cmd.go new file mode 100644 index 0000000000..5c30cb81e6 --- /dev/null +++ b/libnetwork/cmd/dnet/cmd.go @@ -0,0 +1,146 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + + "github.com/codegangsta/cli" + "github.com/docker/docker/pkg/term" + "github.com/docker/libnetwork/client" +) + +var ( + containerCreateCommand = cli.Command{ + Name: "create", + Usage: "Create a container", + Action: runContainerCreate, + } + + containerRmCommand = cli.Command{ + Name: "rm", + Usage: "Remove a container", + Action: runContainerRm, + } + + containerCommands = []cli.Command{ + containerCreateCommand, + containerRmCommand, + } + + dnetCommands = []cli.Command{ + createDockerCommand("network"), + createDockerCommand("service"), + { + Name: "container", + Usage: "Container management commands", + Subcommands: containerCommands, + }, + } +) + +func runContainerCreate(c *cli.Context) { + if len(c.Args()) == 0 { + fmt.Printf("Please provide container id argument\n") + os.Exit(1) + } + + sc := client.SandboxCreate{ContainerID: c.Args()[0]} + obj, _, err := readBody(epConn.httpCall("POST", "/sandboxes", sc, nil)) + if err != nil { + fmt.Printf("POST failed during create container: %v\n", err) + os.Exit(1) + } + + var replyID string + err = json.Unmarshal(obj, &replyID) + if err != nil { + fmt.Printf("Unmarshall of response failed during create container: %v\n", err) + os.Exit(1) + } + + fmt.Printf("%s\n", replyID) + +} + +func runContainerRm(c *cli.Context) { + var sbList []*client.SandboxResource + + if len(c.Args()) == 0 { + fmt.Printf("Please provide container id argument\n") + os.Exit(1) + } + + obj, _, err := readBody(epConn.httpCall("GET", "/sandboxes?partial-container-id="+c.Args()[0], nil, nil)) + if err != nil { + fmt.Printf("GET failed during container id lookup: %v\n", err) + os.Exit(1) + } + + err = json.Unmarshal(obj, &sbList) + if err != nil { + fmt.Printf("Unmarshall of container id lookup response failed: %v", err) + os.Exit(1) + } + + if len(sbList) == 0 { + fmt.Printf("No sandbox for container %s found\n", c.Args()[0]) + os.Exit(1) + } + + _, _, err = readBody(epConn.httpCall("DELETE", "/sandboxes/"+sbList[0].ID, nil, nil)) + if err != nil { + fmt.Printf("DELETE of sandbox id %s failed: %v", sbList[0].ID, err) + os.Exit(1) + } +} + +func runDockerCommand(c *cli.Context, cmd string) { + _, stdout, stderr := term.StdStreams() + oldcli := client.NewNetworkCli(stdout, stderr, epConn.httpCall) + var args []string + args = append(args, cmd) + if c.Bool("h") { + args = append(args, "--help") + } else { + args = append(args, c.Args()...) + } + if err := oldcli.Cmd("dnet", args...); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func createDockerCommand(cmd string) cli.Command { + return cli.Command{ + Name: cmd, + Usage: fmt.Sprintf("%s management commands", cmd), + SkipFlagParsing: true, + Action: func(c *cli.Context) { + runDockerCommand(c, cmd) + }, + Subcommands: []cli.Command{ + { + Name: "h, -help", + Usage: fmt.Sprintf("%s help", cmd), + }, + }, + } +} + +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 +} diff --git a/libnetwork/cmd/dnet/dnet.go b/libnetwork/cmd/dnet/dnet.go index 33b5235d5d..2771e80561 100644 --- a/libnetwork/cmd/dnet/dnet.go +++ b/libnetwork/cmd/dnet/dnet.go @@ -10,7 +10,7 @@ import ( "os" "strings" - flag "github.com/docker/docker/pkg/mflag" + "github.com/codegangsta/cli" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/reexec" @@ -18,7 +18,6 @@ import ( "github.com/docker/docker/pkg/term" "github.com/docker/libnetwork" "github.com/docker/libnetwork/api" - "github.com/docker/libnetwork/client" "github.com/docker/libnetwork/config" "github.com/docker/libnetwork/netlabel" "github.com/docker/libnetwork/options" @@ -36,6 +35,8 @@ const ( defaultCfgFile = "/etc/default/libnetwork.toml" ) +var epConn *dnetConnection + func main() { if reexec.Init() { return @@ -44,7 +45,7 @@ func main() { _, stdout, stderr := term.StdStreams() logrus.SetOutput(stderr) - err := dnetCommand(stdout, stderr) + err := dnetApp(stdout, stderr) if err != nil { os.Exit(1) } @@ -89,61 +90,16 @@ func processConfig(cfg *config.Config) []config.Option { return options } -func dnetCommand(stdout, stderr io.Writer) error { - flag.Parse() +func dnetApp(stdout, stderr io.Writer) error { + app := cli.NewApp() - if *flHelp { - flag.Usage() - return nil - } + app.Name = "dnet" + app.Usage = "A self-sufficient runtime for container networking." + app.Flags = dnetFlags + app.Before = processFlags + app.Commands = dnetCommands - if *flLogLevel != "" { - lvl, err := logrus.ParseLevel(*flLogLevel) - if err != nil { - fmt.Fprintf(stderr, "Unable to parse logging level: %s\n", *flLogLevel) - return err - } - logrus.SetLevel(lvl) - } else { - logrus.SetLevel(logrus.InfoLevel) - } - - if *flDebug { - logrus.SetLevel(logrus.DebugLevel) - } - - if *flHost == "" { - defaultHost := os.Getenv("DNET_HOST") - if defaultHost == "" { - // TODO : Add UDS support - defaultHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort) - } - *flHost = defaultHost - } - - dc, err := newDnetConnection(*flHost) - if err != nil { - if *flDaemon { - logrus.Error(err) - } else { - fmt.Fprint(stderr, err) - } - return err - } - - if *flDaemon { - err := dc.dnetDaemon() - if err != nil { - logrus.Errorf("dnet Daemon exited with an error : %v", err) - } - return err - } - - cli := client.NewNetworkCli(stdout, stderr, dc.httpCall) - if err := cli.Cmd("dnet", flag.Args()...); err != nil { - fmt.Fprintln(stderr, err) - return err - } + app.Run(os.Args) return nil } @@ -177,8 +133,8 @@ type dnetConnection struct { addr string } -func (d *dnetConnection) dnetDaemon() error { - cfg, err := parseConfig(*flCfgFile) +func (d *dnetConnection) dnetDaemon(cfgFile string) error { + cfg, err := parseConfig(cfgFile) var cOptions []config.Option if err == nil { cOptions = processConfig(cfg) diff --git a/libnetwork/cmd/dnet/dnet_test.go b/libnetwork/cmd/dnet/dnet_test.go deleted file mode 100644 index 44ca1cd1d4..0000000000 --- a/libnetwork/cmd/dnet/dnet_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "os" - "testing" - "time" - - "github.com/docker/libnetwork/testutils" -) - -const dnetCommandName = "dnet" - -var origStdOut = os.Stdout - -func TestDnetDaemonCustom(t *testing.T) { - if !testutils.IsRunningInContainer() { - t.Skip("This test must run inside a container ") - } - customPort := 4567 - doneChan := make(chan bool) - go func() { - args := []string{dnetCommandName, "-d", fmt.Sprintf("-H=:%d", customPort)} - executeDnetCommand(t, args, true) - doneChan <- true - }() - - select { - case <-doneChan: - t.Fatal("dnet Daemon is not supposed to exit") - case <-time.After(3 * time.Second): - args := []string{dnetCommandName, "-d=false", fmt.Sprintf("-H=:%d", customPort), "-D", "network", "ls"} - executeDnetCommand(t, args, true) - } -} - -func TestDnetDaemonInvalidCustom(t *testing.T) { - if !testutils.IsRunningInContainer() { - t.Skip("This test must run inside a container ") - } - customPort := 4668 - doneChan := make(chan bool) - go func() { - args := []string{dnetCommandName, "-d=true", fmt.Sprintf("-H=:%d", customPort)} - executeDnetCommand(t, args, true) - doneChan <- true - }() - - select { - case <-doneChan: - t.Fatal("dnet Daemon is not supposed to exit") - case <-time.After(3 * time.Second): - args := []string{dnetCommandName, "-d=false", "-H=:6669", "-D", "network", "ls"} - executeDnetCommand(t, args, false) - } -} - -func TestDnetDaemonInvalidParams(t *testing.T) { - if !testutils.IsRunningInContainer() { - t.Skip("This test must run inside a container ") - } - args := []string{dnetCommandName, "-d=false", "-H=tcp:/127.0.0.1:8080"} - executeDnetCommand(t, args, false) - - args = []string{dnetCommandName, "-d=false", "-H=unix://var/run/dnet.sock"} - executeDnetCommand(t, args, false) - - args = []string{dnetCommandName, "-d=false", "-H=", "-l=invalid"} - executeDnetCommand(t, args, false) - - args = []string{dnetCommandName, "-d=false", "-H=", "-l=error", "invalid"} - executeDnetCommand(t, args, false) -} - -func TestDnetDefaultsWithFlags(t *testing.T) { - if !testutils.IsRunningInContainer() { - t.Skip("This test must run inside a container ") - } - doneChan := make(chan bool) - go func() { - args := []string{dnetCommandName, "-d=true", "-H=", "-l=error"} - executeDnetCommand(t, args, true) - doneChan <- true - }() - - select { - case <-doneChan: - t.Fatal("dnet Daemon is not supposed to exit") - case <-time.After(3 * time.Second): - args := []string{dnetCommandName, "-d=false", "network", "create", "-d=null", "test"} - executeDnetCommand(t, args, true) - - args = []string{dnetCommandName, "-d=false", "-D", "network", "ls"} - executeDnetCommand(t, args, true) - } -} - -func TestDnetMain(t *testing.T) { - if !testutils.IsRunningInContainer() { - t.Skip("This test must run inside a container ") - } - customPort := 4568 - doneChan := make(chan bool) - go func() { - args := []string{dnetCommandName, "-d=true", "-h=false", fmt.Sprintf("-H=:%d", customPort)} - os.Args = args - main() - doneChan <- true - }() - select { - case <-doneChan: - t.Fatal("dnet Daemon is not supposed to exit") - case <-time.After(2 * time.Second): - } -} - -func executeDnetCommand(t *testing.T, args []string, shouldSucced bool) { - _, w, _ := os.Pipe() - os.Stdout = w - - os.Args = args - err := dnetCommand(ioutil.Discard, ioutil.Discard) - if shouldSucced && err != nil { - os.Stdout = origStdOut - t.Fatalf("cli [%v] must succeed, but failed with an error : %v", args, err) - } else if !shouldSucced && err == nil { - os.Stdout = origStdOut - t.Fatalf("cli [%v] must fail, but succeeded with an error : %v", args, err) - } - os.Stdout = origStdOut -} diff --git a/libnetwork/cmd/dnet/flags.go b/libnetwork/cmd/dnet/flags.go index 27dfbd19ed..50c341746c 100644 --- a/libnetwork/cmd/dnet/flags.go +++ b/libnetwork/cmd/dnet/flags.go @@ -4,48 +4,84 @@ import ( "fmt" "os" - flag "github.com/docker/docker/pkg/mflag" + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" ) -type command struct { - name string - description string -} - -type byName []command - var ( - flDaemon = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode") - flHost = flag.String([]string{"H", "-host"}, "", "Daemon socket to connect to") - flLogLevel = flag.String([]string{"l", "-log-level"}, "info", "Set the logging level") - flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode") - flCfgFile = flag.String([]string{"c", "-cfg-file"}, "/etc/default/libnetwork.toml", "Configuration file") - flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage") - - dnetCommands = []command{ - {"network", "Network management commands"}, - {"service", "Service management commands"}, + dnetFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "d, -daemon", + Usage: "Enable daemon mode", + }, + cli.StringFlag{ + Name: "H, -host", + Value: "", + Usage: "Daemon socket to connect to", + }, + cli.StringFlag{ + Name: "l, -log-level", + Value: "info", + Usage: "Set the logging level", + }, + cli.BoolFlag{ + Name: "D, -debug", + Usage: "Enable debug mode", + }, + cli.StringFlag{ + Name: "c, -cfg-file", + Value: "/etc/default/libnetwork.toml", + Usage: "Configuration file", + }, } ) -func init() { - flag.Usage = func() { - fmt.Fprint(os.Stdout, "Usage: dnet [OPTIONS] COMMAND [arg...]\n\nA self-sufficient runtime for container networking.\n\nOptions:\n") +func processFlags(c *cli.Context) error { + var err error - flag.CommandLine.SetOutput(os.Stdout) - flag.PrintDefaults() - - help := "\nCommands:\n" - - for _, cmd := range dnetCommands { - help += fmt.Sprintf(" %-10.10s%s\n", cmd.name, cmd.description) + if c.String("l") != "" { + lvl, err := logrus.ParseLevel(c.String("l")) + if err != nil { + fmt.Printf("Unable to parse logging level: %s\n", c.String("l")) + os.Exit(1) } - - help += "\nRun 'dnet COMMAND --help' for more information on a command." - fmt.Fprintf(os.Stdout, "%s\n", help) + logrus.SetLevel(lvl) + } else { + logrus.SetLevel(logrus.InfoLevel) } -} -func printUsage() { - fmt.Println("Usage: dnet COMMAND [arg...]") + if c.Bool("D") { + logrus.SetLevel(logrus.DebugLevel) + } + + hostFlag := c.String("H") + if hostFlag == "" { + defaultHost := os.Getenv("DNET_HOST") + if defaultHost == "" { + // TODO : Add UDS support + defaultHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort) + } + hostFlag = defaultHost + } + + epConn, err = newDnetConnection(hostFlag) + if err != nil { + if c.Bool("d") { + logrus.Error(err) + } else { + fmt.Println(err) + } + os.Exit(1) + } + + if c.Bool("d") { + err = epConn.dnetDaemon(c.String("c")) + if err != nil { + logrus.Errorf("dnet Daemon exited with an error : %v", err) + os.Exit(1) + } + os.Exit(1) + } + + return nil } diff --git a/libnetwork/test/integration/dnet/dnet.bats b/libnetwork/test/integration/dnet/dnet.bats new file mode 100644 index 0000000000..3d3a94261a --- /dev/null +++ b/libnetwork/test/integration/dnet/dnet.bats @@ -0,0 +1,31 @@ +#!/usr/bin/env bats + +load helpers + +@test "Test dnet custom port" { + start_dnet 1 a none null 4567 + dnet_cmd 4567 network ls + stop_dnet 1 a +} + +@test "Test dnet invalid custom port" { + start_dnet 1 b none null 4567 + run dnet_cmd 4568 network ls + echo ${output} + [ "$status" -ne 0 ] + stop_dnet 1 b +} + +@test "Test dnet invalid params" { + start_dnet 1 c none null + run dnet_cmd 8080 network ls + echo ${output} + [ "$status" -ne 0 ] + run ./cmd/dnet/dnet -H=unix://var/run/dnet.sock network ls + echo ${output} + [ "$status" -ne 0 ] + run ./cmd/dnet/dnet -H= -l=invalid network ls + echo ${output} + [ "$status" -ne 0 ] + stop_dnet 1 c +} diff --git a/libnetwork/test/integration/dnet/helpers.bash b/libnetwork/test/integration/dnet/helpers.bash index bb188d31ac..b2fe6f59ca 100644 --- a/libnetwork/test/integration/dnet/helpers.bash +++ b/libnetwork/test/integration/dnet/helpers.bash @@ -1,10 +1,21 @@ +function inst_id2port() { + echo $((41000+${1}-1)) +} + function start_consul() { stop_consul - docker run -d --name=pr_consul -p 8500:8500 -p 8300-8302:8300-8302/tcp -p 8300-8302:8300-8302/udp -h consul progrium/consul -server -bootstrap + docker run -d \ + --name=pr_consul \ + -p 8500:8500 \ + -p 8300-8302:8300-8302/tcp \ + -p 8300-8302:8300-8302/udp \ + -h consul \ + progrium/consul -server -bootstrap sleep 2 } function stop_consul() { + echo "consul started" docker stop pr_consul || true # You cannot destroy a container in Circle CI. So do not attempt destroy in circleci if [ -z "$CIRCLECI" ]; then @@ -13,11 +24,19 @@ function stop_consul() { } function start_dnet() { - stop_dnet $1 - name="dnet-$1" - hport=$((41000+${1}-1)) + stop_dnet $1 $2 + name="dnet-$1-$2" + if [ -z "$5" ] + then + hport=$((41000+${1}-1)) + cport=2385 + hopt="" + else + hport=$5 + cport=$5 + hopt="-H tcp://0.0.0.0:${cport}" + fi - bridge_ip=$(docker inspect --format '{{.NetworkSettings.Gateway}}' pr_consul) mkdir -p /tmp/dnet/${name} tomlfile="/tmp/dnet/${name}/libnetwork.toml" cat > ${tomlfile} <> ${tomlfile} <