Add simple integration test cases

- Enhance dnet to use codegansta/cli as the frontend
    - Add `container create/rm` commands only in dnet
    - With the above dnet enhancements add more integration tests

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
This commit is contained in:
Jana Radhakrishnan 2015-09-15 22:26:27 -07:00
parent 59fd1a605a
commit ea4cdf441e
10 changed files with 395 additions and 252 deletions

View file

@ -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)

View file

@ -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 {

View file

@ -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"`

146
libnetwork/cmd/dnet/cmd.go Normal file
View file

@ -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
}

View file

@ -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)

View file

@ -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
}

View file

@ -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 <OPTIONS> 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
}

View file

@ -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
}

View file

@ -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} <<EOF
@ -25,21 +44,42 @@ title = "LibNetwork Configuration file"
[daemon]
debug = false
defaultnetwork = "${2}"
defaultdriver = "${3}"
defaultnetwork = "${3}"
defaultdriver = "${4}"
labels = ["com.docker.network.driver.overlay.bind_interface=eth0"]
[datastore]
embedded = false
EOF
if [ "${4}" == "overlay" ]
then
bridge_ip=$(docker inspect --format '{{.NetworkSettings.Gateway}}' pr_consul)
cat >> ${tomlfile} <<EOF
[datastore.client]
provider = "consul"
Address = "${bridge_ip}:8500"
EOF
docker run -d --name=${name} --privileged -p ${hport}:2385 -v $(pwd)/:/go/src/github.com/docker/libnetwork -v /tmp:/tmp -w /go/src/github.com/docker/libnetwork golang:1.4 ./cmd/dnet/dnet -dD -c ${tomlfile}
fi
docker run \
-d \
--name=${name} \
--privileged \
-p ${hport}:${cport} \
-v $(pwd)/:/go/src/github.com/docker/libnetwork \
-v /tmp:/tmp \
-w /go/src/github.com/docker/libnetwork \
golang:1.4 ./cmd/dnet/dnet -d -D ${hopt} -c ${tomlfile}
sleep 2
}
function skip_for_circleci() {
if [ -n "$CIRCLECI" ]; then
skip
fi
}
function stop_dnet() {
name="dnet-$1"
name="dnet-$1-$2"
rm -rf /tmp/dnet/${name} || true
docker stop ${name} || true
# You cannot destroy a container in Circle CI. So do not attempt destroy in circleci
@ -50,7 +90,7 @@ function stop_dnet() {
}
function dnet_cmd() {
hport=$((41000+${1}-1))
hport=$1
shift
./cmd/dnet/dnet -H 127.0.0.1:${hport} $*
./cmd/dnet/dnet -H tcp://127.0.0.1:${hport} $*
}

View file

@ -2,28 +2,24 @@
load helpers
export BATS_TEST_CNT=0
function setup() {
if [ "${BATS_TEST_CNT}" -eq 0 ]; then
if [ "${BATS_TEST_NUMBER}" -eq 1 ]; then
start_consul
start_dnet 1 multihost overlay
export BATS_TEST_CNT=$((${BATS_TEST_CNT}+1))
start_dnet 1 simple multihost overlay
fi
}
function teardown() {
export BATS_TEST_CNT=$((${BATS_TEST_CNT}-1))
if [ "${BATS_TEST_CNT}" -eq 0 ]; then
stop_dnet 1
if [ "${BATS_TEST_NUMBER}" -eq 6 ]; then
stop_dnet 1 simple
stop_consul
fi
}
@test "Test default network" {
echo $(docker ps)
run dnet_cmd 1 network ls
run dnet_cmd $(inst_id2port 1) network ls
[ "$status" -eq 0 ]
echo ${output}
echo ${lines[1]}
name=$(echo ${lines[1]} | cut -d" " -f2)
@ -32,3 +28,72 @@ function teardown() {
[ "$name" = "multihost" ]
[ "$driver" = "overlay" ]
}
@test "Test network create" {
echo $(docker ps)
run dnet_cmd $(inst_id2port 1) network create -d overlay mh1
[ "$status" -eq 0 ]
line=$(dnet_cmd $(inst_id2port 1) network ls | grep mh1)
echo ${line}
name=$(echo ${line} | cut -d" " -f2)
driver=$(echo ${line} | cut -d" " -f3)
echo ${name} ${driver}
[ "$name" = "mh1" ]
[ "$driver" = "overlay" ]
dnet_cmd $(inst_id2port 1) network rm mh1
}
@test "Test network delete with id" {
echo $(docker ps)
run dnet_cmd $(inst_id2port 1) network create -d overlay mh1
[ "$status" -eq 0 ]
echo ${output}
dnet_cmd $(inst_id2port 1) network rm ${output}
}
@test "Test service create" {
echo $(docker ps)
run dnet_cmd $(inst_id2port 1) service publish svc1.multihost
[ "$status" -eq 0 ]
echo ${output}
run dnet_cmd $(inst_id2port 1) service ls
[ "$status" -eq 0 ]
echo ${output}
echo ${lines[1]}
svc=$(echo ${lines[1]} | cut -d" " -f2)
network=$(echo ${lines[1]} | cut -d" " -f3)
echo ${svc} ${network}
[ "$network" = "multihost" ]
[ "$svc" = "svc1" ]
dnet_cmd $(inst_id2port 1) service unpublish svc1.multihost
}
@test "Test service delete with id" {
echo $(docker ps)
run dnet_cmd $(inst_id2port 1) service publish svc1.multihost
[ "$status" -eq 0 ]
echo ${output}
run dnet_cmd $(inst_id2port 1) service ls
[ "$status" -eq 0 ]
echo ${output}
echo ${lines[1]}
id=$(echo ${lines[1]} | cut -d" " -f1)
dnet_cmd $(inst_id2port 1) service unpublish ${id}
}
@test "Test service attach" {
skip_for_circleci
echo $(docker ps)
dnet_cmd $(inst_id2port 1) service publish svc1.multihost
dnet_cmd $(inst_id2port 1) container create container_1
dnet_cmd $(inst_id2port 1) service attach container_1 svc1.multihost
run dnet_cmd $(inst_id2port 1) service ls
[ "$status" -eq 0 ]
echo ${output}
echo ${lines[1]}
container=$(echo ${lines[1]} | cut -d" " -f4)
[ "$container" = "container_1" ]
dnet_cmd $(inst_id2port 1) service detach container_1 svc1.multihost
dnet_cmd $(inst_id2port 1) container rm container_1
dnet_cmd $(inst_id2port 1) service unpublish svc1.multihost
}