mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Initial dnet tool to test and manage libnetwork end-to-end
Signed-off-by: Madhu Venugopal <madhu@docker.com>
This commit is contained in:
parent
57628535ba
commit
36a0f91b5d
3 changed files with 387 additions and 0 deletions
206
libnetwork/cmd/dnet/dnet.go
Normal file
206
libnetwork/cmd/dnet/dnet.go
Normal file
|
@ -0,0 +1,206 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/libnetwork"
|
||||
"github.com/docker/libnetwork/api"
|
||||
"github.com/docker/libnetwork/client"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultHTTPHost is used if only port is provided to -H flag e.g. docker -d -H tcp://:8080
|
||||
DefaultHTTPHost = "127.0.0.1"
|
||||
// DefaultHTTPPort is the default http port used by dnet
|
||||
DefaultHTTPPort = 2385
|
||||
// DefaultUnixSocket exported
|
||||
DefaultUnixSocket = "/var/run/dnet.sock"
|
||||
)
|
||||
|
||||
func main() {
|
||||
_, stdout, stderr := term.StdStreams()
|
||||
logrus.SetOutput(stderr)
|
||||
|
||||
err := dnetCommand(stdout, stderr)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func dnetCommand(stdout, stderr io.Writer) error {
|
||||
flag.Parse()
|
||||
|
||||
if *flHelp {
|
||||
flag.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type dnetConnection struct {
|
||||
// proto holds the client protocol i.e. unix.
|
||||
proto string
|
||||
// addr holds the client address.
|
||||
addr string
|
||||
}
|
||||
|
||||
func (d *dnetConnection) dnetDaemon() error {
|
||||
controller, err := libnetwork.New()
|
||||
if err != nil {
|
||||
fmt.Println("Error starting dnetDaemon :", err)
|
||||
return err
|
||||
}
|
||||
httpHandler := api.NewHTTPHandler(controller)
|
||||
r := mux.NewRouter().StrictSlash(false)
|
||||
post := r.PathPrefix("/networks").Subrouter()
|
||||
post.Methods("GET").HandlerFunc(httpHandler)
|
||||
post.Methods("PUT", "POST").HandlerFunc(httpHandler)
|
||||
post.Methods("DELETE").HandlerFunc(httpHandler)
|
||||
return http.ListenAndServe(d.addr, r)
|
||||
}
|
||||
|
||||
func newDnetConnection(val string) (*dnetConnection, error) {
|
||||
url, err := parsers.ParseHost(DefaultHTTPHost, DefaultUnixSocket, val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protoAddrParts := strings.SplitN(url, "://", 2)
|
||||
if len(protoAddrParts) != 2 {
|
||||
return nil, fmt.Errorf("bad format, expected tcp://ADDR")
|
||||
}
|
||||
if strings.ToLower(protoAddrParts[0]) != "tcp" {
|
||||
return nil, fmt.Errorf("dnet currently only supports tcp transport")
|
||||
}
|
||||
|
||||
return &dnetConnection{protoAddrParts[0], protoAddrParts[1]}, nil
|
||||
}
|
||||
|
||||
func (d *dnetConnection) httpCall(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
|
||||
var in io.Reader
|
||||
in, err := encodeData(data)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("%s", path), in)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
setupRequestHeaders(method, data, req, headers)
|
||||
|
||||
req.URL.Host = d.addr
|
||||
req.URL.Scheme = "http"
|
||||
|
||||
httpClient := &http.Client{}
|
||||
resp, err := httpClient.Do(req)
|
||||
statusCode := -1
|
||||
if resp != nil {
|
||||
statusCode = resp.StatusCode
|
||||
}
|
||||
if err != nil {
|
||||
return nil, statusCode, fmt.Errorf("An error occurred trying to connect: %v", err)
|
||||
}
|
||||
|
||||
if statusCode < 200 || statusCode >= 400 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, statusCode, err
|
||||
}
|
||||
return nil, statusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
||||
}
|
||||
|
||||
return resp.Body, statusCode, nil
|
||||
}
|
||||
|
||||
func setupRequestHeaders(method string, data interface{}, req *http.Request, headers map[string][]string) {
|
||||
if data != nil {
|
||||
if headers == nil {
|
||||
headers = make(map[string][]string)
|
||||
}
|
||||
headers["Content-Type"] = []string{"application/json"}
|
||||
}
|
||||
|
||||
expectedPayload := (method == "POST" || method == "PUT")
|
||||
|
||||
if expectedPayload && req.Header.Get("Content-Type") == "" {
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
}
|
||||
|
||||
if headers != nil {
|
||||
for k, v := range headers {
|
||||
req.Header[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func encodeData(data interface{}) (*bytes.Buffer, error) {
|
||||
params := bytes.NewBuffer(nil)
|
||||
if data != nil {
|
||||
if err := json.NewEncoder(params).Encode(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return params, nil
|
||||
}
|
132
libnetwork/cmd/dnet/dnet_test.go
Normal file
132
libnetwork/cmd/dnet/dnet_test.go
Normal file
|
@ -0,0 +1,132 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/libnetwork/netutils"
|
||||
)
|
||||
|
||||
const dnetCommandName = "dnet"
|
||||
|
||||
var origStdOut = os.Stdout
|
||||
|
||||
func TestDnetDaemonCustom(t *testing.T) {
|
||||
if !netutils.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 !netutils.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 !netutils.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 !netutils.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 !netutils.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
|
||||
}
|
49
libnetwork/cmd/dnet/flags.go
Normal file
49
libnetwork/cmd/dnet/flags.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
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")
|
||||
flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage")
|
||||
|
||||
dnetCommands = []command{
|
||||
{"network", "Network management commands"},
|
||||
}
|
||||
)
|
||||
|
||||
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")
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
help += "\nRun 'dnet COMMAND --help' for more information on a command."
|
||||
fmt.Fprintf(os.Stdout, "%s\n", help)
|
||||
}
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Println("Usage: dnet network <subcommand> <OPTIONS>")
|
||||
}
|
Loading…
Add table
Reference in a new issue