Merge pull request #42262 from cpuguy83/move_libnetwork

Move libnetwork
This commit is contained in:
Brian Goff 2021-06-03 12:06:31 -07:00 committed by GitHub
commit a77317882d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
434 changed files with 35389 additions and 885 deletions

View file

@ -166,13 +166,6 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \
PREFIX=/build /tmp/install/install.sh containerd
FROM dev-base AS proxy
ARG LIBNETWORK_COMMIT
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \
--mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \
PREFIX=/build /tmp/install/install.sh proxy
FROM base AS golangci_lint
ARG GOLANGCI_LINT_COMMIT
RUN --mount=type=cache,target=/root/.cache/go-build \
@ -307,7 +300,6 @@ COPY --from=runc /build/ /usr/local/bin/
COPY --from=containerd /build/ /usr/local/bin/
COPY --from=rootlesskit /build/ /usr/local/bin/
COPY --from=vpnkit /build/ /usr/local/bin/
COPY --from=proxy /build/ /usr/local/bin/
ENV PATH=/usr/local/cli:$PATH
ARG DOCKER_BUILDTAGS
ENV DOCKER_BUILDTAGS="${DOCKER_BUILDTAGS}"
@ -353,7 +345,6 @@ COPY --from=tini /build/ /usr/local/bin/
COPY --from=runc /build/ /usr/local/bin/
COPY --from=containerd /build/ /usr/local/bin/
COPY --from=rootlesskit /build/ /usr/local/bin/
COPY --from=proxy /build/ /usr/local/bin/
COPY --from=vpnkit /build/ /usr/local/bin/
WORKDIR /go/src/github.com/docker/docker

18
Jenkinsfile vendored
View file

@ -121,7 +121,7 @@ pipeline {
-e VALIDATE_BRANCH=${CHANGE_TARGET} \
docker:${GIT_COMMIT} \
hack/make.sh \
dynbinary-daemon \
dynbinary \
test-docker-py
'''
}
@ -160,7 +160,7 @@ pipeline {
-e DOCKER_GITCOMMIT=${GIT_COMMIT} \
-e DOCKER_GRAPHDRIVER \
docker:${GIT_COMMIT} \
hack/make.sh binary-daemon
hack/make.sh binary
'''
}
}
@ -180,6 +180,9 @@ pipeline {
// needs to be last stage that calls make.sh for the junit report to work
stage("Unit tests") {
steps {
sh '''
sudo modprobe ip6table_filter
'''
sh '''
docker run --rm -t --privileged \
-v "$WORKSPACE/bundles:/go/src/github.com/docker/docker/bundles" \
@ -319,7 +322,7 @@ pipeline {
-e DOCKER_GRAPHDRIVER \
docker:${GIT_COMMIT} \
hack/make.sh \
dynbinary-daemon
dynbinary
# flaky + integration
TEST_INTEGRATION_DEST=1 CONTAINER_NAME=${CONTAINER_NAME}-1 TEST_SKIP_INTEGRATION_CLI=1 run_tests test-integration-flaky &
@ -577,6 +580,9 @@ pipeline {
}
stage("Unit tests") {
steps {
sh '''
sudo modprobe ip6table_filter
'''
sh '''
docker run --rm -t --privileged \
-v "$WORKSPACE/bundles:/go/src/github.com/docker/docker/bundles" \
@ -776,6 +782,9 @@ pipeline {
}
stage("Unit tests") {
steps {
sh '''
sudo modprobe ip6table_filter
'''
sh '''
docker run --rm -t --privileged \
-v "$WORKSPACE/bundles:/go/src/github.com/docker/docker/bundles" \
@ -972,6 +981,9 @@ pipeline {
}
stage("Unit tests") {
steps {
sh '''
sudo modprobe ip6table_filter
'''
sh '''
docker run --rm -t --privileged \
-v "$WORKSPACE/bundles:/go/src/github.com/docker/docker/bundles" \

View file

@ -6,7 +6,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
"github.com/docker/libnetwork"
"github.com/docker/docker/libnetwork"
)
// Backend is all the methods that need to be implemented

View file

@ -14,8 +14,8 @@ import (
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/errdefs"
"github.com/docker/libnetwork"
netconst "github.com/docker/libnetwork/datastore"
"github.com/docker/docker/libnetwork"
netconst "github.com/docker/docker/libnetwork/datastore"
"github.com/pkg/errors"
)

View file

@ -17,10 +17,10 @@ import (
"github.com/docker/docker/builder"
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/daemon/images"
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/system"
"github.com/docker/libnetwork"
controlapi "github.com/moby/buildkit/api/services/control"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/control"

View file

@ -10,9 +10,9 @@ import (
"sync"
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/libnetwork"
"github.com/moby/buildkit/executor"
"github.com/moby/buildkit/executor/oci"
"github.com/moby/buildkit/executor/runcexecutor"

View file

@ -5,8 +5,8 @@ import (
"errors"
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/libnetwork"
"github.com/moby/buildkit/executor"
"github.com/moby/buildkit/executor/oci"
)

View file

@ -16,8 +16,8 @@ import (
"github.com/docker/docker/daemon"
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/libcontainerd/supervisor"
"github.com/docker/docker/libnetwork/portallocator"
"github.com/docker/docker/pkg/homedir"
"github.com/docker/libnetwork/portallocator"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"

View file

@ -3,7 +3,7 @@ package daemon // import "github.com/docker/docker/daemon"
import (
apitypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
lncluster "github.com/docker/libnetwork/cluster"
lncluster "github.com/docker/docker/libnetwork/cluster"
)
// Cluster is the interface for github.com/docker/docker/daemon/cluster.(*Cluster).

View file

@ -53,8 +53,8 @@ import (
types "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/daemon/cluster/controllers/plugin"
executorpkg "github.com/docker/docker/daemon/cluster/executor"
lncluster "github.com/docker/docker/libnetwork/cluster"
"github.com/docker/docker/pkg/signal"
lncluster "github.com/docker/libnetwork/cluster"
swarmapi "github.com/docker/swarmkit/api"
swarmnode "github.com/docker/swarmkit/node"
"github.com/pkg/errors"

View file

@ -6,7 +6,7 @@ import (
basictypes "github.com/docker/docker/api/types"
networktypes "github.com/docker/docker/api/types/network"
types "github.com/docker/docker/api/types/swarm"
netconst "github.com/docker/libnetwork/datastore"
netconst "github.com/docker/docker/libnetwork/datastore"
swarmapi "github.com/docker/swarmkit/api"
gogotypes "github.com/gogo/protobuf/types"
)

View file

@ -17,11 +17,11 @@ import (
containerpkg "github.com/docker/docker/container"
clustertypes "github.com/docker/docker/daemon/cluster/provider"
networkSettings "github.com/docker/docker/daemon/network"
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/libnetwork/cluster"
networktypes "github.com/docker/docker/libnetwork/types"
"github.com/docker/docker/plugin"
volumeopts "github.com/docker/docker/volume/service/opts"
"github.com/docker/libnetwork"
"github.com/docker/libnetwork/cluster"
networktypes "github.com/docker/libnetwork/types"
"github.com/docker/swarmkit/agent/exec"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)

View file

@ -20,8 +20,8 @@ import (
"github.com/docker/docker/daemon"
"github.com/docker/docker/daemon/cluster/convert"
executorpkg "github.com/docker/docker/daemon/cluster/executor"
"github.com/docker/docker/libnetwork"
volumeopts "github.com/docker/docker/volume/service/opts"
"github.com/docker/libnetwork"
"github.com/docker/swarmkit/agent/exec"
"github.com/docker/swarmkit/api"
"github.com/docker/swarmkit/log"

View file

@ -20,9 +20,9 @@ import (
"github.com/docker/docker/daemon/cluster/convert"
executorpkg "github.com/docker/docker/daemon/cluster/executor"
clustertypes "github.com/docker/docker/daemon/cluster/provider"
netconst "github.com/docker/docker/libnetwork/datastore"
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
netconst "github.com/docker/libnetwork/datastore"
"github.com/docker/swarmkit/agent/exec"
"github.com/docker/swarmkit/api"
"github.com/docker/swarmkit/api/genericresource"

View file

@ -11,8 +11,8 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/events"
executorpkg "github.com/docker/docker/daemon/cluster/executor"
"github.com/docker/docker/libnetwork"
"github.com/docker/go-connections/nat"
"github.com/docker/libnetwork"
"github.com/docker/swarmkit/agent/exec"
"github.com/docker/swarmkit/api"
"github.com/docker/swarmkit/log"

View file

@ -15,7 +15,7 @@ import (
"github.com/docker/docker/daemon/cluster/convert"
executorpkg "github.com/docker/docker/daemon/cluster/executor"
clustertypes "github.com/docker/docker/daemon/cluster/provider"
networktypes "github.com/docker/libnetwork/types"
networktypes "github.com/docker/docker/libnetwork/types"
"github.com/docker/swarmkit/agent"
"github.com/docker/swarmkit/agent/exec"
"github.com/docker/swarmkit/api"

View file

@ -10,7 +10,7 @@ import (
types "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/daemon/cluster/executor/container"
lncluster "github.com/docker/libnetwork/cluster"
lncluster "github.com/docker/docker/libnetwork/cluster"
swarmapi "github.com/docker/swarmkit/api"
swarmallocator "github.com/docker/swarmkit/manager/allocator/cnmallocator"
swarmnode "github.com/docker/swarmkit/node"

View file

@ -7,8 +7,8 @@ import (
"testing"
"github.com/docker/docker/daemon/discovery"
"github.com/docker/docker/libnetwork/ipamutils"
"github.com/docker/docker/opts"
"github.com/docker/libnetwork/ipamutils"
"github.com/spf13/pflag"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"

View file

@ -14,15 +14,15 @@ import (
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/network"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/libnetwork"
netconst "github.com/docker/docker/libnetwork/datastore"
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/libnetwork/options"
"github.com/docker/docker/libnetwork/types"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/runconfig"
"github.com/docker/go-connections/nat"
"github.com/docker/libnetwork"
netconst "github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/options"
"github.com/docker/libnetwork/types"
"github.com/sirupsen/logrus"
)

View file

@ -12,11 +12,11 @@ import (
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/links"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/runconfig"
"github.com/docker/libnetwork"
"github.com/moby/sys/mount"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"

View file

@ -6,8 +6,8 @@ import (
"os"
"github.com/docker/docker/container"
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/pkg/system"
"github.com/docker/libnetwork"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

View file

@ -55,6 +55,9 @@ import (
"github.com/docker/docker/layer"
"github.com/docker/docker/libcontainerd"
libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/libnetwork/cluster"
nwconfig "github.com/docker/docker/libnetwork/config"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/plugingetter"
"github.com/docker/docker/pkg/system"
@ -65,9 +68,6 @@ import (
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
volumesservice "github.com/docker/docker/volume/service"
"github.com/docker/libnetwork"
"github.com/docker/libnetwork/cluster"
nwconfig "github.com/docker/libnetwork/config"
"github.com/moby/locker"
"github.com/pkg/errors"
"golang.org/x/sync/semaphore"

View file

@ -9,7 +9,7 @@ import (
"strings"
"github.com/docker/docker/daemon/config"
"github.com/docker/libnetwork/resolvconf"
"github.com/docker/docker/libnetwork/resolvconf"
"github.com/moby/sys/mount"
"github.com/moby/sys/mountinfo"
"github.com/pkg/errors"

View file

@ -10,12 +10,12 @@ import (
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/container"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/libnetwork"
_ "github.com/docker/docker/pkg/discovery/memory"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/truncindex"
volumesservice "github.com/docker/docker/volume/service"
"github.com/docker/go-connections/nat"
"github.com/docker/libnetwork"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"

View file

@ -28,6 +28,13 @@ import (
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/daemon/initlayer"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/libnetwork"
nwconfig "github.com/docker/docker/libnetwork/config"
"github.com/docker/docker/libnetwork/drivers/bridge"
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/libnetwork/netutils"
"github.com/docker/docker/libnetwork/options"
lntypes "github.com/docker/docker/libnetwork/types"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/containerfs"
"github.com/docker/docker/pkg/idtools"
@ -36,13 +43,6 @@ import (
"github.com/docker/docker/pkg/sysinfo"
"github.com/docker/docker/runconfig"
volumemounts "github.com/docker/docker/volume/mounts"
"github.com/docker/libnetwork"
nwconfig "github.com/docker/libnetwork/config"
"github.com/docker/libnetwork/drivers/bridge"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/netutils"
"github.com/docker/libnetwork/options"
lntypes "github.com/docker/libnetwork/types"
"github.com/moby/sys/mount"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux"

View file

@ -14,6 +14,12 @@ import (
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/libnetwork"
nwconfig "github.com/docker/docker/libnetwork/config"
"github.com/docker/docker/libnetwork/datastore"
winlibnetwork "github.com/docker/docker/libnetwork/drivers/windows"
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/libnetwork/options"
"github.com/docker/docker/pkg/containerfs"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/parsers"
@ -21,12 +27,6 @@ import (
"github.com/docker/docker/pkg/sysinfo"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/runconfig"
"github.com/docker/libnetwork"
nwconfig "github.com/docker/libnetwork/config"
"github.com/docker/libnetwork/datastore"
winlibnetwork "github.com/docker/libnetwork/drivers/windows"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/options"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/windows"

View file

@ -10,7 +10,7 @@ import (
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/container"
daemonevents "github.com/docker/docker/daemon/events"
"github.com/docker/libnetwork"
"github.com/docker/docker/libnetwork"
swarmapi "github.com/docker/swarmkit/api"
gogotypes "github.com/gogo/protobuf/types"
"github.com/sirupsen/logrus"

View file

@ -17,18 +17,18 @@ import (
clustertypes "github.com/docker/docker/daemon/cluster/provider"
internalnetwork "github.com/docker/docker/daemon/network"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/libnetwork"
lncluster "github.com/docker/docker/libnetwork/cluster"
"github.com/docker/docker/libnetwork/driverapi"
"github.com/docker/docker/libnetwork/ipamapi"
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/libnetwork/networkdb"
"github.com/docker/docker/libnetwork/options"
networktypes "github.com/docker/docker/libnetwork/types"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/plugingetter"
"github.com/docker/docker/runconfig"
"github.com/docker/go-connections/nat"
"github.com/docker/libnetwork"
lncluster "github.com/docker/libnetwork/cluster"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/ipamapi"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/networkdb"
"github.com/docker/libnetwork/options"
networktypes "github.com/docker/libnetwork/types"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

View file

@ -3,7 +3,7 @@ package daemon
import (
"strings"
"github.com/docker/libnetwork"
"github.com/docker/docker/libnetwork"
)
// getEndpointInNetwork returns the container's endpoint to the provided network.

View file

@ -10,9 +10,9 @@ import (
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/daemon/network"
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/pkg/containerfs"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/libnetwork"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/skip"

View file

@ -13,8 +13,8 @@ import (
"github.com/docker/docker/api/types/filters"
timetypes "github.com/docker/docker/api/types/time"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/runconfig"
"github.com/docker/libnetwork"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

View file

@ -9,10 +9,10 @@ import (
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/daemon/images"
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/pkg/discovery"
_ "github.com/docker/docker/pkg/discovery/memory"
"github.com/docker/docker/registry"
"github.com/docker/libnetwork"
"github.com/sirupsen/logrus"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"

View file

@ -5,7 +5,7 @@ import (
dockercontainer "github.com/docker/docker/container"
"github.com/docker/docker/errdefs"
"github.com/docker/libnetwork"
"github.com/docker/docker/libnetwork"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

View file

@ -8,8 +8,6 @@ binary_extension() {
fi
}
GO_PACKAGE='github.com/docker/docker/cmd/dockerd'
BINARY_SHORT_NAME='dockerd'
BINARY_NAME="$BINARY_SHORT_NAME-$VERSION"
BINARY_EXTENSION="$(binary_extension)"
BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION"

14
hack/make/.install Normal file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env bash
install_binary() {
local file="$1"
local target="${DOCKER_MAKE_INSTALL_PREFIX:=/usr/local}/bin/"
if [ "$(go env GOOS)" == "linux" ]; then
echo "Installing $(basename $file) to ${target}"
mkdir -p "$target"
cp -f -L "$file" "$target"
else
echo "Install is only supported on linux"
return 1
fi
}

View file

@ -7,4 +7,5 @@ rm -rf "$DEST"
DEST="${DEST}-daemon"
ABS_DEST="${ABS_DEST}-daemon"
. hack/make/binary-daemon
. hack/make/binary-proxy
)

View file

@ -14,7 +14,7 @@ copy_binaries() {
return
fi
echo "Copying nested executables into $dir"
for file in containerd containerd-shim containerd-shim-runc-v2 ctr runc docker-init docker-proxy rootlesskit rootlesskit-docker-proxy dockerd-rootless.sh dockerd-rootless-setuptool.sh; do
for file in containerd containerd-shim containerd-shim-runc-v2 ctr runc docker-init rootlesskit rootlesskit-docker-proxy dockerd-rootless.sh dockerd-rootless-setuptool.sh; do
cp -f "$(command -v "$file")" "$dir/"
if [ "$hash" = "hash" ]; then
hash_files "$dir/$file"
@ -31,5 +31,11 @@ copy_binaries() {
}
[ -z "$KEEPDEST" ] && rm -rf "$DEST"
source "${MAKEDIR}/.binary"
copy_binaries "$DEST" 'hash'
(
GO_PACKAGE='github.com/docker/docker/cmd/dockerd'
BINARY_SHORT_NAME='dockerd'
source "${MAKEDIR}/.binary"
copy_binaries "$DEST" 'hash'
)

10
hack/make/binary-proxy Normal file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -e
(
GO_PACKAGE='github.com/docker/docker/libnetwork/cmd/proxy'
BINARY_SHORT_NAME='docker-proxy'
source "${MAKEDIR}/.binary"
)

View file

@ -30,7 +30,7 @@ for platform in ${DOCKER_CROSSPLATFORMS}; do
echo "Cross building: ${DEST}"
mkdir -p "${DEST}"
ABS_DEST="$(cd "${DEST}" && pwd -P)"
source "${MAKEDIR}/binary-daemon"
source "${MAKEDIR}/binary"
source "${MAKEDIR}/cross-platform-dependent"
)

View file

@ -1,9 +1,11 @@
#!/usr/bin/env bash
set -e
rm -rf "$DEST"
# This script exists as backwards compatibility for CI
(
DEST="${DEST}-daemon"
ABS_DEST="${ABS_DEST}-daemon"
. hack/make/dynbinary-daemon
. hack/make/dynbinary-proxy
)

View file

@ -1,11 +1,16 @@
#!/usr/bin/env bash
set -e
[ -z "$KEEPDEST" ] && rm -rf "$DEST"
(
export IAMSTATIC='false'
export LDFLAGS_STATIC_DOCKER=''
export BUILDFLAGS=("${BUILDFLAGS[@]/netgo /}") # disable netgo, since we don't need it for a dynamic binary
export BUILDFLAGS=("${BUILDFLAGS[@]/osusergo /}") # ditto for osusergo
export BUILDFLAGS=("${BUILDFLAGS[@]/static_build /}") # we're not building a "static" binary here
GO_PACKAGE='github.com/docker/docker/cmd/dockerd'
BINARY_SHORT_NAME='dockerd'
source "${MAKEDIR}/.binary"
)

15
hack/make/dynbinary-proxy Normal file
View file

@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -e
(
export IAMSTATIC='false'
export LDFLAGS_STATIC_DOCKER=''
export BUILDFLAGS=("${BUILDFLAGS[@]/netgo /}") # disable netgo, since we don't need it for a dynamic binary
export BUILDFLAGS=("${BUILDFLAGS[@]/osusergo /}") # ditto for osusergo
export BUILDFLAGS=("${BUILDFLAGS[@]/static_build /}") # we're not building a "static" binary here
GO_PACKAGE='github.com/docker/docker/libnetwork/cmd/proxy'
BINARY_SHORT_NAME='docker-proxy'
source "${MAKEDIR}/.binary"
)

View file

@ -3,18 +3,7 @@
set -e
rm -rf "$DEST"
install_binary() {
local file="$1"
local target="${DOCKER_MAKE_INSTALL_PREFIX:=/usr/local}/bin/"
if [ "$(go env GOOS)" == "linux" ]; then
echo "Installing $(basename $file) to ${target}"
mkdir -p "$target"
cp -f -L "$file" "$target"
else
echo "Install is only supported on linux"
return 1
fi
}
source "${MAKEDIR}/.install"
(
DEST="$(dirname $DEST)/binary-daemon"

11
hack/make/install-proxy Normal file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -e
rm -rf "$DEST"
source "${MAKEDIR}/.install"
(
DEST="$(dirname $DEST)/binary-proxy"
install_binary "${DEST}/docker-proxy"
)

View file

@ -10,7 +10,7 @@
#
# TESTDIRS='./pkg/term' hack/test/unit
#
set -eu -o pipefail
set -eux -o pipefail
BUILDFLAGS=(-tags 'netgo seccomp libdm_no_deferred_remove')
TESTFLAGS+=" -test.timeout=${TIMEOUT:-5m}"
@ -18,6 +18,11 @@ TESTDIRS="${TESTDIRS:-./...}"
exclude_paths='/vendor/|/integration'
pkg_list=$(go list $TESTDIRS | grep -vE "($exclude_paths)")
echo "${pkg_list}" | grep --fixed-strings "libnetwork/drivers/bridge" \
&& if ! type docker-proxy; then
hack/make.sh binary-proxy install-proxy
fi
mkdir -p bundles
gotestsum --format=standard-quiet --jsonfile=bundles/go-test-report.json --junitfile=bundles/junit-report.xml -- \
"${BUILDFLAGS[@]}" \

View file

@ -25,6 +25,8 @@ linters:
skip-dirs:
- bundles
- docs
# TODO: This package should be completely removed
- libnetwork/client/mflag
linters-settings:
govet:
@ -59,6 +61,11 @@ issues:
- text: "G304: Potential file inclusion via variable"
linters:
- gosec
- text: "G306: Expect WriteFile permissions to be 0600 or less"
linters:
- gosec
- text: 'G307: Deferring unsafe method "Close" on type "*os.File"'
linters: gosec
# Exclude some linters from running on tests files.
- path: _test\.go
@ -86,3 +93,16 @@ issues:
- text: "SA1019: httputil.ErrPersistEOF is deprecated"
linters:
- staticcheck
# This code is doing some fun stuff with reflect and it trips up the linter.
- text: "field `foo` is unused"
path: "libnetwork/options/options_test.go"
linters:
- structcheck
- unused
# This field is only used on windows but is defined in a platform agnostic file.
# The linter doesn't understand that the field is used.
- text: "`resolverOnce` is unused"
path: libnetwork/network.go
linters:
- structcheck
- unused

View file

@ -4,7 +4,7 @@ set -e -o pipefail
shfmtflags="-bn -ci -sr"
# NOTE: `git grep '^#!'` may also pick up non-shell script files.
# Add exceptional files to `egrep -v` if any false-positive is detected.
if git grep --name-only '^#!' | egrep -v '(vendor|\.go|Jenkinsfile)' \
if git grep --name-only '^#!' | egrep -v '(vendor|\.go|Jenkinsfile|\.py|\.bats)' \
| xargs shfmt -d $shfmtflags; then
echo 'Congratulations! The shell scripts are properly formatted.'
else

View file

@ -31,10 +31,10 @@ import (
"github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/integration-cli/cli/build"
"github.com/docker/docker/integration-cli/daemon"
"github.com/docker/docker/libnetwork/iptables"
"github.com/docker/docker/opts"
testdaemon "github.com/docker/docker/testutil/daemon"
units "github.com/docker/go-units"
"github.com/docker/libnetwork/iptables"
"github.com/docker/libtrust"
"github.com/moby/sys/mount"
"golang.org/x/sys/unix"

View file

@ -18,14 +18,14 @@ import (
"github.com/docker/docker/api/types/versions/v1p20"
"github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/integration-cli/daemon"
"github.com/docker/docker/libnetwork/driverapi"
remoteapi "github.com/docker/docker/libnetwork/drivers/remote/api"
"github.com/docker/docker/libnetwork/ipamapi"
remoteipam "github.com/docker/docker/libnetwork/ipams/remote/api"
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/runconfig"
testdaemon "github.com/docker/docker/testutil/daemon"
"github.com/docker/libnetwork/driverapi"
remoteapi "github.com/docker/libnetwork/drivers/remote/api"
"github.com/docker/libnetwork/ipamapi"
remoteipam "github.com/docker/libnetwork/ipams/remote/api"
"github.com/docker/libnetwork/netlabel"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
"gotest.tools/v3/assert"

View file

@ -27,13 +27,13 @@ import (
"github.com/docker/docker/client"
"github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/integration-cli/cli/build"
"github.com/docker/docker/libnetwork/resolvconf"
"github.com/docker/docker/libnetwork/types"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/testutil"
"github.com/docker/docker/testutil/fakecontext"
"github.com/docker/go-connections/nat"
"github.com/docker/libnetwork/resolvconf"
"github.com/docker/libnetwork/types"
"github.com/moby/sys/mountinfo"
"gotest.tools/v3/assert"
"gotest.tools/v3/icmd"

View file

@ -24,9 +24,9 @@ import (
"github.com/docker/docker/integration-cli/checker"
"github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/integration-cli/daemon"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/ipamapi"
remoteipam "github.com/docker/libnetwork/ipams/remote/api"
"github.com/docker/docker/libnetwork/driverapi"
"github.com/docker/docker/libnetwork/ipamapi"
remoteipam "github.com/docker/docker/libnetwork/ipams/remote/api"
"github.com/docker/swarmkit/ca/keyutils"
"github.com/vishvananda/netlink"
"gotest.tools/v3/assert"

View file

@ -569,7 +569,7 @@ func (c *client) CreateCheckpoint(ctx context.Context, containerID, checkpointDi
var cpDesc *v1.Descriptor
for _, m := range index.Manifests {
if m.MediaType == images.MediaTypeContainerd1Checkpoint {
cpDesc = &m
cpDesc = &m // nolint:gosec
break
}
}

View file

@ -0,0 +1,79 @@
version: 2
defaults: &defaults
working_directory: ~/go/src/github.com/docker/libnetwork
docker:
# the following image is irrelevant for the build, everything is built inside a container, check the Makefile
- image: 'circleci/golang:latest'
environment:
dockerbuildargs: .
dockerargs: --privileged -e CIRCLECI
jobs:
builder:
<<: *defaults
steps:
- checkout
- setup_remote_docker:
version: 19.03.12
reusable: true
exclusive: false
- run: make builder
build:
<<: *defaults
steps:
- checkout
- setup_remote_docker:
version: 19.03.12
reusable: true
exclusive: false
- run: make build
check:
<<: *defaults
steps:
- checkout
- setup_remote_docker:
version: 19.03.12
reusable: true
exclusive: false
- run: make check
cross:
<<: *defaults
steps:
- checkout
- setup_remote_docker:
version: 19.03.12
reusable: true
exclusive: false
- run: make cross
unit-tests:
<<: *defaults
steps:
- checkout
- setup_remote_docker:
version: 19.03.12
reusable: true
exclusive: false
- run: make unit-tests
workflows:
version: 2
ci:
jobs:
- builder
- build:
requires:
- builder
- check:
requires:
- builder
- cross:
requires:
- builder
- unit-tests:
requires:
- builder

5
libnetwork/.dockerignore Normal file
View file

@ -0,0 +1,5 @@
.git
.dockerignore
Dockerfile
bin
tags

45
libnetwork/.gitignore vendored Normal file
View file

@ -0,0 +1,45 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
*~
.gtm
bin/
tags
.DS_Store
# Folders
integration-tmp/
_obj
_test
.vagrant
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
cmd/dnet/dnet
# Coverage
*.tmp
*.coverprofile
# IDE files and folders
.project
.settings/
libnetworkbuild.created
test/networkDb/testMain
test/networkDb/gossipdb

199
libnetwork/CHANGELOG.md Normal file
View file

@ -0,0 +1,199 @@
# Changelog
## 0.8.0-dev.2 (2016-05-07)
- Fix an issue which may arise during sandbox cleanup (https://github.com/docker/libnetwork/pull/1157)
- Fix cleanup logic in case of ipv6 allocation failure
- Don't add /etc/hosts record if container's ip is empty (--net=none)
- Fix default gw logic for internal networks
- Error when updating IPv6 gateway (https://github.com/docker/libnetwork/issues/1142)
- Fixes https://github.com/docker/libnetwork/issues/1113
- Fixes https://github.com/docker/libnetwork/issues/1069
- Fxies https://github.com/docker/libnetwork/issues/1117
- Increase the concurrent query rate-limit count
- Changes to build libnetwork in Solaris
## 0.8.0-dev.1 (2016-04-16)
- Fixes docker/docker#16964
- Added maximum egress bandwidth qos for Windows
## 0.7.0-rc.6 (2016-04-10)
- Flush cached resolver socket on default gateway change
## 0.7.0-rc.5 (2016-04-08)
- Persist ipam driver options
- Fixes https://github.com/docker/libnetwork/issues/1087
- Use go vet from go tool
- Godep update to pick up latest docker/docker packages
- Validate remote driver response using docker plugins package method.
## 0.7.0-rc.4 (2016-04-06)
- Fix the handling for default gateway Endpoint join/leave.
## 0.7.0-rc.3 (2016-04-05)
- Revert fix for default gateway endpoint join/leave. Needs to be reworked.
- Persist the network internal mode for bridge networks
## 0.7.0-rc.2 (2016-04-05)
- Fixes https://github.com/docker/libnetwork/issues/1070
- Move IPAM resource initialization out of init()
- Initialize overlay driver before network delete
- Fix the handling for default gateway Endpoint join/lean
## 0.7.0-rc.1 (2016-03-30)
- Fixes https://github.com/docker/libnetwork/issues/985
- Fixes https://github.com/docker/libnetwork/issues/945
- Log time taken to set sandbox key
- Limit number of concurrent DNS queries
## 0.7.0-dev.10 (2016-03-21)
- Add IPv6 service discovery (AAAA records) in embedded DNS server
- Honor enableIPv6 flag in network create for the IP allocation
- Avoid V6 queries in docker domain going to external nameservers
## 0.7.0-dev.9 (2016-03-18)
- Support labels on networks
## 0.7.0-dev.8 (2016-03-16)
- Windows driver to respect user set MAC address.
- Fix possible nil pointer reference in ServeDNS() with concurrent go routines.
- Fix netns path setting from hook (for containerd integration)
- Clear cached udp connections on resolver Stop()
- Avoid network/endpoint count inconsistences and remove stale networks after ungraceful shutdown
- Fix possible endpoint count inconsistency after ungraceful shutdown
- Reject a null v4 IPAM slice in exp vlan drivers
- Removed experimental drivers modprobe check
## 0.7.0-dev.7 (2016-03-11)
- Bumped up the minimum kernel version for ipvlan to 4.2
- Removed modprobe from macvlan/ipvlan drivers to resolve docker IT failures
- Close dbus connection if firewalld is not started
## 0.7.0-dev.6 (2016-03-10)
- Experimental support for macvlan and ipvlan drivers
## 0.7.0-dev.5 (2016-03-08)
- Fixes https://github.com/docker/docker/issues/20847
- Fixes https://github.com/docker/docker/issues/20997
- Fixes issues unveiled by docker integ test over 0.7.0-dev.4
## 0.7.0-dev.4 (2016-03-07)
- Changed ownership of exposed ports and port-mapping options from Endpoint to Sandbox
- Implement DNS RR in the Docker embedded DNS server
- Fixes https://github.com/docker/libnetwork/issues/984 (multi container overlay veth leak)
- Libnetwork to program container's interface MAC address
- Fixed bug in iptables.Exists() logic
- Fixes https://github.com/docker/docker/issues/20694
- Source external DNS queries from container namespace
- Added inbuilt nil IPAM driver
- Windows drivers integration fixes
- Extract hostname from (hostname.domainname). Related to https://github.com/docker/docker/issues/14282
- Fixed race in sandbox statistics read
- Fixes https://github.com/docker/libnetwork/issues/892 (docker start fails when ipv6.disable=1)
- Fixed error message on bridge network creation conflict
## 0.7.0-dev.3 (2016-02-17)
- Fixes https://github.com/docker/docker/issues/20350
- Fixes https://github.com/docker/docker/issues/20145
- Initial Windows HNS integration
- Allow passing global datastore config to libnetwork after boot
- Set Recursion Available bit in DNS query responses
- Make sure iptables chains are recreated on firewalld reload
## 0.7.0-dev.2 (2016-02-11)
- Fixes https://github.com/docker/docker/issues/20140
## 0.7.0-dev.1 (2016-02-10)
- Expose EnableIPV6 option
- discoverapi refactoring
- Fixed a few typos & docs update
## 0.6.1-rc2 (2016-02-09)
- Fixes https://github.com/docker/docker/issues/20132
- Fixes https://github.com/docker/docker/issues/20140
- Fixes https://github.com/docker/docker/issues/20019
## 0.6.1-rc1 (2016-02-05)
- Fixes https://github.com/docker/docker/issues/20026
## 0.6.0-rc7 (2016-02-01)
- Allow inter-network connections via exposed ports
## 0.6.0-rc6 (2016-01-30)
- Properly fixes https://github.com/docker/docker/issues/18814
## 0.6.0-rc5 (2016-01-26)
- Cleanup stale overlay sandboxes
## 0.6.0-rc4 (2016-01-25)
- Add Endpoints() API to Sandbox interface
- Fixed a race-condition in default gateway network creation
## 0.6.0-rc3 (2016-01-25)
- Fixes docker/docker#19576
- Fixed embedded DNS to listen in TCP as well
- Fixed a race-condition in IPAM to choose non-overlapping subnet for concurrent requests
## 0.6.0-rc2 (2016-01-21)
- Fixes docker/docker#19376
- Fixes docker/docker#15819
- Fixes libnetwork/#885, Not filter v6 DNS servers from resolv.conf
- Fixes docker/docker #19448, also handles the . in service and network names correctly.
## 0.6.0-rc1 (2016-01-14)
- Fixes docker/docker#19404
- Fixes the ungraceful daemon restart issue in systemd with remote network plugin
(https://github.com/docker/libnetwork/issues/813)
## 0.5.6 (2016-01-14)
- Setup embedded DNS server correctly on container restart. Fixes docker/docker#19354
## 0.5.5 (2016-01-14)
- Allow network-scoped alias to be resolved for anonymous endpoint
- Self repair corrupted IP database that could happen in 1.9.0 & 1.9.1
- Skip IPTables cleanup if --iptables=false is set. Fixes docker/docker#19063
## 0.5.4 (2016-01-12)
- Removed the isNodeAlive protection when user forces an endpoint delete
## 0.5.3 (2016-01-12)
- Bridge driver supporting internal network option
- Backend implementation to support "force" option to network disconnect
- Fixing a regex in etchosts package to fix docker/docker#19080
## 0.5.2 (2016-01-08)
- Embedded DNS replacing /etc/hosts based Service Discovery
- Container local alias and Network-scoped alias support
- Backend support for internal network mode
- Support for IPAM driver options
- Fixes overlay veth cleanup issue : docker/docker#18814
- fixes docker/docker#19139
- disable IPv6 Duplicate Address Detection
## 0.5.1 (2015-12-07)
- Allowing user to assign IP Address for containers
- Fixes docker/docker#18214
- Fixes docker/docker#18380
## 0.5.0 (2015-10-30)
- Docker multi-host networking exiting experimental channel
- Introduced IP Address Management and IPAM drivers
- DEPRECATE service discovery from default bridge network
- Introduced new network UX
- Support for multiple networks in bridge driver
- Local persistence with boltdb
## 0.4.0 (2015-07-24)
- Introduce experimental version of Overlay driver
- Introduce experimental version of network plugins
- Introduce experimental version of network & service UX
- Introduced experimental /etc/hosts based service discovery
- Integrated with libkv
- Improving test coverage
- Fixed a bunch of issues with osl namespace mgmt
## 0.3.0 (2015-05-27)
- Introduce CNM (Container Networking Model)
- Replace docker networking with CNM & Bridge driver

22
libnetwork/Dockerfile Normal file
View file

@ -0,0 +1,22 @@
ARG GO_VERSION=1.13.8
FROM golang:${GO_VERSION}-buster as dev
RUN apt-get update && apt-get -y install iptables \
protobuf-compiler
RUN go get -d github.com/gogo/protobuf/protoc-gen-gogo && \
cd /go/src/github.com/gogo/protobuf/protoc-gen-gogo && \
git reset --hard 30cf7ac33676b5786e78c746683f0d4cd64fa75b && \
go install
RUN go get golang.org/x/lint/golint \
golang.org/x/tools/cmd/cover \
github.com/mattn/goveralls \
github.com/gordonklaus/ineffassign \
github.com/client9/misspell/cmd/misspell
WORKDIR /go/src/github.com/docker/libnetwork
FROM dev
COPY . .

52
libnetwork/MAINTAINERS Normal file
View file

@ -0,0 +1,52 @@
# Libnetwork maintainers file
#
# This file describes who runs the docker/libnetwork project and how.
# This is a living document - if you see something out of date or missing, speak up!
#
# It is structured to be consumable by both humans and programs.
# To extract its contents programmatically, use any TOML-compliant parser.
#
# This file is compiled into the MAINTAINERS file in docker/opensource.
#
[Org]
[Org."Core maintainers"]
people = [
"arkodg",
"euanh",
"fcrisciani",
"mavenugo",
"selansen",
]
[people]
# A reference list of all people associated with the project.
# All other sections should refer to people by their canonical key
# in the people section.
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
[people.arkodg]
Name = "Arko Dasgupta"
Email = "arko.dasgupta@docker.com"
GitHub = "arkodg"
[people.euanh]
Name = "Euan Harris"
Email = "euan.harris@docker.com"
GitHub = "euanh"
[people.fcrisciani]
Name = "Flavio Crisciani"
Email = "flavio.crisciani@docker.com"
GitHub = "fcrisciani"
[people.mavenugo]
Name = "Madhu Venugopal"
Email = "madhu@docker.com"
GitHub = "mavenugo"
[people.selansen]
Name = "Elangovan Sivanandam"
Email = "elango.siva@docker.com"
GitHub = "selansen"

178
libnetwork/Makefile Normal file
View file

@ -0,0 +1,178 @@
.PHONY: all all-local build build-local clean cross cross-local vet lint misspell check check-local check-code check-format unit-tests protobuf protobuf-local check-protobuf
SHELL=/bin/bash
dockerbuildargs ?= --target dev - < Dockerfile
dockerargs ?= --privileged -v $(shell pwd):/go/src/github.com/docker/libnetwork -w /go/src/github.com/docker/libnetwork
build_image=libnetworkbuild
container_env = -e "INSIDECONTAINER=-incontainer=true"
docker = docker run --rm -it --init ${dockerargs} $$EXTRA_ARGS ${container_env} ${build_image}
CROSS_PLATFORMS = linux/amd64 linux/386 linux/arm windows/amd64
PACKAGES=$(shell go list ./... | grep -v /vendor/)
PROTOC_FLAGS=-I=. -I=/go/src -I=/go/src/github.com/gogo/protobuf -I=/go/src/github.com/gogo/protobuf/protobuf
export PATH := $(CURDIR)/bin:$(PATH)
# Several targets in this Makefile expect to run within the
# libnetworkbuild container. In general, a target named '<target>-local'
# relies on utilities inside the build container. Usually there is also
# a wrapper called '<target>' which starts a container and runs
# 'make <target>-local' inside it.
###########################################################################
# Top level targets
###########################################################################
all: build check clean
all-local: build-local check-local clean
###########################################################################
# Build targets
###########################################################################
# builder builds the libnetworkbuild container. All wrapper targets
# must depend on this to ensure that the container exists.
builder:
DOCKER_BUILDKIT=1 docker build --progress=plain -t ${build_image} --build-arg=GO_VERSION ${dockerbuildargs}
build: builder
@echo "🐳 $@"
@${docker} make build-local
build-local:
@echo "🐳 $@"
@mkdir -p "bin"
go build -tags experimental -o "bin/dnet" ./cmd/dnet
go build -o "bin/docker-proxy" ./cmd/proxy
CGO_ENABLED=0 go build -o "bin/diagnosticClient" ./cmd/diagnostic
CGO_ENABLED=0 go build -o "bin/testMain" ./cmd/networkdb-test/testMain.go
build-images:
@echo "🐳 $@"
cp cmd/diagnostic/daemon.json ./bin
DOCKER_BUILDKIT=1 docker build --progress=plain -f cmd/diagnostic/Dockerfile.client -t dockereng/network-diagnostic:onlyclient bin/
DOCKER_BUILDKIT=1 docker build --progress=plain -f cmd/diagnostic/Dockerfile.dind -t dockereng/network-diagnostic:17.12-dind bin/
DOCKER_BUILDKIT=1 docker build --progress=plain -f cmd/networkdb-test/Dockerfile -t dockereng/e2e-networkdb:master bin/
DOCKER_BUILDKIT=1 docker build --progress=plain -t dockereng/network-diagnostic:support.sh support/
push-images: build-images
@echo "🐳 $@"
docker push dockereng/network-diagnostic:onlyclient
docker push dockereng/network-diagnostic:17.12-dind
docker push dockereng/e2e-networkdb:master
docker push dockereng/network-diagnostic:support.sh
clean:
@echo "🐳 $@"
@if [ -d bin ]; then \
echo "Removing binaries"; \
rm -rf bin; \
fi
cross: builder
@mkdir -p "bin"
@for platform in ${CROSS_PLATFORMS}; do \
EXTRA_ARGS="-e GOOS=$${platform%/*} -e GOARCH=$${platform##*/}" ; \
echo "$${platform}..." ; \
${docker} make cross-local ; \
done
cross-local:
@echo "🐳 $@"
go build -o "bin/dnet-$$GOOS-$$GOARCH" ./cmd/dnet
go build -o "bin/docker-proxy-$$GOOS-$$GOARCH" ./cmd/proxy
# Rebuild protocol buffers.
# These may need to be rebuilt after vendoring updates, so .proto files are declared .PHONY so they are always rebuilt.
PROTO_FILES=$(shell find . -path ./vendor -prune -o -name \*.proto -print)
PB_FILES=$(PROTO_FILES:.proto=.pb.go)
# Pattern rule for protoc. If PROTOC_CHECK is defined, it checks
# whether the generated files are up to date and fails if they are not
%.pb.go: %.proto
@if [ ${PROTOC_CHECK} ]; then \
protoc ${PROTOC_FLAGS} --gogo_out=/tmp $< ; \
diff -q $@ /tmp/$@ >/dev/null || (echo "👹 $@ is out of date; please run 'make protobuf' and check in updates" && exit 1) ; \
else \
protoc ${PROTOC_FLAGS} --gogo_out=./ $< ; \
fi
.PHONY: $(PROTO_FILES)
protobuf: builder
@${docker} make protobuf-local
protobuf-local: $(PB_FILES)
###########################################################################
# Test targets
###########################################################################
check: builder
@${docker} make check-local
check-local: check-code check-format
check-code: check-protobuf lint vet ineffassign
check-format: fmt misspell
unit-tests: builder
${docker} make unit-tests-local
unit-tests-local:
@echo "🐳 Running tests... "
@echo "mode: count" > coverage.coverprofile
@go build -o "bin/docker-proxy" ./cmd/proxy
@for dir in $$( find . -maxdepth 10 -not -path './.git*' -not -path '*/_*' -not -path './vendor/*' -type d); do \
if ls $$dir/*.go &> /dev/null; then \
pushd . &> /dev/null ; \
cd $$dir ; \
go test ${INSIDECONTAINER} -test.parallel 5 -test.v -covermode=count -coverprofile=./profile.tmp ; \
ret=$$? ;\
if [ $$ret -ne 0 ]; then exit $$ret; fi ;\
popd &> /dev/null; \
if [ -f $$dir/profile.tmp ]; then \
cat $$dir/profile.tmp | tail -n +2 >> coverage.coverprofile ; \
rm $$dir/profile.tmp ; \
fi ; \
fi ; \
done
@echo "Done running tests"
# Depends on binaries because vet will silently fail if it can not load compiled imports
vet: ## run go vet
@echo "🐳 $@"
@test -z "$$(go vet ${PACKAGES} 2>&1 | grep -v 'constant [0-9]* not a string in call to Errorf' | egrep -v '(timestamp_test.go|duration_test.go|exit status 1)' | tee /dev/stderr)"
misspell:
@echo "🐳 $@"
@test -z "$$(find . -type f | grep -v vendor/ | grep "\.go\|\.md" | xargs misspell -error | tee /dev/stderr)"
fmt: ## run go fmt
@echo "🐳 $@"
@test -z "$$(gofmt -s -l . | grep -v vendor/ | grep -v ".pb.go$$" | tee /dev/stderr)" || \
(echo "👹 please format Go code with 'gofmt -s -w'" && false)
lint: ## run go lint
@echo "🐳 $@"
@test -z "$$(golint ./... | grep -v vendor/ | grep -v ".pb.go:" | grep -v ".mock.go" | tee /dev/stderr)"
ineffassign: ## run ineffassign
@echo "🐳 $@"
@test -z "$$(ineffassign . | grep -v vendor/ | grep -v ".pb.go:" | grep -v ".mock.go" | tee /dev/stderr)"
# check-protobuf rebuilds .pb.go files and fails if they have changed
check-protobuf: PROTOC_CHECK=1
check-protobuf: $(PB_FILES)
@echo "🐳 $@"
###########################################################################
# Utility targets
###########################################################################
shell: builder
@${docker} ${SHELL}

58
libnetwork/Vagrantfile vendored Normal file
View file

@ -0,0 +1,58 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
$consul=<<SCRIPT
apt-get update
apt-get -y install wget
wget -qO- https://experimental.docker.com/ | sh
gpasswd -a vagrant docker
service docker restart
docker run -d -p 8500:8500 -p 8300-8302:8300-8302/tcp -p 8300-8302:8300-8302/udp -h consul progrium/consul -server -bootstrap
SCRIPT
$bootstrap=<<SCRIPT
apt-get update
apt-get -y install wget curl
apt-get -y install bridge-utils
wget -qO- https://experimental.docker.com/ | sh
gpasswd -a vagrant docker
echo DOCKER_OPTS=\\"--cluster-store=consul://192.168.33.10:8500 --cluster-advertise=${1}:0\\" >> /etc/default/docker
cp /vagrant/docs/vagrant-systemd/docker.service /etc/systemd/system/
systemctl daemon-reload
systemctl restart docker.service
SCRIPT
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.ssh.shell = "bash -c 'BASH_ENV=/etc/profile exec bash'"
num_nodes = 2
base_ip = "192.168.33."
net_ips = num_nodes.times.collect { |n| base_ip + "#{n+11}" }
config.vm.define "consul-server" do |consul|
consul.vm.box = "ubuntu/trusty64"
consul.vm.hostname = "consul-server"
consul.vm.network :private_network, ip: "192.168.33.10"
consul.vm.provider "virtualbox" do |vb|
vb.customize ["modifyvm", :id, "--memory", "512"]
end
consul.vm.provision :shell, inline: $consul
end
num_nodes.times do |n|
config.vm.define "net-#{n+1}" do |net|
net.vm.box = "ubuntu/xenial64"
net_ip = net_ips[n]
net_index = n+1
net.vm.hostname = "net-#{net_index}"
net.vm.provider "virtualbox" do |vb|
vb.customize ["modifyvm", :id, "--memory", "1024"]
end
net.vm.network :private_network, ip: "#{net_ip}"
net.vm.provision :shell, inline: $bootstrap, :args => "#{net_ip}"
end
end
end

View file

@ -1,6 +1,6 @@
package libnetwork
//go:generate protoc -I.:Godeps/_workspace/src/github.com/gogo/protobuf --gogo_out=import_path=github.com/docker/libnetwork,Mgogoproto/gogo.proto=github.com/gogo/protobuf/gogoproto:. agent.proto
//go:generate protoc -I.:Godeps/_workspace/src/github.com/gogo/protobuf --gogo_out=import_path=github.com/docker/docker/libnetwork,Mgogoproto/gogo.proto=github.com/gogo/protobuf/gogoproto:. agent.proto
import (
"encoding/json"
@ -9,13 +9,13 @@ import (
"sort"
"sync"
"github.com/docker/docker/libnetwork/cluster"
"github.com/docker/docker/libnetwork/datastore"
"github.com/docker/docker/libnetwork/discoverapi"
"github.com/docker/docker/libnetwork/driverapi"
"github.com/docker/docker/libnetwork/networkdb"
"github.com/docker/docker/libnetwork/types"
"github.com/docker/go-events"
"github.com/docker/libnetwork/cluster"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/discoverapi"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/networkdb"
"github.com/docker/libnetwork/types"
"github.com/gogo/protobuf/proto"
"github.com/sirupsen/logrus"
)
@ -793,7 +793,7 @@ func (n *network) addDriverWatches() {
return
}
agent.networkDB.WalkTable(table.name, func(nid, key string, value []byte, deleted bool) bool {
err = agent.networkDB.WalkTable(table.name, func(nid, key string, value []byte, deleted bool) bool {
// skip the entries that are mark for deletion, this is safe because this function is
// called at initialization time so there is no state to delete
if nid == n.ID() && !deleted {
@ -801,6 +801,9 @@ func (n *network) addDriverWatches() {
}
return false
})
if err != nil {
logrus.WithError(err).Warn("Error while walking networkdb")
}
}
}

973
libnetwork/api/api.go Normal file
View file

@ -0,0 +1,973 @@
package api
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/libnetwork/netutils"
"github.com/docker/docker/libnetwork/types"
"github.com/gorilla/mux"
)
var (
successResponse = responseStatus{Status: "Success", StatusCode: http.StatusOK}
createdResponse = responseStatus{Status: "Created", StatusCode: http.StatusCreated}
badQueryResponse = responseStatus{Status: "Unsupported query", StatusCode: http.StatusBadRequest}
)
const (
// Resource name regex
// Gorilla mux encloses the passed pattern with '^' and '$'. So we need to do some tricks
// to have mux eventually build a query regex which matches empty or word string (`^$|[\w]+`)
regex = "[a-zA-Z_0-9-]+"
qregx = "$|" + regex
// Router URL variable definition
nwNameQr = "{" + urlNwName + ":" + qregx + "}"
nwID = "{" + urlNwID + ":" + regex + "}"
nwPIDQr = "{" + urlNwPID + ":" + qregx + "}"
epNameQr = "{" + urlEpName + ":" + qregx + "}"
epID = "{" + urlEpID + ":" + regex + "}"
epPIDQr = "{" + urlEpPID + ":" + qregx + "}"
sbID = "{" + urlSbID + ":" + regex + "}"
sbPIDQr = "{" + urlSbPID + ":" + qregx + "}"
cnIDQr = "{" + urlCnID + ":" + qregx + "}"
cnPIDQr = "{" + urlCnPID + ":" + qregx + "}"
// Internal URL variable name.They can be anything as
// long as they do not collide with query fields.
urlNwName = "network-name"
urlNwID = "network-id"
urlNwPID = "network-partial-id"
urlEpName = "endpoint-name"
urlEpID = "endpoint-id"
urlEpPID = "endpoint-partial-id"
urlSbID = "sandbox-id"
urlSbPID = "sandbox-partial-id"
urlCnID = "container-id"
urlCnPID = "container-partial-id"
)
// NewHTTPHandler creates and initialize the HTTP handler to serve the requests for libnetwork
func NewHTTPHandler(c libnetwork.NetworkController) func(w http.ResponseWriter, req *http.Request) {
h := &httpHandler{c: c}
h.initRouter()
return h.handleRequest
}
type responseStatus struct {
Status string
StatusCode int
}
func (r *responseStatus) isOK() bool {
return r.StatusCode == http.StatusOK || r.StatusCode == http.StatusCreated
}
type processor func(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus)
type httpHandler struct {
c libnetwork.NetworkController
r *mux.Router
}
func (h *httpHandler) handleRequest(w http.ResponseWriter, req *http.Request) {
// Make sure the service is there
if h.c == nil {
http.Error(w, "NetworkController is not available", http.StatusServiceUnavailable)
return
}
// Get handler from router and execute it
h.r.ServeHTTP(w, req)
}
func (h *httpHandler) initRouter() {
m := map[string][]struct {
url string
qrs []string
fct processor
}{
"GET": {
// Order matters
{"/networks", []string{"name", nwNameQr}, procGetNetworks},
{"/networks", []string{"partial-id", nwPIDQr}, procGetNetworks},
{"/networks", nil, procGetNetworks},
{"/networks/" + nwID, nil, procGetNetwork},
{"/networks/" + nwID + "/endpoints", []string{"name", epNameQr}, procGetEndpoints},
{"/networks/" + nwID + "/endpoints", []string{"partial-id", epPIDQr}, procGetEndpoints},
{"/networks/" + nwID + "/endpoints", nil, procGetEndpoints},
{"/networks/" + nwID + "/endpoints/" + epID, nil, procGetEndpoint},
{"/services", []string{"network", nwNameQr}, procGetServices},
{"/services", []string{"name", epNameQr}, procGetServices},
{"/services", []string{"partial-id", epPIDQr}, procGetServices},
{"/services", nil, procGetServices},
{"/services/" + epID, nil, procGetService},
{"/services/" + epID + "/backend", nil, procGetSandbox},
{"/sandboxes", []string{"partial-container-id", cnPIDQr}, procGetSandboxes},
{"/sandboxes", []string{"container-id", cnIDQr}, procGetSandboxes},
{"/sandboxes", []string{"partial-id", sbPIDQr}, procGetSandboxes},
{"/sandboxes", nil, procGetSandboxes},
{"/sandboxes/" + sbID, nil, procGetSandbox},
},
"POST": {
{"/networks", nil, procCreateNetwork},
{"/networks/" + nwID + "/endpoints", nil, procCreateEndpoint},
{"/networks/" + nwID + "/endpoints/" + epID + "/sandboxes", nil, procJoinEndpoint},
{"/services", nil, procPublishService},
{"/services/" + epID + "/backend", nil, procAttachBackend},
{"/sandboxes", nil, procCreateSandbox},
},
"DELETE": {
{"/networks/" + nwID, nil, procDeleteNetwork},
{"/networks/" + nwID + "/endpoints/" + epID, nil, procDeleteEndpoint},
{"/networks/" + nwID + "/endpoints/" + epID + "/sandboxes/" + sbID, nil, procLeaveEndpoint},
{"/services/" + epID, nil, procUnpublishService},
{"/services/" + epID + "/backend/" + sbID, nil, procDetachBackend},
{"/sandboxes/" + sbID, nil, procDeleteSandbox},
},
}
h.r = mux.NewRouter()
for method, routes := range m {
for _, route := range routes {
r := h.r.Path("/{.*}" + route.url).Methods(method).HandlerFunc(makeHandler(h.c, route.fct))
if route.qrs != nil {
r.Queries(route.qrs...)
}
r = h.r.Path(route.url).Methods(method).HandlerFunc(makeHandler(h.c, route.fct))
if route.qrs != nil {
r.Queries(route.qrs...)
}
}
}
}
func makeHandler(ctrl libnetwork.NetworkController, fct processor) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
var (
body []byte
err error
)
if req.Body != nil {
body, err = ioutil.ReadAll(req.Body)
if err != nil {
http.Error(w, "Invalid body: "+err.Error(), http.StatusBadRequest)
return
}
}
res, rsp := fct(ctrl, mux.Vars(req), body)
if !rsp.isOK() {
http.Error(w, rsp.Status, rsp.StatusCode)
return
}
if res != nil {
writeJSON(w, rsp.StatusCode, res)
}
}
}
/*****************
Resource Builders
******************/
func buildNetworkResource(nw libnetwork.Network) *networkResource {
r := &networkResource{}
if nw != nil {
r.Name = nw.Name()
r.ID = nw.ID()
r.Type = nw.Type()
epl := nw.Endpoints()
r.Endpoints = make([]*endpointResource, 0, len(epl))
for _, e := range epl {
epr := buildEndpointResource(e)
r.Endpoints = append(r.Endpoints, epr)
}
}
return r
}
func buildEndpointResource(ep libnetwork.Endpoint) *endpointResource {
r := &endpointResource{}
if ep != nil {
r.Name = ep.Name()
r.ID = ep.ID()
r.Network = ep.Network()
}
return r
}
func buildSandboxResource(sb libnetwork.Sandbox) *sandboxResource {
r := &sandboxResource{}
if sb != nil {
r.ID = sb.ID()
r.Key = sb.Key()
r.ContainerID = sb.ContainerID()
}
return r
}
/****************
Options Parsers
*****************/
func (sc *sandboxCreate) parseOptions() []libnetwork.SandboxOption {
var setFctList []libnetwork.SandboxOption
if sc.HostName != "" {
setFctList = append(setFctList, libnetwork.OptionHostname(sc.HostName))
}
if sc.DomainName != "" {
setFctList = append(setFctList, libnetwork.OptionDomainname(sc.DomainName))
}
if sc.HostsPath != "" {
setFctList = append(setFctList, libnetwork.OptionHostsPath(sc.HostsPath))
}
if sc.ResolvConfPath != "" {
setFctList = append(setFctList, libnetwork.OptionResolvConfPath(sc.ResolvConfPath))
}
if sc.UseDefaultSandbox {
setFctList = append(setFctList, libnetwork.OptionUseDefaultSandbox())
}
if sc.UseExternalKey {
setFctList = append(setFctList, libnetwork.OptionUseExternalKey())
}
if sc.DNS != nil {
for _, d := range sc.DNS {
setFctList = append(setFctList, libnetwork.OptionDNS(d))
}
}
if sc.ExtraHosts != nil {
for _, e := range sc.ExtraHosts {
setFctList = append(setFctList, libnetwork.OptionExtraHost(e.Name, e.Address))
}
}
if sc.ExposedPorts != nil {
setFctList = append(setFctList, libnetwork.OptionExposedPorts(sc.ExposedPorts))
}
if sc.PortMapping != nil {
setFctList = append(setFctList, libnetwork.OptionPortMapping(sc.PortMapping))
}
return setFctList
}
/******************
Process functions
*******************/
func processCreateDefaults(c libnetwork.NetworkController, nc *networkCreate) {
if nc.NetworkType == "" {
nc.NetworkType = c.Config().Daemon.DefaultDriver
}
}
/***************************
NetworkController interface
****************************/
func procCreateNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var create networkCreate
err := json.Unmarshal(body, &create)
if err != nil {
return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
}
processCreateDefaults(c, &create)
options := []libnetwork.NetworkOption{}
if val, ok := create.NetworkOpts[netlabel.Internal]; ok {
internal, err := strconv.ParseBool(val)
if err != nil {
return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
}
if internal {
options = append(options, libnetwork.NetworkOptionInternalNetwork())
}
}
if val, ok := create.NetworkOpts[netlabel.EnableIPv6]; ok {
enableIPv6, err := strconv.ParseBool(val)
if err != nil {
return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
}
options = append(options, libnetwork.NetworkOptionEnableIPv6(enableIPv6))
}
if len(create.DriverOpts) > 0 {
options = append(options, libnetwork.NetworkOptionDriverOpts(create.DriverOpts))
}
if len(create.IPv4Conf) > 0 {
ipamV4Conf := &libnetwork.IpamConf{
PreferredPool: create.IPv4Conf[0].PreferredPool,
SubPool: create.IPv4Conf[0].SubPool,
}
options = append(options, libnetwork.NetworkOptionIpam("default", "", []*libnetwork.IpamConf{ipamV4Conf}, nil, nil))
}
nw, err := c.NewNetwork(create.NetworkType, create.Name, create.ID, options...)
if err != nil {
return nil, convertNetworkError(err)
}
return nw.ID(), &createdResponse
}
func procGetNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
t, by := detectNetworkTarget(vars)
nw, errRsp := findNetwork(c, t, by)
if !errRsp.isOK() {
return nil, errRsp
}
return buildNetworkResource(nw), &successResponse
}
func procGetNetworks(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var list []*networkResource
// Look for query filters and validate
name, queryByName := vars[urlNwName]
shortID, queryByPid := vars[urlNwPID]
if queryByName && queryByPid {
return nil, &badQueryResponse
}
if queryByName {
if nw, errRsp := findNetwork(c, name, byName); errRsp.isOK() {
list = append(list, buildNetworkResource(nw))
}
} else if queryByPid {
// Return all the prefix-matching networks
l := func(nw libnetwork.Network) bool {
if strings.HasPrefix(nw.ID(), shortID) {
list = append(list, buildNetworkResource(nw))
}
return false
}
c.WalkNetworks(l)
} else {
for _, nw := range c.Networks() {
list = append(list, buildNetworkResource(nw))
}
}
return list, &successResponse
}
func procCreateSandbox(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var create sandboxCreate
err := json.Unmarshal(body, &create)
if err != nil {
return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
}
sb, err := c.NewSandbox(create.ContainerID, create.parseOptions()...)
if err != nil {
return "", convertNetworkError(err)
}
return sb.ID(), &createdResponse
}
/******************
Network interface
*******************/
func procCreateEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var ec endpointCreate
err := json.Unmarshal(body, &ec)
if err != nil {
return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
}
nwT, nwBy := detectNetworkTarget(vars)
n, errRsp := findNetwork(c, nwT, nwBy)
if !errRsp.isOK() {
return "", errRsp
}
var setFctList []libnetwork.EndpointOption
for _, str := range ec.MyAliases {
setFctList = append(setFctList, libnetwork.CreateOptionMyAlias(str))
}
ep, err := n.CreateEndpoint(ec.Name, setFctList...)
if err != nil {
return "", convertNetworkError(err)
}
return ep.ID(), &createdResponse
}
func procGetEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
nwT, nwBy := detectNetworkTarget(vars)
epT, epBy := detectEndpointTarget(vars)
ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
if !errRsp.isOK() {
return nil, errRsp
}
return buildEndpointResource(ep), &successResponse
}
func procGetEndpoints(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
// Look for query filters and validate
name, queryByName := vars[urlEpName]
shortID, queryByPid := vars[urlEpPID]
if queryByName && queryByPid {
return nil, &badQueryResponse
}
nwT, nwBy := detectNetworkTarget(vars)
nw, errRsp := findNetwork(c, nwT, nwBy)
if !errRsp.isOK() {
return nil, errRsp
}
var list []*endpointResource
// If query parameter is specified, return a filtered collection
if queryByName {
if ep, errRsp := findEndpoint(c, nwT, name, nwBy, byName); errRsp.isOK() {
list = append(list, buildEndpointResource(ep))
}
} else if queryByPid {
// Return all the prefix-matching endpoints
l := func(ep libnetwork.Endpoint) bool {
if strings.HasPrefix(ep.ID(), shortID) {
list = append(list, buildEndpointResource(ep))
}
return false
}
nw.WalkEndpoints(l)
} else {
for _, ep := range nw.Endpoints() {
epr := buildEndpointResource(ep)
list = append(list, epr)
}
}
return list, &successResponse
}
func procDeleteNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
target, by := detectNetworkTarget(vars)
nw, errRsp := findNetwork(c, target, by)
if !errRsp.isOK() {
return nil, errRsp
}
err := nw.Delete()
if err != nil {
return nil, convertNetworkError(err)
}
return nil, &successResponse
}
/******************
Endpoint interface
*******************/
func procJoinEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var ej endpointJoin
var setFctList []libnetwork.EndpointOption
err := json.Unmarshal(body, &ej)
if err != nil {
return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
}
nwT, nwBy := detectNetworkTarget(vars)
epT, epBy := detectEndpointTarget(vars)
ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
if !errRsp.isOK() {
return nil, errRsp
}
sb, errRsp := findSandbox(c, ej.SandboxID, byID)
if !errRsp.isOK() {
return nil, errRsp
}
for _, str := range ej.Aliases {
name, alias, err := netutils.ParseAlias(str)
if err != nil {
return "", convertNetworkError(err)
}
setFctList = append(setFctList, libnetwork.CreateOptionAlias(name, alias))
}
err = ep.Join(sb, setFctList...)
if err != nil {
return nil, convertNetworkError(err)
}
return sb.Key(), &successResponse
}
func procLeaveEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
nwT, nwBy := detectNetworkTarget(vars)
epT, epBy := detectEndpointTarget(vars)
ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
if !errRsp.isOK() {
return nil, errRsp
}
sb, errRsp := findSandbox(c, vars[urlSbID], byID)
if !errRsp.isOK() {
return nil, errRsp
}
err := ep.Leave(sb)
if err != nil {
return nil, convertNetworkError(err)
}
return nil, &successResponse
}
func procDeleteEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
nwT, nwBy := detectNetworkTarget(vars)
epT, epBy := detectEndpointTarget(vars)
ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
if !errRsp.isOK() {
return nil, errRsp
}
err := ep.Delete(false)
if err != nil {
return nil, convertNetworkError(err)
}
return nil, &successResponse
}
/******************
Service interface
*******************/
func procGetServices(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
// Look for query filters and validate
nwName, filterByNwName := vars[urlNwName]
svName, queryBySvName := vars[urlEpName]
shortID, queryBySvPID := vars[urlEpPID]
if filterByNwName && queryBySvName || filterByNwName && queryBySvPID || queryBySvName && queryBySvPID {
return nil, &badQueryResponse
}
var list []*endpointResource
switch {
case filterByNwName:
// return all service present on the specified network
nw, errRsp := findNetwork(c, nwName, byName)
if !errRsp.isOK() {
return list, &successResponse
}
for _, ep := range nw.Endpoints() {
epr := buildEndpointResource(ep)
list = append(list, epr)
}
case queryBySvName:
// Look in each network for the service with the specified name
l := func(ep libnetwork.Endpoint) bool {
if ep.Name() == svName {
list = append(list, buildEndpointResource(ep))
return true
}
return false
}
for _, nw := range c.Networks() {
nw.WalkEndpoints(l)
}
case queryBySvPID:
// Return all the prefix-matching services
l := func(ep libnetwork.Endpoint) bool {
if strings.HasPrefix(ep.ID(), shortID) {
list = append(list, buildEndpointResource(ep))
}
return false
}
for _, nw := range c.Networks() {
nw.WalkEndpoints(l)
}
default:
for _, nw := range c.Networks() {
for _, ep := range nw.Endpoints() {
epr := buildEndpointResource(ep)
list = append(list, epr)
}
}
}
return list, &successResponse
}
func procGetService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
epT, epBy := detectEndpointTarget(vars)
sv, errRsp := findService(c, epT, epBy)
if !errRsp.isOK() {
return nil, endpointToService(errRsp)
}
return buildEndpointResource(sv), &successResponse
}
func procPublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var sp servicePublish
err := json.Unmarshal(body, &sp)
if err != nil {
return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
}
n, errRsp := findNetwork(c, sp.Network, byName)
if !errRsp.isOK() {
return "", errRsp
}
var setFctList []libnetwork.EndpointOption
for _, str := range sp.MyAliases {
setFctList = append(setFctList, libnetwork.CreateOptionMyAlias(str))
}
ep, err := n.CreateEndpoint(sp.Name, setFctList...)
if err != nil {
return "", endpointToService(convertNetworkError(err))
}
return ep.ID(), &createdResponse
}
func procUnpublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var sd serviceDelete
if body != nil {
err := json.Unmarshal(body, &sd)
if err != nil {
return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
}
}
epT, epBy := detectEndpointTarget(vars)
sv, errRsp := findService(c, epT, epBy)
if !errRsp.isOK() {
return nil, errRsp
}
if err := sv.Delete(sd.Force); err != nil {
return nil, endpointToService(convertNetworkError(err))
}
return nil, &successResponse
}
func procAttachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var bk endpointJoin
var setFctList []libnetwork.EndpointOption
err := json.Unmarshal(body, &bk)
if err != nil {
return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
}
epT, epBy := detectEndpointTarget(vars)
sv, errRsp := findService(c, epT, epBy)
if !errRsp.isOK() {
return nil, errRsp
}
sb, errRsp := findSandbox(c, bk.SandboxID, byID)
if !errRsp.isOK() {
return nil, errRsp
}
for _, str := range bk.Aliases {
name, alias, err := netutils.ParseAlias(str)
if err != nil {
return "", convertNetworkError(err)
}
setFctList = append(setFctList, libnetwork.CreateOptionAlias(name, alias))
}
err = sv.Join(sb, setFctList...)
if err != nil {
return nil, convertNetworkError(err)
}
return sb.Key(), &successResponse
}
func procDetachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
epT, epBy := detectEndpointTarget(vars)
sv, errRsp := findService(c, epT, epBy)
if !errRsp.isOK() {
return nil, errRsp
}
sb, errRsp := findSandbox(c, vars[urlSbID], byID)
if !errRsp.isOK() {
return nil, errRsp
}
err := sv.Leave(sb)
if err != nil {
return nil, convertNetworkError(err)
}
return nil, &successResponse
}
/******************
Sandbox interface
*******************/
func procGetSandbox(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
if epT, ok := vars[urlEpID]; ok {
sv, errRsp := findService(c, epT, byID)
if !errRsp.isOK() {
return nil, endpointToService(errRsp)
}
return buildSandboxResource(sv.Info().Sandbox()), &successResponse
}
sbT, by := detectSandboxTarget(vars)
sb, errRsp := findSandbox(c, sbT, by)
if !errRsp.isOK() {
return nil, errRsp
}
return buildSandboxResource(sb), &successResponse
}
type cndFnMkr func(string) cndFn
type cndFn func(libnetwork.Sandbox) bool
// list of (query type, condition function makers) couples
var cndMkrList = []struct {
identifier string
maker cndFnMkr
}{
{urlSbPID, func(id string) cndFn {
return func(sb libnetwork.Sandbox) bool { return strings.HasPrefix(sb.ID(), id) }
}},
{urlCnID, func(id string) cndFn {
return func(sb libnetwork.Sandbox) bool { return sb.ContainerID() == id }
}},
{urlCnPID, func(id string) cndFn {
return func(sb libnetwork.Sandbox) bool { return strings.HasPrefix(sb.ContainerID(), id) }
}},
}
func getQueryCondition(vars map[string]string) func(libnetwork.Sandbox) bool {
for _, im := range cndMkrList {
if val, ok := vars[im.identifier]; ok {
return im.maker(val)
}
}
return func(sb libnetwork.Sandbox) bool { return true }
}
func sandboxWalker(condition cndFn, list *[]*sandboxResource) libnetwork.SandboxWalker {
return func(sb libnetwork.Sandbox) bool {
if condition(sb) {
*list = append(*list, buildSandboxResource(sb))
}
return false
}
}
func procGetSandboxes(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var list []*sandboxResource
cnd := getQueryCondition(vars)
c.WalkSandboxes(sandboxWalker(cnd, &list))
return list, &successResponse
}
func procDeleteSandbox(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
sbT, by := detectSandboxTarget(vars)
sb, errRsp := findSandbox(c, sbT, by)
if !errRsp.isOK() {
return nil, errRsp
}
err := sb.Delete()
if err != nil {
return nil, convertNetworkError(err)
}
return nil, &successResponse
}
/***********
Utilities
************/
const (
byID = iota
byName
)
func detectNetworkTarget(vars map[string]string) (string, int) {
if target, ok := vars[urlNwName]; ok {
return target, byName
}
if target, ok := vars[urlNwID]; ok {
return target, byID
}
// vars are populated from the URL, following cannot happen
panic("Missing URL variable parameter for network")
}
func detectSandboxTarget(vars map[string]string) (string, int) {
if target, ok := vars[urlSbID]; ok {
return target, byID
}
// vars are populated from the URL, following cannot happen
panic("Missing URL variable parameter for sandbox")
}
func detectEndpointTarget(vars map[string]string) (string, int) {
if target, ok := vars[urlEpName]; ok {
return target, byName
}
if target, ok := vars[urlEpID]; ok {
return target, byID
}
// vars are populated from the URL, following cannot happen
panic("Missing URL variable parameter for endpoint")
}
func findNetwork(c libnetwork.NetworkController, s string, by int) (libnetwork.Network, *responseStatus) {
var (
nw libnetwork.Network
err error
)
switch by {
case byID:
nw, err = c.NetworkByID(s)
case byName:
if s == "" {
s = c.Config().Daemon.DefaultNetwork
}
nw, err = c.NetworkByName(s)
default:
panic(fmt.Sprintf("unexpected selector for network search: %d", by))
}
if err != nil {
if _, ok := err.(types.NotFoundError); ok {
return nil, &responseStatus{Status: "Resource not found: Network", StatusCode: http.StatusNotFound}
}
return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
}
return nw, &successResponse
}
func findSandbox(c libnetwork.NetworkController, s string, by int) (libnetwork.Sandbox, *responseStatus) {
var (
sb libnetwork.Sandbox
err error
)
switch by {
case byID:
sb, err = c.SandboxByID(s)
default:
panic(fmt.Sprintf("unexpected selector for sandbox search: %d", by))
}
if err != nil {
if _, ok := err.(types.NotFoundError); ok {
return nil, &responseStatus{Status: "Resource not found: Sandbox", StatusCode: http.StatusNotFound}
}
return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
}
return sb, &successResponse
}
func findEndpoint(c libnetwork.NetworkController, ns, es string, nwBy, epBy int) (libnetwork.Endpoint, *responseStatus) {
nw, errRsp := findNetwork(c, ns, nwBy)
if !errRsp.isOK() {
return nil, errRsp
}
var (
err error
ep libnetwork.Endpoint
)
switch epBy {
case byID:
ep, err = nw.EndpointByID(es)
case byName:
ep, err = nw.EndpointByName(es)
default:
panic(fmt.Sprintf("unexpected selector for endpoint search: %d", epBy))
}
if err != nil {
if _, ok := err.(types.NotFoundError); ok {
return nil, &responseStatus{Status: "Resource not found: Endpoint", StatusCode: http.StatusNotFound}
}
return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
}
return ep, &successResponse
}
func findService(c libnetwork.NetworkController, svs string, svBy int) (libnetwork.Endpoint, *responseStatus) {
for _, nw := range c.Networks() {
var (
ep libnetwork.Endpoint
err error
)
switch svBy {
case byID:
ep, err = nw.EndpointByID(svs)
case byName:
ep, err = nw.EndpointByName(svs)
default:
panic(fmt.Sprintf("unexpected selector for service search: %d", svBy))
}
if err == nil {
return ep, &successResponse
} else if _, ok := err.(types.NotFoundError); !ok {
return nil, convertNetworkError(err)
}
}
return nil, &responseStatus{Status: "Service not found", StatusCode: http.StatusNotFound}
}
func endpointToService(rsp *responseStatus) *responseStatus {
rsp.Status = strings.Replace(rsp.Status, "endpoint", "service", -1)
return rsp
}
func convertNetworkError(err error) *responseStatus {
var code int
switch err.(type) {
case types.BadRequestError:
code = http.StatusBadRequest
case types.ForbiddenError:
code = http.StatusForbidden
case types.NotFoundError:
code = http.StatusNotFound
case types.TimeoutError:
code = http.StatusRequestTimeout
case types.NotImplementedError:
code = http.StatusNotImplemented
case types.NoServiceError:
code = http.StatusServiceUnavailable
case types.InternalError:
code = http.StatusInternalServerError
default:
code = http.StatusInternalServerError
}
return &responseStatus{Status: err.Error(), StatusCode: code}
}
func writeJSON(w http.ResponseWriter, code int, v interface{}) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
return json.NewEncoder(w).Encode(v)
}

File diff suppressed because it is too large Load diff

96
libnetwork/api/types.go Normal file
View file

@ -0,0 +1,96 @@
package api
import "github.com/docker/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"`
Endpoints []*endpointResource `json:"endpoints"`
}
// endpointResource is the body of the "get endpoint" http response message
type endpointResource 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
************/
type ipamConf struct {
PreferredPool string
SubPool string
Gateway string
AuxAddresses map[string]string
}
// networkCreate is the expected body of the "create network" http request message
type networkCreate struct {
Name string `json:"name"`
ID string `json:"id"`
NetworkType string `json:"network_type"`
IPv4Conf []ipamConf `json:"ipv4_configuration"`
DriverOpts map[string]string `json:"driver_opts"`
NetworkOpts map[string]string `json:"network_opts"`
}
// endpointCreate represents the body of the "create endpoint" http request message
type endpointCreate struct {
Name string `json:"name"`
MyAliases []string `json:"my_aliases"`
}
// sandboxCreate is the expected body of the "create sandbox" 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"`
UseExternalKey bool `json:"use_external_key"`
ExposedPorts []types.TransportPort `json:"exposed_ports"`
PortMapping []types.PortBinding `json:"port_mapping"`
}
// endpointJoin represents the expected body of the "join endpoint" or "leave endpoint" http request messages
type endpointJoin struct {
SandboxID string `json:"sandbox_id"`
Aliases []string `json:"aliases"`
}
// servicePublish represents the body of the "publish service" http request message
type servicePublish struct {
Name string `json:"name"`
MyAliases []string `json:"my_aliases"`
Network string `json:"network_name"`
}
// serviceDelete represents the body of the "unpublish service" http request message
type serviceDelete struct {
Name string `json:"name"`
Force bool `json:"force"`
}
// extraHost represents the extra host object
type extraHost struct {
Name string `json:"name"`
Address string `json:"address"`
}

View file

@ -10,8 +10,8 @@ import (
"fmt"
"sync"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/types"
"github.com/docker/docker/libnetwork/datastore"
"github.com/docker/docker/libnetwork/types"
"github.com/sirupsen/logrus"
)
@ -144,11 +144,7 @@ func (s *sequence) equal(o *sequence) bool {
this = this.next
other = other.next
}
// Check if other is longer than this
if other != nil {
return false
}
return true
return other == nil
}
// ToByteArray converts the sequence into a byte array
@ -699,7 +695,6 @@ func removeCurrentIfEmpty(head **sequence, previous, current *sequence) {
*head = current.next
} else {
previous.next = current.next
current = current.next
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -2,10 +2,9 @@ package bitseq
import (
"encoding/json"
"fmt"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/types"
"github.com/docker/docker/libnetwork/datastore"
"github.com/docker/docker/libnetwork/types"
)
// Key provides the Key to be used in KV Store
@ -106,17 +105,6 @@ func (h *Handle) DataScope() string {
return h.store.Scope()
}
func (h *Handle) fromDsValue(value []byte) error {
var ba []byte
if err := json.Unmarshal(value, &ba); err != nil {
return fmt.Errorf("failed to decode json: %s", err.Error())
}
if err := h.FromByteArray(ba); err != nil {
return fmt.Errorf("failed to decode handle: %s", err.Error())
}
return nil
}
func (h *Handle) writeToStore() error {
h.Lock()
store := h.store

115
libnetwork/client/client.go Normal file
View file

@ -0,0 +1,115 @@
package client
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"reflect"
"strings"
flag "github.com/docker/docker/libnetwork/client/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'", 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

@ -0,0 +1,122 @@
package client
import (
"bytes"
"testing"
_ "github.com/docker/docker/libnetwork/testutils"
)
func TestClientServiceInvalidCommand(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "invalid")
if err == nil {
t.Fatal("Passing invalid commands must fail")
}
}
func TestClientServiceCreate(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "publish", mockServiceName+"."+mockNwName)
if err != nil {
t.Fatal(err)
}
}
func TestClientServiceRm(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "unpublish", mockServiceName+"."+mockNwName)
if err != nil {
t.Fatal(err)
}
}
func TestClientServiceLs(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "ls")
if err != nil {
t.Fatal(err)
}
}
func TestClientServiceInfo(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "info", mockServiceName+"."+mockNwName)
if err != nil {
t.Fatal(err)
}
}
func TestClientServiceInfoById(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "info", mockServiceID+"."+mockNwName)
if err != nil {
t.Fatal(err)
}
}
func TestClientServiceJoin(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "attach", mockContainerID, mockServiceName+"."+mockNwName)
if err != nil {
t.Fatal(err)
}
}
func TestClientServiceLeave(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "detach", mockContainerID, mockServiceName+"."+mockNwName)
if err != nil {
t.Fatal(err)
}
}
// Docker Flag processing in flag.go uses os.Exit() frequently, even for --help
// TODO : Handle the --help test-case in the IT when CLI is available
/*
func TestClientNetworkServiceCreateHelp(t *testing.T) {
var out, errOut bytes.Buffer
cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
return nil, 0, nil
}
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "create", "--help")
if err != nil {
t.Fatalf(err.Error())
}
}
*/
// Docker flag processing in flag.go uses os.Exit(1) for incorrect parameter case.
// TODO : Handle the missing argument case in the IT when CLI is available
/*
func TestClientNetworkServiceCreateMissingArgument(t *testing.T) {
var out, errOut bytes.Buffer
cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
return nil, 0, nil
}
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "create")
if err != nil {
t.Fatal(err.Error())
}
}
*/

View file

@ -0,0 +1,228 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"testing"
_ "github.com/docker/docker/libnetwork/testutils"
)
// nopCloser is used to provide a dummy CallFunc for Cmd()
type nopCloser struct {
io.Reader
}
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, http.Header, int, error)
var mockNwJSON, mockNwListJSON, mockServiceJSON, mockServiceListJSON, mockSbJSON, mockSbListJSON []byte
var mockNwName = "test"
var mockNwID = "2a3456789"
var mockServiceName = "testSrv"
var mockServiceID = "2a3456789"
var mockContainerID = "2a3456789"
var mockSandboxID = "2b3456789"
func setupMockHTTPCallback() {
var list []networkResource
nw := networkResource{Name: mockNwName, ID: mockNwID}
mockNwJSON, _ = json.Marshal(nw)
list = append(list, nw)
mockNwListJSON, _ = json.Marshal(list)
var srvList []serviceResource
ep := serviceResource{Name: mockServiceName, ID: mockServiceID, Network: mockNwName}
mockServiceJSON, _ = json.Marshal(ep)
srvList = append(srvList, ep)
mockServiceListJSON, _ = json.Marshal(srvList)
var sbxList []SandboxResource
sb := SandboxResource{ID: mockSandboxID, ContainerID: mockContainerID}
mockSbJSON, _ = json.Marshal(sb)
sbxList = append(sbxList, sb)
mockSbListJSON, _ = json.Marshal(sbxList)
dummyHTTPHdr := http.Header{}
callbackFunc = func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
var rsp string
switch method {
case "GET":
if strings.Contains(path, fmt.Sprintf("networks?name=%s", mockNwName)) {
rsp = string(mockNwListJSON)
} else if strings.Contains(path, "networks?name=") {
rsp = "[]"
} else if strings.Contains(path, fmt.Sprintf("networks?partial-id=%s", mockNwID)) {
rsp = string(mockNwListJSON)
} else if strings.Contains(path, "networks?partial-id=") {
rsp = "[]"
} else if strings.HasSuffix(path, "networks") {
rsp = string(mockNwListJSON)
} else if strings.HasSuffix(path, "networks/"+mockNwID) {
rsp = string(mockNwJSON)
} else if strings.Contains(path, fmt.Sprintf("services?name=%s", mockServiceName)) {
rsp = string(mockServiceListJSON)
} else if strings.Contains(path, "services?name=") {
rsp = "[]"
} else if strings.Contains(path, fmt.Sprintf("services?partial-id=%s", mockServiceID)) {
rsp = string(mockServiceListJSON)
} else if strings.Contains(path, "services?partial-id=") {
rsp = "[]"
} else if strings.HasSuffix(path, "services") {
rsp = string(mockServiceListJSON)
} else if strings.HasSuffix(path, "services/"+mockServiceID) {
rsp = string(mockServiceJSON)
} else if strings.Contains(path, "containers") {
return nopCloser{bytes.NewBufferString("")}, dummyHTTPHdr, 400, fmt.Errorf("Bad Request")
} else if strings.Contains(path, fmt.Sprintf("sandboxes?container-id=%s", mockContainerID)) {
rsp = string(mockSbListJSON)
} else if strings.Contains(path, fmt.Sprintf("sandboxes?partial-container-id=%s", mockContainerID)) {
rsp = string(mockSbListJSON)
}
case "POST":
var data []byte
if strings.HasSuffix(path, "networks") {
data, _ = json.Marshal(mockNwID)
} else if strings.HasSuffix(path, "services") {
data, _ = json.Marshal(mockServiceID)
} else if strings.HasSuffix(path, "backend") {
data, _ = json.Marshal(mockSandboxID)
}
rsp = string(data)
case "PUT":
case "DELETE":
rsp = ""
}
return nopCloser{bytes.NewBufferString(rsp)}, dummyHTTPHdr, 200, nil
}
}
func TestClientDummyCommand(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "dummy")
if err == nil {
t.Fatal("Incorrect Command must fail")
}
}
func TestClientNetworkInvalidCommand(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "invalid")
if err == nil {
t.Fatal("Passing invalid commands must fail")
}
}
func TestClientNetworkCreate(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "create", mockNwName)
if err != nil {
t.Fatal(err.Error())
}
}
func TestClientNetworkCreateWithDriver(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "create", "-f=dummy", mockNwName)
if err == nil {
t.Fatal("Passing incorrect flags to the create command must fail")
}
err = cli.Cmd("docker", "network", "create", "-d=dummy", mockNwName)
if err != nil {
t.Fatalf(err.Error())
}
}
func TestClientNetworkRm(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "rm", mockNwName)
if err != nil {
t.Fatal(err.Error())
}
}
func TestClientNetworkLs(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "ls")
if err != nil {
t.Fatal(err.Error())
}
}
func TestClientNetworkInfo(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "info", mockNwName)
if err != nil {
t.Fatal(err.Error())
}
}
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())
}
}
// Docker Flag processing in flag.go uses os.Exit() frequently, even for --help
// TODO : Handle the --help test-case in the IT when CLI is available
/*
func TestClientNetworkServiceCreateHelp(t *testing.T) {
var out, errOut bytes.Buffer
cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
return nil, 0, nil
}
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "create", "--help")
if err != nil {
t.Fatalf(err.Error())
}
}
*/
// Docker flag processing in flag.go uses os.Exit(1) for incorrect parameter case.
// TODO : Handle the missing argument case in the IT when CLI is available
/*
func TestClientNetworkServiceCreateMissingArgument(t *testing.T) {
var out, errOut bytes.Buffer
cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
return nil, 0, nil
}
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "create")
if err != nil {
t.Fatal(err.Error())
}
}
*/

View file

@ -0,0 +1,27 @@
Copyright (c) 2014-2016 The Docker & Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,40 @@
Package mflag (aka multiple-flag) implements command-line flag parsing.
It's an **hacky** fork of the [official golang package](http://golang.org/pkg/flag/)
It adds:
* both short and long flag version
`./example -s red` `./example --string blue`
* multiple names for the same option
```
$>./example -h
Usage of example:
-s, --string="": a simple string
```
___
It is very flexible on purpose, so you can do things like:
```
$>./example -h
Usage of example:
-s, -string, --string="": a simple string
```
Or:
```
$>./example -h
Usage of example:
-oldflag, --newflag="": a simple string
```
You can also hide some flags from the usage, so if we want only `--newflag`:
```
$>./example -h
Usage of example:
--newflag="": a simple string
$>./example -oldflag str
str
```
See [example.go](example/example.go) for more details.

View file

@ -0,0 +1,36 @@
package main
import (
"fmt"
flag "github.com/docker/docker/libnetwork/client/mflag"
)
var (
i int
str string
b, b2, h bool
)
func init() {
flag.Bool([]string{"#hp", "#-help"}, false, "display the help")
flag.BoolVar(&b, []string{"b", "#bal", "#bol", "-bal"}, false, "a simple bool")
flag.BoolVar(&b, []string{"g", "#gil"}, false, "a simple bool")
flag.BoolVar(&b2, []string{"#-bool"}, false, "a simple bool")
flag.IntVar(&i, []string{"-integer", "-number"}, -1, "a simple integer")
flag.StringVar(&str, []string{"s", "#hidden", "-string"}, "", "a simple string") //-s -hidden and --string will work, but -hidden won't be in the usage
flag.BoolVar(&h, []string{"h", "#help", "-help"}, false, "display the help")
flag.StringVar(&str, []string{"mode"}, "mode1", "set the mode\nmode1: use the mode1\nmode2: use the mode2\nmode3: use the mode3")
flag.Parse()
}
func main() {
if h {
flag.PrintDefaults()
} else {
fmt.Printf("s/#hidden/-string: %s\n", str)
fmt.Printf("b: %t\n", b)
fmt.Printf("-bool: %t\n", b2)
fmt.Printf("s/#hidden/-string(via lookup): %s\n", flag.Lookup("s").Value.String())
fmt.Printf("ARGS: %v\n", flag.Args())
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,527 @@
// Copyright 2014-2016 The Docker & Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mflag
import (
"bytes"
"fmt"
"os"
"sort"
"strings"
"testing"
"time"
_ "github.com/docker/docker/libnetwork/testutils"
)
// ResetForTesting clears all flag state and sets the usage function as directed.
// After calling ResetForTesting, parse errors in flag handling will not
// exit the program.
func ResetForTesting(usage func()) {
CommandLine = NewFlagSet(os.Args[0], ContinueOnError)
Usage = usage
}
func boolString(s string) string {
if s == "0" {
return "false"
}
return "true"
}
func TestEverything(t *testing.T) {
ResetForTesting(nil)
Bool([]string{"test_bool"}, false, "bool value")
Int([]string{"test_int"}, 0, "int value")
Int64([]string{"test_int64"}, 0, "int64 value")
Uint([]string{"test_uint"}, 0, "uint value")
Uint64([]string{"test_uint64"}, 0, "uint64 value")
String([]string{"test_string"}, "0", "string value")
Float64([]string{"test_float64"}, 0, "float64 value")
Duration([]string{"test_duration"}, 0, "time.Duration value")
m := make(map[string]*Flag)
desired := "0"
visitor := func(f *Flag) {
for _, name := range f.Names {
if len(name) > 5 && name[0:5] == "test_" {
m[name] = f
ok := false
switch {
case f.Value.String() == desired:
ok = true
case name == "test_bool" && f.Value.String() == boolString(desired):
ok = true
case name == "test_duration" && f.Value.String() == desired+"s":
ok = true
}
if !ok {
t.Error("Visit: bad value", f.Value.String(), "for", name)
}
}
}
}
VisitAll(visitor)
if len(m) != 8 {
t.Error("VisitAll misses some flags")
for k, v := range m {
t.Log(k, *v)
}
}
m = make(map[string]*Flag)
Visit(visitor)
if len(m) != 0 {
t.Error("Visit sees unset flags")
for k, v := range m {
t.Log(k, *v)
}
}
// Now set all flags
Set("test_bool", "true")
Set("test_int", "1")
Set("test_int64", "1")
Set("test_uint", "1")
Set("test_uint64", "1")
Set("test_string", "1")
Set("test_float64", "1")
Set("test_duration", "1s")
desired = "1"
Visit(visitor)
if len(m) != 8 {
t.Error("Visit fails after set")
for k, v := range m {
t.Log(k, *v)
}
}
// Now test they're visited in sort order.
var flagNames []string
Visit(func(f *Flag) {
flagNames = append(flagNames, f.Names...)
})
if !sort.StringsAreSorted(flagNames) {
t.Errorf("flag names not sorted: %v", flagNames)
}
}
func TestGet(t *testing.T) {
ResetForTesting(nil)
Bool([]string{"test_bool"}, true, "bool value")
Int([]string{"test_int"}, 1, "int value")
Int64([]string{"test_int64"}, 2, "int64 value")
Uint([]string{"test_uint"}, 3, "uint value")
Uint64([]string{"test_uint64"}, 4, "uint64 value")
String([]string{"test_string"}, "5", "string value")
Float64([]string{"test_float64"}, 6, "float64 value")
Duration([]string{"test_duration"}, 7, "time.Duration value")
visitor := func(f *Flag) {
for _, name := range f.Names {
if len(name) > 5 && name[0:5] == "test_" {
g, ok := f.Value.(Getter)
if !ok {
t.Errorf("Visit: value does not satisfy Getter: %T", f.Value)
return
}
switch name {
case "test_bool":
ok = g.Get() == true
case "test_int":
ok = g.Get() == int(1)
case "test_int64":
ok = g.Get() == int64(2)
case "test_uint":
ok = g.Get() == uint(3)
case "test_uint64":
ok = g.Get() == uint64(4)
case "test_string":
ok = g.Get() == "5"
case "test_float64":
ok = g.Get() == float64(6)
case "test_duration":
ok = g.Get() == time.Duration(7)
}
if !ok {
t.Errorf("Visit: bad value %T(%v) for %s", g.Get(), g.Get(), name)
}
}
}
}
VisitAll(visitor)
}
func testParse(f *FlagSet, t *testing.T) {
if f.Parsed() {
t.Error("f.Parse() = true before Parse")
}
boolFlag := f.Bool([]string{"bool"}, false, "bool value")
bool2Flag := f.Bool([]string{"bool2"}, false, "bool2 value")
f.Bool([]string{"bool3"}, false, "bool3 value")
bool4Flag := f.Bool([]string{"bool4"}, false, "bool4 value")
intFlag := f.Int([]string{"-int"}, 0, "int value")
int64Flag := f.Int64([]string{"-int64"}, 0, "int64 value")
uintFlag := f.Uint([]string{"uint"}, 0, "uint value")
uint64Flag := f.Uint64([]string{"-uint64"}, 0, "uint64 value")
stringFlag := f.String([]string{"string"}, "0", "string value")
f.String([]string{"string2"}, "0", "string2 value")
singleQuoteFlag := f.String([]string{"squote"}, "", "single quoted value")
doubleQuoteFlag := f.String([]string{"dquote"}, "", "double quoted value")
mixedQuoteFlag := f.String([]string{"mquote"}, "", "mixed quoted value")
mixed2QuoteFlag := f.String([]string{"mquote2"}, "", "mixed2 quoted value")
nestedQuoteFlag := f.String([]string{"nquote"}, "", "nested quoted value")
nested2QuoteFlag := f.String([]string{"nquote2"}, "", "nested2 quoted value")
float64Flag := f.Float64([]string{"float64"}, 0, "float64 value")
durationFlag := f.Duration([]string{"duration"}, 5*time.Second, "time.Duration value")
extra := "one-extra-argument"
args := []string{
"-bool",
"-bool2=true",
"-bool4=false",
"--int", "22",
"--int64", "0x23",
"-uint", "24",
"--uint64", "25",
"-string", "hello",
"-squote='single'",
`-dquote="double"`,
`-mquote='mixed"`,
`-mquote2="mixed2'`,
`-nquote="'single nested'"`,
`-nquote2='"double nested"'`,
"-float64", "2718e28",
"-duration", "2m",
extra,
}
if err := f.Parse(args); err != nil {
t.Fatal(err)
}
if !f.Parsed() {
t.Error("f.Parse() = false after Parse")
}
if *boolFlag != true {
t.Error("bool flag should be true, is ", *boolFlag)
}
if *bool2Flag != true {
t.Error("bool2 flag should be true, is ", *bool2Flag)
}
if !f.IsSet("bool2") {
t.Error("bool2 should be marked as set")
}
if f.IsSet("bool3") {
t.Error("bool3 should not be marked as set")
}
if !f.IsSet("bool4") {
t.Error("bool4 should be marked as set")
}
if *bool4Flag != false {
t.Error("bool4 flag should be false, is ", *bool4Flag)
}
if *intFlag != 22 {
t.Error("int flag should be 22, is ", *intFlag)
}
if *int64Flag != 0x23 {
t.Error("int64 flag should be 0x23, is ", *int64Flag)
}
if *uintFlag != 24 {
t.Error("uint flag should be 24, is ", *uintFlag)
}
if *uint64Flag != 25 {
t.Error("uint64 flag should be 25, is ", *uint64Flag)
}
if *stringFlag != "hello" {
t.Error("string flag should be `hello`, is ", *stringFlag)
}
if !f.IsSet("string") {
t.Error("string flag should be marked as set")
}
if f.IsSet("string2") {
t.Error("string2 flag should not be marked as set")
}
if *singleQuoteFlag != "single" {
t.Error("single quote string flag should be `single`, is ", *singleQuoteFlag)
}
if *doubleQuoteFlag != "double" {
t.Error("double quote string flag should be `double`, is ", *doubleQuoteFlag)
}
if *mixedQuoteFlag != `'mixed"` {
t.Error("mixed quote string flag should be `'mixed\"`, is ", *mixedQuoteFlag)
}
if *mixed2QuoteFlag != `"mixed2'` {
t.Error("mixed2 quote string flag should be `\"mixed2'`, is ", *mixed2QuoteFlag)
}
if *nestedQuoteFlag != "'single nested'" {
t.Error("nested quote string flag should be `'single nested'`, is ", *nestedQuoteFlag)
}
if *nested2QuoteFlag != `"double nested"` {
t.Error("double quote string flag should be `\"double nested\"`, is ", *nested2QuoteFlag)
}
if *float64Flag != 2718e28 {
t.Error("float64 flag should be 2718e28, is ", *float64Flag)
}
if *durationFlag != 2*time.Minute {
t.Error("duration flag should be 2m, is ", *durationFlag)
}
if len(f.Args()) != 1 {
t.Error("expected one argument, got", len(f.Args()))
} else if f.Args()[0] != extra {
t.Errorf("expected argument %q got %q", extra, f.Args()[0])
}
}
func testPanic(f *FlagSet, t *testing.T) {
f.Int([]string{"-int"}, 0, "int value")
if f.Parsed() {
t.Error("f.Parse() = true before Parse")
}
args := []string{
"-int", "21",
}
f.Parse(args)
}
func TestParsePanic(t *testing.T) {
ResetForTesting(func() {})
testPanic(CommandLine, t)
}
func TestParse(t *testing.T) {
ResetForTesting(func() { t.Error("bad parse") })
testParse(CommandLine, t)
}
func TestFlagSetParse(t *testing.T) {
testParse(NewFlagSet("test", ContinueOnError), t)
}
// Declare a user-defined flag type.
type flagVar []string
func (f *flagVar) String() string {
return fmt.Sprint([]string(*f))
}
func (f *flagVar) Set(value string) error {
*f = append(*f, value)
return nil
}
func TestUserDefined(t *testing.T) {
var flags FlagSet
flags.Init("test", ContinueOnError)
var v flagVar
flags.Var(&v, []string{"v"}, "usage")
if err := flags.Parse([]string{"-v", "1", "-v", "2", "-v=3"}); err != nil {
t.Error(err)
}
if len(v) != 3 {
t.Fatal("expected 3 args; got ", len(v))
}
expect := "[1 2 3]"
if v.String() != expect {
t.Errorf("expected value %q got %q", expect, v.String())
}
}
// Declare a user-defined boolean flag type.
type boolFlagVar struct {
count int
}
func (b *boolFlagVar) String() string {
return fmt.Sprintf("%d", b.count)
}
func (b *boolFlagVar) Set(value string) error {
if value == "true" {
b.count++
}
return nil
}
func (b *boolFlagVar) IsBoolFlag() bool {
return b.count < 4
}
func TestUserDefinedBool(t *testing.T) {
var flags FlagSet
flags.Init("test", ContinueOnError)
var b boolFlagVar
var err error
flags.Var(&b, []string{"b"}, "usage")
if err = flags.Parse([]string{"-b", "-b", "-b", "-b=true", "-b=false", "-b", "barg", "-b"}); err != nil {
if b.count < 4 {
t.Error(err)
}
}
if b.count != 4 {
t.Errorf("want: %d; got: %d", 4, b.count)
}
if err == nil {
t.Error("expected error; got none")
}
}
func TestSetOutput(t *testing.T) {
var flags FlagSet
var buf bytes.Buffer
flags.SetOutput(&buf)
flags.Init("test", ContinueOnError)
flags.Parse([]string{"-unknown"})
if out := buf.String(); !strings.Contains(out, "-unknown") {
t.Logf("expected output mentioning unknown; got %q", out)
}
}
// This tests that one can reset the flags. This still works but not well, and is
// superseded by FlagSet.
func TestChangingArgs(t *testing.T) {
ResetForTesting(func() { t.Fatal("bad parse") })
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"cmd", "-before", "subcmd", "-after", "args"}
before := Bool([]string{"before"}, false, "")
if err := CommandLine.Parse(os.Args[1:]); err != nil {
t.Fatal(err)
}
cmd := Arg(0)
os.Args = Args()
after := Bool([]string{"after"}, false, "")
Parse()
args := Args()
if !*before || cmd != "subcmd" || !*after || len(args) != 1 || args[0] != "args" {
t.Fatalf("expected true subcmd true [args] got %v %v %v %v", *before, cmd, *after, args)
}
}
// Test that -help invokes the usage message and returns ErrHelp.
func TestHelp(t *testing.T) {
var helpCalled = false
fs := NewFlagSet("help test", ContinueOnError)
fs.Usage = func() { helpCalled = true }
var flag bool
fs.BoolVar(&flag, []string{"flag"}, false, "regular flag")
// Regular flag invocation should work
err := fs.Parse([]string{"-flag=true"})
if err != nil {
t.Fatal("expected no error; got ", err)
}
if !flag {
t.Error("flag was not set by -flag")
}
if helpCalled {
t.Error("help called for regular flag")
helpCalled = false // reset for next test
}
// Help flag should work as expected.
err = fs.Parse([]string{"-help"})
if err == nil {
t.Fatal("error expected")
}
if err != ErrHelp {
t.Fatal("expected ErrHelp; got ", err)
}
if !helpCalled {
t.Fatal("help was not called")
}
// If we define a help flag, that should override.
var help bool
fs.BoolVar(&help, []string{"help"}, false, "help flag")
helpCalled = false
err = fs.Parse([]string{"-help"})
if err != nil {
t.Fatal("expected no error for defined -help; got ", err)
}
if helpCalled {
t.Fatal("help was called; should not have been for defined help flag")
}
}
// Test the flag count functions.
func TestFlagCounts(t *testing.T) {
fs := NewFlagSet("help test", ContinueOnError)
var flag bool
fs.BoolVar(&flag, []string{"flag1"}, false, "regular flag")
fs.BoolVar(&flag, []string{"#deprecated1"}, false, "regular flag")
fs.BoolVar(&flag, []string{"f", "flag2"}, false, "regular flag")
fs.BoolVar(&flag, []string{"#d", "#deprecated2"}, false, "regular flag")
fs.BoolVar(&flag, []string{"flag3"}, false, "regular flag")
fs.BoolVar(&flag, []string{"g", "#flag4", "-flag4"}, false, "regular flag")
if fs.FlagCount() != 6 {
t.Fatal("FlagCount wrong. ", fs.FlagCount())
}
if fs.FlagCountUndeprecated() != 4 {
t.Fatal("FlagCountUndeprecated wrong. ", fs.FlagCountUndeprecated())
}
if fs.NFlag() != 0 {
t.Fatal("NFlag wrong. ", fs.NFlag())
}
err := fs.Parse([]string{"-fd", "-g", "-flag4"})
if err != nil {
t.Fatal("expected no error for defined -help; got ", err)
}
if fs.NFlag() != 4 {
t.Fatal("NFlag wrong. ", fs.NFlag())
}
}
// Show up bug in sortFlags
func TestSortFlags(t *testing.T) {
fs := NewFlagSet("help TestSortFlags", ContinueOnError)
var err error
var b bool
fs.BoolVar(&b, []string{"b", "-banana"}, false, "usage")
err = fs.Parse([]string{"--banana=true"})
if err != nil {
t.Fatal("expected no error; got ", err)
}
count := 0
fs.VisitAll(func(flag *Flag) {
count++
if flag == nil {
t.Fatal("VisitAll should not return a nil flag")
}
})
flagcount := fs.FlagCount()
if flagcount != count {
t.Fatalf("FlagCount (%d) != number (%d) of elements visited", flagcount, count)
}
// Make sure its idempotent
if flagcount != fs.FlagCount() {
t.Fatalf("FlagCount (%d) != fs.FlagCount() (%d) of elements visited", flagcount, fs.FlagCount())
}
count = 0
fs.Visit(func(flag *Flag) {
count++
if flag == nil {
t.Fatal("Visit should not return a nil flag")
}
})
nflag := fs.NFlag()
if nflag != count {
t.Fatalf("NFlag (%d) != number (%d) of elements visited", nflag, count)
}
if nflag != fs.NFlag() {
t.Fatalf("NFlag (%d) != fs.NFlag() (%d) of elements visited", nflag, fs.NFlag())
}
}
func TestMergeFlags(t *testing.T) {
base := NewFlagSet("base", ContinueOnError)
base.String([]string{"f"}, "", "")
fs := NewFlagSet("test", ContinueOnError)
Merge(fs, base)
if len(fs.formal) != 1 {
t.Fatalf("FlagCount (%d) != number (1) of elements merged", len(fs.formal))
}
}

View file

@ -0,0 +1,267 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"text/tabwriter"
flag "github.com/docker/docker/libnetwork/client/mflag"
"github.com/docker/docker/libnetwork/netlabel"
"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")
flID := cmd.String([]string{"-id"}, "", "Network ID string")
flOpts := cmd.String([]string{"o", "-opt"}, "", "Network options")
flInternal := cmd.Bool([]string{"-internal"}, false, "Config the network to be internal")
flIPv6 := cmd.Bool([]string{"-ipv6"}, false, "Enable IPv6 on the network")
flSubnet := cmd.String([]string{"-subnet"}, "", "Subnet option")
flRange := cmd.String([]string{"-ip-range"}, "", "Range option")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
networkOpts := make(map[string]string)
if *flInternal {
networkOpts[netlabel.Internal] = "true"
}
if *flIPv6 {
networkOpts[netlabel.EnableIPv6] = "true"
}
driverOpts := make(map[string]string)
if *flOpts != "" {
opts := strings.Split(*flOpts, ",")
for _, opt := range opts {
driverOpts[netlabel.Key(opt)] = netlabel.Value(opt)
}
}
var icList []ipamConf
if *flSubnet != "" {
ic := ipamConf{
PreferredPool: *flSubnet,
}
if *flRange != "" {
ic.SubPool = *flRange
}
icList = append(icList, ic)
}
// Construct network create request body
nc := networkCreate{Name: cmd.Arg(0), NetworkType: *flDriver, ID: *flID, IPv4Conf: icList, DriverOpts: driverOpts, NetworkOpts: networkOpts}
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 an 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

@ -0,0 +1,400 @@
package client
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"text/tabwriter"
flag "github.com/docker/docker/libnetwork/client/mflag"
"github.com/docker/docker/libnetwork/netutils"
"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 connection 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 "", errors.New("Unexpected data type for container ID in json response")
}
return "", errors.New("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)
flAlias := flag.NewListOpts(netutils.ValidateAlias)
cmd.Var(&flAlias, []string{"-alias"}, "Add alias to self")
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, MyAliases: flAlias.GetAll()}
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)
force := cmd.Bool([]string{"f", "-force"}, false, "force unpublish service")
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
}
sd := serviceDelete{Name: sn, Force: *force}
_, _, err = readBody(cli.call("DELETE", "/services/"+serviceID, sd, 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)
flAlias := flag.NewListOpts(netutils.ValidateAlias)
cmd.Var(&flAlias, []string{"-alias"}, "Add alias for another container")
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, Aliases: flAlias.GetAll()}
_, _, 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

@ -0,0 +1,88 @@
package client
import "github.com/docker/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
************/
type ipamConf struct {
PreferredPool string
SubPool string
Gateway string
AuxAddresses map[string]string
}
// networkCreate is the expected body of the "create network" http request message
type networkCreate struct {
Name string `json:"name"`
ID string `json:"id"`
NetworkType string `json:"network_type"`
IPv4Conf []ipamConf `json:"ipv4_configuration"`
DriverOpts map[string]string `json:"driver_opts"`
NetworkOpts map[string]string `json:"network_opts"`
}
// serviceCreate represents the body of the "publish service" http request message
type serviceCreate struct {
Name string `json:"name"`
MyAliases []string `json:"my_aliases"`
Network string `json:"network_name"`
}
// serviceDelete represents the body of the "unpublish service" http request message
type serviceDelete struct {
Name string `json:"name"`
Force bool `json:"force"`
}
// serviceAttach represents the expected body of the "attach/detach sandbox to/from service" http request messages
type serviceAttach struct {
SandboxID string `json:"sandbox_id"`
Aliases []string `json:"aliases"`
}
// 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"`
ExposedPorts []types.TransportPort `json:"exposed_ports"`
PortMapping []types.PortBinding `json:"port_mapping"`
}
// extraHost represents the extra host object
type extraHost struct {
Name string `json:"name"`
Address string `json:"address"`
}

View file

@ -0,0 +1,4 @@
FROM alpine
RUN apk add --no-cache curl
COPY diagnosticClient /usr/local/bin/diagnosticClient
ENTRYPOINT ["/usr/local/bin/diagnosticClient"]

View file

@ -0,0 +1,5 @@
FROM docker:17.12-dind
RUN apk add --no-cache curl
ENV DIND_CLIENT=true
COPY daemon.json /etc/docker/daemon.json
COPY diagnosticClient /usr/local/bin/diagnosticClient

View file

@ -0,0 +1,262 @@
---
description: Learn to use the built-in network debugger to debug overlay networking problems
keywords: network, troubleshooting, debug
title: Debug overlay or swarm networking issues
---
**WARNING**
This tool can change the internal state of the libnetwork API, be really mindful
on its use and read carefully the following guide. Improper use of it will damage
or permanently destroy the network configuration.
Docker CE 17.12 and higher introduce a network debugging tool designed to help
debug issues with overlay networks and swarm services running on Linux hosts.
When enabled, a network diagnostic server listens on the specified port and
provides diagnostic information. The network debugging tool should only be
started to debug specific issues, and should not be left running all the time.
Information about networks is stored in the database, which can be examined using
the API. Currently the database contains information about the overlay network
as well as the service discovery data.
The Docker API exposes endpoints to query and control the network debugging
tool. CLI integration is provided as a preview, but the implementation is not
yet considered stable and commands and options may change without notice.
The tool is available into 2 forms:
1) client only: dockereng/network-diagnostic:onlyclient
2) docker in docker version: dockereng/network-diagnostic:17.12-dind
The latter allows to use the tool with a cluster running an engine older than 17.12
## Enable the diagnostic server
The tool currently only works on Docker hosts running on Linux. To enable it on a node
follow the step below.
1. Set the `network-diagnostic-port` to a port which is free on the Docker
host, in the `/etc/docker/daemon.json` configuration file.
```json
“network-diagnostic-port”: <port>
```
2. Get the process ID (PID) of the `dockerd` process. It is the second field in
the output, and is typically a number from 2 to 6 digits long.
```bash
$ ps aux |grep dockerd | grep -v grep
```
3. Reload the Docker configuration without restarting Docker, by sending the
`HUP` signal to the PID you found in the previous step.
```bash
kill -HUP <pid-of-dockerd>
```
If systemd is used the command `systemctl reload docker` will be enough
A message like the following will appear in the Docker host logs:
```none
Starting the diagnostic server listening on <port> for commands
```
## Disable the diagnostic tool
Repeat these steps for each node participating in the swarm.
1. Remove the `network-diagnostic-port` key from the `/etc/docker/daemon.json`
configuration file.
2. Get the process ID (PID) of the `dockerd` process. It is the second field in
the output, and is typically a number from 2 to 6 digits long.
```bash
$ ps aux |grep dockerd | grep -v grep
```
3. Reload the Docker configuration without restarting Docker, by sending the
`HUP` signal to the PID you found in the previous step.
```bash
kill -HUP <pid-of-dockerd>
```
A message like the following will appear in the Docker host logs:
```none
Disabling the diagnostic server
```
## Access the diagnostic tool's API
The network diagnostic tool exposes its own RESTful API. To access the API,
send a HTTP request to the port where the tool is listening. The following
commands assume the tool is listening on port 2000.
Examples are not given for every endpoint.
### Get help
```bash
$ curl localhost:2000/help
OK
/updateentry
/getentry
/gettable
/leavenetwork
/createentry
/help
/clusterpeers
/ready
/joinnetwork
/deleteentry
/networkpeers
/
/join
```
### Join or leave the network database cluster
```bash
$ curl localhost:2000/join?members=ip1,ip2,...
```
```bash
$ curl localhost:2000/leave?members=ip1,ip2,...
```
`ip1`, `ip2`, ... are the swarm node ips (usually one is enough)
### Join or leave a network
```bash
$ curl localhost:2000/joinnetwork?nid=<network id>
```
```bash
$ curl localhost:2000/leavenetwork?nid=<network id>
```
`network id` can be retrieved on the manager with `docker network ls --no-trunc` and has
to be the full length identifier
### List cluster peers
```bash
$ curl localhost:2000/clusterpeers
```
### List nodes connected to a given network
```bash
$ curl localhost:2000/networkpeers?nid=<network id>
```
`network id` can be retrieved on the manager with `docker network ls --no-trunc` and has
to be the full length identifier
### Dump database tables
The tables are called `endpoint_table` and `overlay_peer_table`.
The `overlay_peer_table` contains all the overlay forwarding information
The `endpoint_table` contains all the service discovery information
```bash
$ curl localhost:2000/gettable?nid=<network id>&tname=<table name>
```
### Interact with a specific database table
The tables are called `endpoint_table` and `overlay_peer_table`.
```bash
$ curl localhost:2000/<method>?nid=<network id>&tname=<table name>&key=<key>[&value=<value>]
```
Note:
operations on tables have node ownership, this means that are going to remain persistent till
the node that inserted them is part of the cluster
## Access the diagnostic tool's CLI
The CLI is provided as a preview and is not yet stable. Commands or options may
change at any time.
The CLI executable is called `diagnosticClient` and is made available using a
standalone container.
`docker run --net host dockereng/network-diagnostic:onlyclient -v -net <full network id> -t sd`
The following flags are supported:
| Flag | Description |
|---------------|-------------------------------------------------|
| -t <string> | Table one of `sd` or `overlay`. |
| -ip <string> | The IP address to query. Defaults to 127.0.0.1. |
| -net <string> | The target network ID. |
| -port <int> | The target port. (default port is 2000) |
| -a | Join/leave network |
| -v | Enable verbose output. |
*NOTE*
By default the tool won't try to join the network. This is following the intent to not change
the state on which the node is when the diagnostic client is run. This means that it is safe
to run the diagnosticClient against a running daemon because it will just dump the current state.
When using instead the diagnosticClient in the containerized version the flag `-a` MUST be passed
to avoid retrieving empty results. On the other side using the `-a` flag against a loaded daemon
will have the undesirable side effect to leave the network and so cutting down the data path for
that daemon.
### Container version of the diagnostic tool
The CLI is provided as a container with a 17.12 engine that needs to run using privileged mode.
*NOTE*
Remember that table operations have ownership, so any `create entry` will be persistent till
the diagnostic container is part of the swarm.
1. Make sure that the node where the diagnostic client will run is not part of the swarm, if so do `docker swarm leave -f`
2. To run the container, use a command like the following:
```bash
$ docker container run --name net-diagnostic -d --privileged --network host dockereng/network-diagnostic:17.12-dind
```
3. Connect to the container using `docker exec -it <container-ID> sh`,
and start the server using the following command:
```bash
$ kill -HUP 1
```
4. Join the diagnostic container to the swarm, then run the diagnostic CLI within the container.
```bash
$ ./diagnosticClient <flags>...
```
4. When finished debugging, leave the swarm and stop the container.
### Examples
The following commands dump the service discovery table and verify node
ownership.
*NOTE*
Remember to use the full network ID, you can easily find that with `docker network ls --no-trunc`
**Service discovery and load balancer:**
```bash
$ diagnostiClient -t sd -v -net n8a8ie6tb3wr2e260vxj8ncy4 -a
```
**Overlay network:**
```bash
$ diagnostiClient -port 2001 -t overlay -v -net n8a8ie6tb3wr2e260vxj8ncy4 -a
```

View file

@ -0,0 +1,4 @@
{
"debug": true,
"network-diagnostic-port": 2000
}

View file

@ -0,0 +1,209 @@
package main
import (
"bufio"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/libnetwork/diagnostic"
"github.com/docker/docker/libnetwork/drivers/overlay"
"github.com/sirupsen/logrus"
)
const (
readyPath = "http://%s:%d/ready"
joinNetwork = "http://%s:%d/joinnetwork?nid=%s"
leaveNetwork = "http://%s:%d/leavenetwork?nid=%s"
clusterPeers = "http://%s:%d/clusterpeers?json"
networkPeers = "http://%s:%d/networkpeers?nid=%s&json"
dumpTable = "http://%s:%d/gettable?nid=%s&tname=%s&json"
deleteEntry = "http://%s:%d/deleteentry?nid=%s&tname=%s&key=%s&json"
)
func httpIsOk(body io.ReadCloser) {
b, err := ioutil.ReadAll(body)
if err != nil {
logrus.Fatalf("Failed the body parse %s", err)
}
if !strings.Contains(string(b), "OK") {
logrus.Fatalf("Server not ready %s", b)
}
body.Close()
}
func main() {
ipPtr := flag.String("ip", "127.0.0.1", "ip address")
portPtr := flag.Int("port", 2000, "port")
networkPtr := flag.String("net", "", "target network")
tablePtr := flag.String("t", "", "table to process <sd/overlay>")
remediatePtr := flag.Bool("r", false, "perform remediation deleting orphan entries")
joinPtr := flag.Bool("a", false, "join/leave network")
verbosePtr := flag.Bool("v", false, "verbose output")
flag.Parse()
if *verbosePtr {
logrus.SetLevel(logrus.DebugLevel)
}
if _, ok := os.LookupEnv("DIND_CLIENT"); !ok && *joinPtr {
logrus.Fatal("you are not using the client in docker in docker mode, the use of the -a flag can be disruptive, " +
"please remove it (doc:https://github.com/docker/docker/libnetwork/blob/master/cmd/diagnostic/README.md)")
}
logrus.Infof("Connecting to %s:%d checking ready", *ipPtr, *portPtr)
resp, err := http.Get(fmt.Sprintf(readyPath, *ipPtr, *portPtr))
if err != nil {
logrus.WithError(err).Fatalf("The connection failed")
}
httpIsOk(resp.Body)
clusterPeers := fetchNodePeers(*ipPtr, *portPtr, "")
var networkPeers map[string]string
var joinedNetwork bool
if *networkPtr != "" {
if *joinPtr {
logrus.Infof("Joining the network:%q", *networkPtr)
resp, err = http.Get(fmt.Sprintf(joinNetwork, *ipPtr, *portPtr, *networkPtr))
if err != nil {
logrus.WithError(err).Fatalf("Failed joining the network")
}
httpIsOk(resp.Body)
joinedNetwork = true
}
networkPeers = fetchNodePeers(*ipPtr, *portPtr, *networkPtr)
if len(networkPeers) == 0 {
logrus.Warnf("There is no peer on network %q, check the network ID, and verify that is the non truncated version", *networkPtr)
}
}
switch *tablePtr {
case "sd":
fetchTable(*ipPtr, *portPtr, *networkPtr, "endpoint_table", clusterPeers, networkPeers, *remediatePtr)
case "overlay":
fetchTable(*ipPtr, *portPtr, *networkPtr, "overlay_peer_table", clusterPeers, networkPeers, *remediatePtr)
}
if joinedNetwork {
logrus.Infof("Leaving the network:%q", *networkPtr)
resp, err = http.Get(fmt.Sprintf(leaveNetwork, *ipPtr, *portPtr, *networkPtr))
if err != nil {
logrus.WithError(err).Fatalf("Failed leaving the network")
}
httpIsOk(resp.Body)
}
}
func fetchNodePeers(ip string, port int, network string) map[string]string {
if network == "" {
logrus.Infof("Fetch cluster peers")
} else {
logrus.Infof("Fetch peers network:%q", network)
}
var path string
if network != "" {
path = fmt.Sprintf(networkPeers, ip, port, network)
} else {
path = fmt.Sprintf(clusterPeers, ip, port)
}
resp, err := http.Get(path) // nolint:gosec
if err != nil {
logrus.WithError(err).Fatalf("Failed fetching path")
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
logrus.WithError(err).Fatalf("Failed the body parse")
}
output := diagnostic.HTTPResult{Details: &diagnostic.TablePeersResult{}}
err = json.Unmarshal(body, &output)
if err != nil {
logrus.WithError(err).Fatalf("Failed the json unmarshalling")
}
logrus.Debugf("Parsing JSON response")
result := make(map[string]string, output.Details.(*diagnostic.TablePeersResult).Length)
for _, v := range output.Details.(*diagnostic.TablePeersResult).Elements {
logrus.Debugf("name:%s ip:%s", v.Name, v.IP)
result[v.Name] = v.IP
}
return result
}
func fetchTable(ip string, port int, network, tableName string, clusterPeers, networkPeers map[string]string, remediate bool) {
logrus.Infof("Fetch %s table and check owners", tableName)
resp, err := http.Get(fmt.Sprintf(dumpTable, ip, port, network, tableName))
if err != nil {
logrus.WithError(err).Fatalf("Failed fetching endpoint table")
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
logrus.WithError(err).Fatalf("Failed the body parse")
}
output := diagnostic.HTTPResult{Details: &diagnostic.TableEndpointsResult{}}
err = json.Unmarshal(body, &output)
if err != nil {
logrus.WithError(err).Fatalf("Failed the json unmarshalling")
}
logrus.Debug("Parsing data structures")
var orphanKeys []string
for _, v := range output.Details.(*diagnostic.TableEndpointsResult).Elements {
decoded, err := base64.StdEncoding.DecodeString(v.Value)
if err != nil {
logrus.WithError(err).Errorf("Failed decoding entry")
continue
}
switch tableName {
case "endpoint_table":
var elem libnetwork.EndpointRecord
elem.Unmarshal(decoded)
logrus.Debugf("key:%s value:%+v owner:%s", v.Key, elem, v.Owner)
case "overlay_peer_table":
var elem overlay.PeerRecord
elem.Unmarshal(decoded)
logrus.Debugf("key:%s value:%+v owner:%s", v.Key, elem, v.Owner)
}
if _, ok := networkPeers[v.Owner]; !ok {
logrus.Warnf("The element with key:%s does not belong to any node on this network", v.Key)
orphanKeys = append(orphanKeys, v.Key)
}
if _, ok := clusterPeers[v.Owner]; !ok {
logrus.Warnf("The element with key:%s does not belong to any node on this cluster", v.Key)
}
}
if len(orphanKeys) > 0 && remediate {
logrus.Warnf("The following keys:%v results as orphan, do you want to proceed with the deletion (this operation is irreversible)? [Yes/No]", orphanKeys)
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
text = strings.Replace(text, "\n", "", -1)
if strings.Compare(text, "Yes") == 0 {
for _, k := range orphanKeys {
resp, err := http.Get(fmt.Sprintf(deleteEntry, ip, port, network, tableName, k))
if err != nil {
logrus.WithError(err).Errorf("Failed deleting entry k:%s", k)
break
}
resp.Body.Close()
}
} else {
logrus.Infof("Deletion skipped")
}
}
}

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/docker/docker/libnetwork/client"
"github.com/moby/term"
"github.com/urfave/cli"
)
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.Println("Please provide container id argument")
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.Println("Please provide container id argument")
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
}

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

@ -0,0 +1,576 @@
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
"github.com/BurntSushi/toml"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/libnetwork/api"
"github.com/docker/docker/libnetwork/cluster"
"github.com/docker/docker/libnetwork/config"
"github.com/docker/docker/libnetwork/datastore"
"github.com/docker/docker/libnetwork/driverapi"
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/libnetwork/netutils"
"github.com/docker/docker/libnetwork/options"
"github.com/docker/docker/libnetwork/types"
"github.com/docker/docker/pkg/discovery"
"github.com/docker/docker/pkg/reexec"
"github.com/gorilla/mux"
"github.com/moby/term"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
const (
// DefaultHTTPHost is used if only port is provided to -H flag e.g. docker -d -H tcp://:8080
DefaultHTTPHost = "0.0.0.0"
// DefaultHTTPPort is the default http port used by dnet
DefaultHTTPPort = 2385
cfgFileEnv = "LIBNETWORK_CFG"
defaultCfgFile = "/etc/default/libnetwork.toml"
defaultHeartbeat = time.Duration(10) * time.Second
ttlFactor = 2
)
var epConn *dnetConnection
func main() {
if reexec.Init() {
return
}
_, stdout, stderr := term.StdStreams()
logrus.SetOutput(stderr)
err := dnetApp(stdout, stderr)
if err != nil {
os.Exit(1)
}
}
// ParseConfig parses the libnetwork configuration file
func (d *dnetConnection) parseOrchestrationConfig(tomlCfgFile string) error {
dummy := &dnetConnection{}
if _, err := toml.DecodeFile(tomlCfgFile, dummy); err != nil {
return err
}
if dummy.Orchestration != nil {
d.Orchestration = dummy.Orchestration
}
return nil
}
func (d *dnetConnection) parseConfig(cfgFile string) (*config.Config, error) {
if strings.Trim(cfgFile, " ") == "" {
cfgFile = os.Getenv(cfgFileEnv)
if strings.Trim(cfgFile, " ") == "" {
cfgFile = defaultCfgFile
}
}
if err := d.parseOrchestrationConfig(cfgFile); err != nil {
return nil, err
}
return config.ParseConfig(cfgFile)
}
func processConfig(cfg *config.Config) []config.Option {
options := []config.Option{}
if cfg == nil {
return options
}
dn := "bridge"
if strings.TrimSpace(cfg.Daemon.DefaultNetwork) != "" {
dn = cfg.Daemon.DefaultNetwork
}
options = append(options, config.OptionDefaultNetwork(dn))
dd := "bridge"
if strings.TrimSpace(cfg.Daemon.DefaultDriver) != "" {
dd = cfg.Daemon.DefaultDriver
}
options = append(options, config.OptionDefaultDriver(dd))
if cfg.Daemon.Labels != nil {
options = append(options, config.OptionLabels(cfg.Daemon.Labels))
}
if dcfg, ok := cfg.Scopes[datastore.GlobalScope]; ok && dcfg.IsValid() {
options = append(options, config.OptionKVProvider(dcfg.Client.Provider))
options = append(options, config.OptionKVProviderURL(dcfg.Client.Address))
}
dOptions, err := startDiscovery(&cfg.Cluster)
if err != nil {
logrus.Infof("Skipping discovery : %s", err.Error())
} else {
options = append(options, dOptions...)
}
return options
}
func startDiscovery(cfg *config.ClusterCfg) ([]config.Option, error) {
if cfg == nil {
return nil, errors.New("discovery requires a valid configuration")
}
hb := time.Duration(cfg.Heartbeat) * time.Second
if hb == 0 {
hb = defaultHeartbeat
}
logrus.Infof("discovery : %s %s", cfg.Discovery, hb.String())
d, err := discovery.New(cfg.Discovery, hb, ttlFactor*hb, map[string]string{})
if err != nil {
return nil, err
}
if cfg.Address == "" {
iface, err := net.InterfaceByName("eth0")
if err != nil {
return nil, err
}
addrs, err := iface.Addrs()
if err != nil || len(addrs) == 0 {
return nil, err
}
ip, _, _ := net.ParseCIDR(addrs[0].String())
cfg.Address = ip.String()
}
if ip := net.ParseIP(cfg.Address); ip == nil {
return nil, errors.New("address config should be either ipv4 or ipv6 address")
}
if err := d.Register(cfg.Address + ":0"); err != nil {
return nil, err
}
options := []config.Option{config.OptionDiscoveryWatcher(d), config.OptionDiscoveryAddress(cfg.Address)}
go func() {
for {
time.Sleep(hb)
if err := d.Register(cfg.Address + ":0"); err != nil {
logrus.Warn(err)
}
}
}()
return options, nil
}
func dnetApp(stdout, stderr io.Writer) error {
app := cli.NewApp()
app.Name = "dnet"
app.Usage = "A self-sufficient runtime for container networking."
app.Flags = dnetFlags
app.Before = processFlags
app.Commands = dnetCommands
app.Run(os.Args)
return nil
}
func createDefaultNetwork(c libnetwork.NetworkController) {
nw := c.Config().Daemon.DefaultNetwork
d := c.Config().Daemon.DefaultDriver
createOptions := []libnetwork.NetworkOption{}
genericOption := options.Generic{}
if nw != "" && d != "" {
// Bridge driver is special due to legacy reasons
if d == "bridge" {
genericOption[netlabel.GenericData] = map[string]string{
"BridgeName": "docker0",
"DefaultBridge": "true",
}
createOptions = append(createOptions,
libnetwork.NetworkOptionGeneric(genericOption),
ipamOption(nw))
}
if n, err := c.NetworkByName(nw); err == nil {
logrus.Debugf("Default network %s already present. Deleting it", nw)
if err = n.Delete(); err != nil {
logrus.Debugf("Network could not be deleted: %v", err)
return
}
}
_, err := c.NewNetwork(d, nw, "", createOptions...)
if err != nil {
logrus.Errorf("Error creating default network : %s : %v", nw, err)
}
}
}
type dnetConnection struct {
// proto holds the client protocol i.e. unix.
proto string
// addr holds the client address.
addr string
Orchestration *NetworkOrchestration
configEvent chan cluster.ConfigEventType
}
// NetworkOrchestration exported
type NetworkOrchestration struct {
Agent bool
Manager bool
Bind string
Peer string
}
func (d *dnetConnection) dnetDaemon(cfgFile string) error {
if err := startTestDriver(); err != nil {
return fmt.Errorf("failed to start test driver: %v", err)
}
cfg, err := d.parseConfig(cfgFile)
var cOptions []config.Option
if err == nil {
cOptions = processConfig(cfg)
} else {
logrus.Errorf("Error parsing config %v", err)
}
bridgeConfig := options.Generic{
"EnableIPForwarding": true,
"EnableIPTables": true,
}
bridgeOption := options.Generic{netlabel.GenericData: bridgeConfig}
cOptions = append(cOptions, config.OptionDriverConfig("bridge", bridgeOption))
controller, err := libnetwork.New(cOptions...)
if err != nil {
fmt.Println("Error starting dnetDaemon :", err)
return err
}
controller.SetClusterProvider(d)
if d.Orchestration.Agent || d.Orchestration.Manager {
d.configEvent <- cluster.EventNodeReady
}
createDefaultNetwork(controller)
httpHandler := api.NewHTTPHandler(controller)
r := mux.NewRouter().StrictSlash(false)
post := r.PathPrefix("/{.*}/networks").Subrouter()
post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
post = r.PathPrefix("/networks").Subrouter()
post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
post = r.PathPrefix("/{.*}/services").Subrouter()
post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
post = r.PathPrefix("/services").Subrouter()
post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
post = r.PathPrefix("/{.*}/sandboxes").Subrouter()
post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
post = r.PathPrefix("/sandboxes").Subrouter()
post.Methods("GET", "PUT", "POST", "DELETE").HandlerFunc(httpHandler)
handleSignals(controller)
setupDumpStackTrap()
return http.ListenAndServe(d.addr, r)
}
func (d *dnetConnection) IsManager() bool {
return d.Orchestration.Manager
}
func (d *dnetConnection) IsAgent() bool {
return d.Orchestration.Agent
}
func (d *dnetConnection) GetAdvertiseAddress() string {
return d.Orchestration.Bind
}
func (d *dnetConnection) GetDataPathAddress() string {
return d.Orchestration.Bind
}
func (d *dnetConnection) GetLocalAddress() string {
return d.Orchestration.Bind
}
func (d *dnetConnection) GetListenAddress() string {
return d.Orchestration.Bind
}
func (d *dnetConnection) GetRemoteAddressList() []string {
return []string{d.Orchestration.Peer}
}
func (d *dnetConnection) ListenClusterEvents() <-chan cluster.ConfigEventType {
return d.configEvent
}
func (d *dnetConnection) AttachNetwork(string, string, []string) (*network.NetworkingConfig, error) {
return nil, nil
}
func (d *dnetConnection) DetachNetwork(string, string) error {
return nil
}
func (d *dnetConnection) UpdateAttachment(string, string, *network.NetworkingConfig) error {
return nil
}
func (d *dnetConnection) WaitForDetachment(context.Context, string, string, string, string) error {
return nil
}
func handleSignals(controller libnetwork.NetworkController) {
c := make(chan os.Signal, 1)
signals := []os.Signal{os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}
signal.Notify(c, signals...)
go func() {
for range c {
controller.Stop()
os.Exit(0)
}
}()
}
func startTestDriver() error {
mux := http.NewServeMux()
server := httptest.NewServer(mux)
if server == nil {
return errors.New("Failed to start an HTTP Server")
}
mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
fmt.Fprintf(w, `{"Implements": ["%s"]}`, driverapi.NetworkPluginEndpointType)
})
mux.HandleFunc(fmt.Sprintf("/%s.GetCapabilities", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
fmt.Fprint(w, `{"Scope":"global"}`)
})
mux.HandleFunc(fmt.Sprintf("/%s.CreateNetwork", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
fmt.Fprint(w, "null")
})
mux.HandleFunc(fmt.Sprintf("/%s.DeleteNetwork", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
fmt.Fprint(w, "null")
})
mux.HandleFunc(fmt.Sprintf("/%s.CreateEndpoint", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
fmt.Fprint(w, "null")
})
mux.HandleFunc(fmt.Sprintf("/%s.DeleteEndpoint", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
fmt.Fprint(w, "null")
})
mux.HandleFunc(fmt.Sprintf("/%s.Join", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
fmt.Fprint(w, "null")
})
mux.HandleFunc(fmt.Sprintf("/%s.Leave", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
fmt.Fprint(w, "null")
})
if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil {
return err
}
return ioutil.WriteFile("/etc/docker/plugins/test.spec", []byte(server.URL), 0644)
}
func newDnetConnection(val string) (*dnetConnection, error) {
url, err := parseHost(val)
if err != nil {
return nil, err
}
protoAddrParts := strings.SplitN(url, "://", 2)
if len(protoAddrParts) != 2 {
return nil, errors.New("bad format, expected tcp://ADDR")
}
if strings.ToLower(protoAddrParts[0]) != "tcp" {
return nil, errors.New("dnet currently only supports tcp transport")
}
return &dnetConnection{protoAddrParts[0], protoAddrParts[1], &NetworkOrchestration{}, make(chan cluster.ConfigEventType, 10)}, nil
}
const (
defaultUnixSocket = "unix:///var/run/docker.sock"
defaultTCPHost = "tcp://localhost:2375"
)
// parseHost and set defaults for a Daemon host string.
func parseHost(val string) (string, error) {
host := strings.TrimSpace(val)
if host == "" {
return defaultUnixSocket, nil
}
addrParts := strings.SplitN(host, "://", 2)
if len(addrParts) == 1 && addrParts[0] != "" {
addrParts = []string{"tcp", addrParts[0]}
}
if addrParts[0] != "tcp" {
return "", errors.New("dnet currently only supports tcp transport")
}
return parseTCPAddr(addrParts[1], defaultTCPHost)
}
// parseTCPAddr parses and validates that the specified address is a valid TCP
// address. It returns a formatted TCP address, either using the address parsed
// from tryAddr, or the contents of defaultAddr if tryAddr is a blank string.
// tryAddr is expected to have already been Trim()'d
// defaultAddr must be in the full `tcp://host:port` form
func parseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
if tryAddr == "" || tryAddr == "tcp://" {
return defaultAddr, nil
}
addr := strings.TrimPrefix(tryAddr, "tcp://")
if strings.Contains(addr, "://") || addr == "" {
return "", fmt.Errorf("Invalid proto, expected tcp: %s", tryAddr)
}
defaultAddr = strings.TrimPrefix(defaultAddr, "tcp://")
defaultHost, defaultPort, err := net.SplitHostPort(defaultAddr)
if err != nil {
return "", err
}
u, err := url.Parse("tcp://" + addr)
if err != nil {
return "", err
}
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
// try port addition once
host, port, err = net.SplitHostPort(net.JoinHostPort(u.Host, defaultPort))
}
if err != nil {
return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
}
if host == "" {
host = defaultHost
}
if port == "" {
port = defaultPort
}
p, err := strconv.Atoi(port)
if err != nil && p == 0 {
return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
}
return fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path), nil
}
func (d *dnetConnection) httpCall(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
var in io.Reader
in, err := encodeData(data)
if err != nil {
return nil, nil, -1, err
}
req, err := http.NewRequest(method, path, in)
if err != nil {
return nil, 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, nil, statusCode, fmt.Errorf("error when trying to connect: %v", err)
}
if statusCode < 200 || statusCode >= 400 {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, nil, statusCode, err
}
return nil, nil, statusCode, fmt.Errorf("error : %s", bytes.TrimSpace(body))
}
return resp.Body, resp.Header, 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")
}
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
}
func ipamOption(bridgeName string) libnetwork.NetworkOption {
if nws, _, err := netutils.ElectInterfaceAddresses(bridgeName); err == nil {
ipamV4Conf := &libnetwork.IpamConf{PreferredPool: nws[0].String()}
hip, _ := types.GetHostPartIP(nws[0].IP, nws[0].Mask)
if hip.IsGlobalUnicast() {
ipamV4Conf.Gateway = nws[0].IP.String()
}
return libnetwork.NetworkOptionIpam("default", "", []*libnetwork.IpamConf{ipamV4Conf}, nil, nil)
}
return nil
}

View file

@ -0,0 +1,19 @@
package main
import (
"os"
"os/signal"
"syscall"
psignal "github.com/docker/docker/pkg/signal"
)
func setupDumpStackTrap() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGUSR1)
go func() {
for range c {
psignal.DumpStacks("")
}
}()
}

View file

@ -0,0 +1,27 @@
package main
import (
"fmt"
"os"
"github.com/docker/docker/pkg/signal"
"github.com/sirupsen/logrus"
"golang.org/x/sys/windows"
)
// Copied over from docker/daemon/debugtrap_windows.go
func setupDumpStackTrap() {
go func() {
sa := windows.SecurityAttributes{
Length: 0,
}
ev, _ := windows.UTF16PtrFromString("Global\\docker-daemon-" + fmt.Sprint(os.Getpid()))
if h, _ := windows.CreateEvent(&sa, 0, 0, ev); h != 0 {
logrus.Debugf("Stackdump - waiting signal at %d", *ev)
for {
windows.WaitForSingleObject(h, windows.INFINITE)
signal.DumpStacks("")
}
}
}()
}

View file

@ -0,0 +1,87 @@
package main
import (
"fmt"
"os"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
var (
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 processFlags(c *cli.Context) error {
var err error
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)
}
logrus.SetLevel(lvl)
} else {
logrus.SetLevel(logrus.InfoLevel)
}
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,16 @@
title = "LibNetwork Configuration file"
[daemon]
debug = false
[cluster]
discovery = "consul://localhost:8500"
Address = "1.1.1.1"
Heartbeat = 20
[datastore]
embedded = false
[datastore.client]
provider = "consul"
Address = "localhost:8500"
[orchestration]
agent = true
peer="2.2.2.2"

View file

@ -0,0 +1,9 @@
FROM alpine
RUN apk --no-cache add curl
COPY testMain /app/
WORKDIR app
ENTRYPOINT ["/app/testMain"]

View file

@ -0,0 +1,15 @@
SERVER
cd test/networkdb
env GOOS=linux go build -v testMain.go && docker build -t dockereng/e2e-networkdb .
(only for testkit case) docker push dockereng/e2e-networkdb
Run server: docker service create --name testdb --network net1 --replicas 3 --env TASK_ID="{{.Task.ID}}" -p mode=host,target=8000 dockereng/e2e-networkdb server 8000
CLIENT
cd test/networkdb
Join cluster: docker run -it --network net1 dockereng/e2e-networkdb client join testdb 8000
Join network: docker run -it --network net1 dockereng/e2e-networkdb client join-network testdb 8000 test
Run test: docker run -it --network net1 dockereng/e2e-networkdb client write-delete-unique-keys testdb 8000 test tableBla 3 10
check table: curl "localhost:32768/gettable?nid=test&tname=table_name"

View file

@ -0,0 +1,861 @@
package dbclient
import (
"context"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"time"
"github.com/sirupsen/logrus"
)
var servicePort string
const totalWrittenKeys string = "totalKeys"
type resultTuple struct {
id string
result int
}
func httpGetFatalError(ip, port, path string) {
body, err := httpGet(ip, port, path)
if err != nil || !strings.Contains(string(body), "OK") {
log.Fatalf("[%s] error %s %s", path, err, body)
}
}
func httpGet(ip, port, path string) ([]byte, error) {
resp, err := http.Get("http://" + ip + ":" + port + path)
if err != nil {
logrus.Errorf("httpGet error:%s", err)
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
return body, err
}
func joinCluster(ip, port string, members []string, doneCh chan resultTuple) {
httpGetFatalError(ip, port, "/join?members="+strings.Join(members, ","))
if doneCh != nil {
doneCh <- resultTuple{id: ip, result: 0}
}
}
func joinNetwork(ip, port, network string, doneCh chan resultTuple) {
httpGetFatalError(ip, port, "/joinnetwork?nid="+network)
if doneCh != nil {
doneCh <- resultTuple{id: ip, result: 0}
}
}
func leaveNetwork(ip, port, network string, doneCh chan resultTuple) {
httpGetFatalError(ip, port, "/leavenetwork?nid="+network)
if doneCh != nil {
doneCh <- resultTuple{id: ip, result: 0}
}
}
func writeTableKey(ip, port, networkName, tableName, key string) {
createPath := "/createentry?unsafe&nid=" + networkName + "&tname=" + tableName + "&value=v&key="
httpGetFatalError(ip, port, createPath+key)
}
func deleteTableKey(ip, port, networkName, tableName, key string) {
deletePath := "/deleteentry?nid=" + networkName + "&tname=" + tableName + "&key="
httpGetFatalError(ip, port, deletePath+key)
}
func clusterPeersNumber(ip, port string, doneCh chan resultTuple) {
body, err := httpGet(ip, port, "/clusterpeers")
if err != nil {
logrus.Errorf("clusterPeers %s there was an error: %s", ip, err)
doneCh <- resultTuple{id: ip, result: -1}
return
}
peersRegexp := regexp.MustCompile(`total entries: ([0-9]+)`)
peersNum, _ := strconv.Atoi(peersRegexp.FindStringSubmatch(string(body))[1])
doneCh <- resultTuple{id: ip, result: peersNum}
}
func networkPeersNumber(ip, port, networkName string, doneCh chan resultTuple) {
body, err := httpGet(ip, port, "/networkpeers?nid="+networkName)
if err != nil {
logrus.Errorf("networkPeersNumber %s there was an error: %s", ip, err)
doneCh <- resultTuple{id: ip, result: -1}
return
}
peersRegexp := regexp.MustCompile(`total entries: ([0-9]+)`)
peersNum, _ := strconv.Atoi(peersRegexp.FindStringSubmatch(string(body))[1])
doneCh <- resultTuple{id: ip, result: peersNum}
}
func dbTableEntriesNumber(ip, port, networkName, tableName string, doneCh chan resultTuple) {
body, err := httpGet(ip, port, "/gettable?nid="+networkName+"&tname="+tableName)
if err != nil {
logrus.Errorf("tableEntriesNumber %s there was an error: %s", ip, err)
doneCh <- resultTuple{id: ip, result: -1}
return
}
elementsRegexp := regexp.MustCompile(`total entries: ([0-9]+)`)
entriesNum, _ := strconv.Atoi(elementsRegexp.FindStringSubmatch(string(body))[1])
doneCh <- resultTuple{id: ip, result: entriesNum}
}
func dbQueueLength(ip, port, networkName string, doneCh chan resultTuple) {
body, err := httpGet(ip, port, "/networkstats?nid="+networkName)
if err != nil {
logrus.Errorf("queueLength %s there was an error: %s", ip, err)
doneCh <- resultTuple{id: ip, result: -1}
return
}
elementsRegexp := regexp.MustCompile(`qlen: ([0-9]+)`)
entriesNum, _ := strconv.Atoi(elementsRegexp.FindStringSubmatch(string(body))[1])
doneCh <- resultTuple{id: ip, result: entriesNum}
}
func clientWatchTable(ip, port, networkName, tableName string, doneCh chan resultTuple) {
httpGetFatalError(ip, port, "/watchtable?nid="+networkName+"&tname="+tableName)
if doneCh != nil {
doneCh <- resultTuple{id: ip, result: 0}
}
}
func clientTableEntriesNumber(ip, port, networkName, tableName string, doneCh chan resultTuple) {
body, err := httpGet(ip, port, "/watchedtableentries?nid="+networkName+"&tname="+tableName)
if err != nil {
logrus.Errorf("clientTableEntriesNumber %s there was an error: %s", ip, err)
doneCh <- resultTuple{id: ip, result: -1}
return
}
elementsRegexp := regexp.MustCompile(`total elements: ([0-9]+)`)
entriesNum, _ := strconv.Atoi(elementsRegexp.FindStringSubmatch(string(body))[1])
doneCh <- resultTuple{id: ip, result: entriesNum}
}
func writeKeysNumber(ip, port, networkName, tableName, key string, number int, doneCh chan resultTuple) {
x := 0
for ; x < number; x++ {
k := key + strconv.Itoa(x)
// write key
writeTableKey(ip, port, networkName, tableName, k)
}
doneCh <- resultTuple{id: ip, result: x}
}
func deleteKeysNumber(ip, port, networkName, tableName, key string, number int, doneCh chan resultTuple) {
x := 0
for ; x < number; x++ {
k := key + strconv.Itoa(x)
// write key
deleteTableKey(ip, port, networkName, tableName, k)
}
doneCh <- resultTuple{id: ip, result: x}
}
func writeUniqueKeys(ctx context.Context, ip, port, networkName, tableName, key string, doneCh chan resultTuple) {
for x := 0; ; x++ {
select {
case <-ctx.Done():
doneCh <- resultTuple{id: ip, result: x}
return
default:
k := key + strconv.Itoa(x)
// write key
writeTableKey(ip, port, networkName, tableName, k)
// give time to send out key writes
time.Sleep(100 * time.Millisecond)
}
}
}
func writeDeleteUniqueKeys(ctx context.Context, ip, port, networkName, tableName, key string, doneCh chan resultTuple) {
for x := 0; ; x++ {
select {
case <-ctx.Done():
doneCh <- resultTuple{id: ip, result: x}
return
default:
k := key + strconv.Itoa(x)
// write key
writeTableKey(ip, port, networkName, tableName, k)
// give time to send out key writes
time.Sleep(100 * time.Millisecond)
// delete key
deleteTableKey(ip, port, networkName, tableName, k)
}
}
}
func writeDeleteLeaveJoin(ctx context.Context, ip, port, networkName, tableName, key string, doneCh chan resultTuple) {
for x := 0; ; x++ {
select {
case <-ctx.Done():
doneCh <- resultTuple{id: ip, result: x}
return
default:
k := key + strconv.Itoa(x)
// write key
writeTableKey(ip, port, networkName, tableName, k)
time.Sleep(100 * time.Millisecond)
// delete key
deleteTableKey(ip, port, networkName, tableName, k)
// give some time
time.Sleep(100 * time.Millisecond)
// leave network
leaveNetwork(ip, port, networkName, nil)
// join network
joinNetwork(ip, port, networkName, nil)
}
}
}
func ready(ip, port string, doneCh chan resultTuple) {
for {
body, err := httpGet(ip, port, "/ready")
if err != nil || !strings.Contains(string(body), "OK") {
time.Sleep(500 * time.Millisecond)
continue
}
// success
break
}
// notify the completion
doneCh <- resultTuple{id: ip, result: 0}
}
func checkTable(ctx context.Context, ips []string, port, networkName, tableName string, expectedEntries int, fn func(string, string, string, string, chan resultTuple)) (opTime time.Duration) {
startTime := time.Now().UnixNano()
var successTime int64
// Loop for 2 minutes to guarantee that the result is stable
for {
select {
case <-ctx.Done():
// Validate test success, if the time is set means that all the tables are empty
if successTime != 0 {
opTime = time.Duration(successTime-startTime) / time.Millisecond
logrus.Infof("Check table passed, the cluster converged in %d msec", opTime)
return
}
log.Fatal("Test failed, there is still entries in the tables of the nodes")
default:
logrus.Infof("Checking table %s expected %d", tableName, expectedEntries)
doneCh := make(chan resultTuple, len(ips))
for _, ip := range ips {
go fn(ip, servicePort, networkName, tableName, doneCh)
}
nodesWithCorrectEntriesNum := 0
for i := len(ips); i > 0; i-- {
tableEntries := <-doneCh
logrus.Infof("Node %s has %d entries", tableEntries.id, tableEntries.result)
if tableEntries.result == expectedEntries {
nodesWithCorrectEntriesNum++
}
}
close(doneCh)
if nodesWithCorrectEntriesNum == len(ips) {
if successTime == 0 {
successTime = time.Now().UnixNano()
logrus.Infof("Success after %d msec", time.Duration(successTime-startTime)/time.Millisecond)
}
} else {
successTime = 0
}
time.Sleep(10 * time.Second)
}
}
}
func waitWriters(parallelWriters int, mustWrite bool, doneCh chan resultTuple) map[string]int {
var totalKeys int
resultTable := make(map[string]int)
for i := 0; i < parallelWriters; i++ {
logrus.Infof("Waiting for %d workers", parallelWriters-i)
workerReturn := <-doneCh
totalKeys += workerReturn.result
if mustWrite && workerReturn.result == 0 {
log.Fatalf("The worker %s did not write any key %d == 0", workerReturn.id, workerReturn.result)
}
if !mustWrite && workerReturn.result != 0 {
log.Fatalf("The worker %s was supposed to return 0 instead %d != 0", workerReturn.id, workerReturn.result)
}
if mustWrite {
resultTable[workerReturn.id] = workerReturn.result
logrus.Infof("The worker %s wrote %d keys", workerReturn.id, workerReturn.result)
}
}
resultTable[totalWrittenKeys] = totalKeys
return resultTable
}
// ready
func doReady(ips []string) {
doneCh := make(chan resultTuple, len(ips))
// check all the nodes
for _, ip := range ips {
go ready(ip, servicePort, doneCh)
}
// wait for the readiness of all nodes
for i := len(ips); i > 0; i-- {
<-doneCh
}
close(doneCh)
}
// join
func doJoin(ips []string) {
doneCh := make(chan resultTuple, len(ips))
// check all the nodes
for i, ip := range ips {
members := append([]string(nil), ips[:i]...)
members = append(members, ips[i+1:]...)
go joinCluster(ip, servicePort, members, doneCh)
}
// wait for the readiness of all nodes
for i := len(ips); i > 0; i-- {
<-doneCh
}
close(doneCh)
}
// cluster-peers expectedNumberPeers maxRetry
func doClusterPeers(ips []string, args []string) {
doneCh := make(chan resultTuple, len(ips))
expectedPeers, _ := strconv.Atoi(args[0])
maxRetry, _ := strconv.Atoi(args[1])
for retry := 0; retry < maxRetry; retry++ {
// check all the nodes
for _, ip := range ips {
go clusterPeersNumber(ip, servicePort, doneCh)
}
var failed bool
// wait for the readiness of all nodes
for i := len(ips); i > 0; i-- {
node := <-doneCh
if node.result != expectedPeers {
failed = true
if retry == maxRetry-1 {
log.Fatalf("Expected peers from %s mismatch %d != %d", node.id, expectedPeers, node.result)
} else {
logrus.Warnf("Expected peers from %s mismatch %d != %d", node.id, expectedPeers, node.result)
}
time.Sleep(1 * time.Second)
}
}
// check if needs retry
if !failed {
break
}
}
close(doneCh)
}
// join-network networkName
func doJoinNetwork(ips []string, args []string) {
doneCh := make(chan resultTuple, len(ips))
// check all the nodes
for _, ip := range ips {
go joinNetwork(ip, servicePort, args[0], doneCh)
}
// wait for the readiness of all nodes
for i := len(ips); i > 0; i-- {
<-doneCh
}
close(doneCh)
}
// leave-network networkName
func doLeaveNetwork(ips []string, args []string) {
doneCh := make(chan resultTuple, len(ips))
// check all the nodes
for _, ip := range ips {
go leaveNetwork(ip, servicePort, args[0], doneCh)
}
// wait for the readiness of all nodes
for i := len(ips); i > 0; i-- {
<-doneCh
}
close(doneCh)
}
// network-peers networkName expectedNumberPeers maxRetry
func doNetworkPeers(ips []string, args []string) {
doneCh := make(chan resultTuple, len(ips))
networkName := args[0]
expectedPeers, _ := strconv.Atoi(args[1])
maxRetry, _ := strconv.Atoi(args[2])
for retry := 0; retry < maxRetry; retry++ {
// check all the nodes
for _, ip := range ips {
go networkPeersNumber(ip, servicePort, networkName, doneCh)
}
var failed bool
// wait for the readiness of all nodes
for i := len(ips); i > 0; i-- {
node := <-doneCh
if node.result != expectedPeers {
failed = true
if retry == maxRetry-1 {
log.Fatalf("Expected peers from %s mismatch %d != %d", node.id, expectedPeers, node.result)
} else {
logrus.Warnf("Expected peers from %s mismatch %d != %d", node.id, expectedPeers, node.result)
}
time.Sleep(1 * time.Second)
}
}
// check if needs retry
if !failed {
break
}
}
close(doneCh)
}
// network-stats-queue networkName <gt/lt> queueSize
func doNetworkStatsQueue(ips []string, args []string) {
doneCh := make(chan resultTuple, len(ips))
networkName := args[0]
comparison := args[1]
size, _ := strconv.Atoi(args[2])
// check all the nodes
for _, ip := range ips {
go dbQueueLength(ip, servicePort, networkName, doneCh)
}
var avgQueueSize int
// wait for the readiness of all nodes
for i := len(ips); i > 0; i-- {
node := <-doneCh
switch comparison {
case "lt":
if node.result > size {
log.Fatalf("Expected queue size from %s to be %d < %d", node.id, node.result, size)
}
case "gt":
if node.result < size {
log.Fatalf("Expected queue size from %s to be %d > %d", node.id, node.result, size)
}
default:
log.Fatal("unknown comparison operator")
}
avgQueueSize += node.result
}
close(doneCh)
avgQueueSize /= len(ips)
fmt.Fprintf(os.Stderr, "doNetworkStatsQueue succeeded with avg queue:%d", avgQueueSize)
}
// write-keys networkName tableName parallelWriters numberOfKeysEach
func doWriteKeys(ips []string, args []string) {
networkName := args[0]
tableName := args[1]
parallelWriters, _ := strconv.Atoi(args[2])
numberOfKeys, _ := strconv.Atoi(args[3])
doneCh := make(chan resultTuple, parallelWriters)
// Enable watch of tables from clients
for i := 0; i < parallelWriters; i++ {
go clientWatchTable(ips[i], servicePort, networkName, tableName, doneCh)
}
waitWriters(parallelWriters, false, doneCh)
// Start parallel writers that will create and delete unique keys
defer close(doneCh)
for i := 0; i < parallelWriters; i++ {
key := "key-" + strconv.Itoa(i) + "-"
logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
go writeKeysNumber(ips[i], servicePort, networkName, tableName, key, numberOfKeys, doneCh)
}
// Sync with all the writers
keyMap := waitWriters(parallelWriters, true, doneCh)
logrus.Infof("Written a total of %d keys on the cluster", keyMap[totalWrittenKeys])
// check table entries for 2 minutes
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
opTime := checkTable(ctx, ips, servicePort, networkName, tableName, keyMap[totalWrittenKeys], dbTableEntriesNumber)
cancel()
fmt.Fprintf(os.Stderr, "doWriteKeys succeeded in %d msec", opTime)
}
// delete-keys networkName tableName parallelWriters numberOfKeysEach
func doDeleteKeys(ips []string, args []string) {
networkName := args[0]
tableName := args[1]
parallelWriters, _ := strconv.Atoi(args[2])
numberOfKeys, _ := strconv.Atoi(args[3])
doneCh := make(chan resultTuple, parallelWriters)
// Enable watch of tables from clients
for i := 0; i < parallelWriters; i++ {
go clientWatchTable(ips[i], servicePort, networkName, tableName, doneCh)
}
waitWriters(parallelWriters, false, doneCh)
// Start parallel writers that will create and delete unique keys
defer close(doneCh)
for i := 0; i < parallelWriters; i++ {
key := "key-" + strconv.Itoa(i) + "-"
logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
go deleteKeysNumber(ips[i], servicePort, networkName, tableName, key, numberOfKeys, doneCh)
}
// Sync with all the writers
keyMap := waitWriters(parallelWriters, true, doneCh)
logrus.Infof("Written a total of %d keys on the cluster", keyMap[totalWrittenKeys])
// check table entries for 2 minutes
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
opTime := checkTable(ctx, ips, servicePort, networkName, tableName, 0, dbTableEntriesNumber)
cancel()
fmt.Fprintf(os.Stderr, "doDeletekeys succeeded in %d msec", opTime)
}
// write-delete-unique-keys networkName tableName numParallelWriters writeTimeSec
func doWriteDeleteUniqueKeys(ips []string, args []string) {
networkName := args[0]
tableName := args[1]
parallelWriters, _ := strconv.Atoi(args[2])
writeTimeSec, _ := strconv.Atoi(args[3])
doneCh := make(chan resultTuple, parallelWriters)
// Enable watch of tables from clients
for i := 0; i < parallelWriters; i++ {
go clientWatchTable(ips[i], servicePort, networkName, tableName, doneCh)
}
waitWriters(parallelWriters, false, doneCh)
// Start parallel writers that will create and delete unique keys
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(writeTimeSec)*time.Second)
for i := 0; i < parallelWriters; i++ {
key := "key-" + strconv.Itoa(i) + "-"
logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
go writeDeleteUniqueKeys(ctx, ips[i], servicePort, networkName, tableName, key, doneCh)
}
// Sync with all the writers
keyMap := waitWriters(parallelWriters, true, doneCh)
cancel()
logrus.Infof("Written a total of %d keys on the cluster", keyMap[totalWrittenKeys])
// check table entries for 2 minutes
ctx, cancel = context.WithTimeout(context.Background(), 2*time.Minute)
opDBTime := checkTable(ctx, ips, servicePort, networkName, tableName, 0, dbTableEntriesNumber)
cancel()
ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second)
opClientTime := checkTable(ctx, ips, servicePort, networkName, tableName, 0, clientTableEntriesNumber)
cancel()
fmt.Fprintf(os.Stderr, "doWriteDeleteUniqueKeys succeeded in %d msec and client %d msec", opDBTime, opClientTime)
}
// write-unique-keys networkName tableName numParallelWriters writeTimeSec
func doWriteUniqueKeys(ips []string, args []string) {
networkName := args[0]
tableName := args[1]
parallelWriters, _ := strconv.Atoi(args[2])
writeTimeSec, _ := strconv.Atoi(args[3])
doneCh := make(chan resultTuple, parallelWriters)
// Enable watch of tables from clients
for i := 0; i < parallelWriters; i++ {
go clientWatchTable(ips[i], servicePort, networkName, tableName, doneCh)
}
waitWriters(parallelWriters, false, doneCh)
// Start parallel writers that will create and delete unique keys
defer close(doneCh)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(writeTimeSec)*time.Second)
for i := 0; i < parallelWriters; i++ {
key := "key-" + strconv.Itoa(i) + "-"
logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
go writeUniqueKeys(ctx, ips[i], servicePort, networkName, tableName, key, doneCh)
}
// Sync with all the writers
keyMap := waitWriters(parallelWriters, true, doneCh)
cancel()
logrus.Infof("Written a total of %d keys on the cluster", keyMap[totalWrittenKeys])
// check table entries for 2 minutes
ctx, cancel = context.WithTimeout(context.Background(), 2*time.Minute)
opTime := checkTable(ctx, ips, servicePort, networkName, tableName, keyMap[totalWrittenKeys], dbTableEntriesNumber)
cancel()
fmt.Fprintf(os.Stderr, "doWriteUniqueKeys succeeded in %d msec", opTime)
}
// write-delete-leave-join networkName tableName numParallelWriters writeTimeSec
func doWriteDeleteLeaveJoin(ips []string, args []string) {
networkName := args[0]
tableName := args[1]
parallelWriters, _ := strconv.Atoi(args[2])
writeTimeSec, _ := strconv.Atoi(args[3])
// Start parallel writers that will create and delete unique keys
doneCh := make(chan resultTuple, parallelWriters)
defer close(doneCh)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(writeTimeSec)*time.Second)
for i := 0; i < parallelWriters; i++ {
key := "key-" + strconv.Itoa(i) + "-"
logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
go writeDeleteLeaveJoin(ctx, ips[i], servicePort, networkName, tableName, key, doneCh)
}
// Sync with all the writers
keyMap := waitWriters(parallelWriters, true, doneCh)
cancel()
logrus.Infof("Written a total of %d keys on the cluster", keyMap["totalKeys"])
// check table entries for 2 minutes
ctx, cancel = context.WithTimeout(context.Background(), 2*time.Minute)
opTime := checkTable(ctx, ips, servicePort, networkName, tableName, 0, dbTableEntriesNumber)
cancel()
fmt.Fprintf(os.Stderr, "doWriteDeleteLeaveJoin succeeded in %d msec", opTime)
}
// write-delete-wait-leave-join networkName tableName numParallelWriters writeTimeSec
func doWriteDeleteWaitLeaveJoin(ips []string, args []string) {
networkName := args[0]
tableName := args[1]
parallelWriters, _ := strconv.Atoi(args[2])
writeTimeSec, _ := strconv.Atoi(args[3])
// Start parallel writers that will create and delete unique keys
doneCh := make(chan resultTuple, parallelWriters)
defer close(doneCh)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(writeTimeSec)*time.Second)
for i := 0; i < parallelWriters; i++ {
key := "key-" + strconv.Itoa(i) + "-"
logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
go writeDeleteUniqueKeys(ctx, ips[i], servicePort, networkName, tableName, key, doneCh)
}
// Sync with all the writers
keyMap := waitWriters(parallelWriters, true, doneCh)
cancel()
logrus.Infof("Written a total of %d keys on the cluster", keyMap[totalWrittenKeys])
// The writers will leave the network
for i := 0; i < parallelWriters; i++ {
logrus.Infof("worker leaveNetwork: %d on IP:%s", i, ips[i])
go leaveNetwork(ips[i], servicePort, networkName, doneCh)
}
waitWriters(parallelWriters, false, doneCh)
// Give some time
time.Sleep(100 * time.Millisecond)
// The writers will join the network
for i := 0; i < parallelWriters; i++ {
logrus.Infof("worker joinNetwork: %d on IP:%s", i, ips[i])
go joinNetwork(ips[i], servicePort, networkName, doneCh)
}
waitWriters(parallelWriters, false, doneCh)
// check table entries for 2 minutes
ctx, cancel = context.WithTimeout(context.Background(), 2*time.Minute)
opTime := checkTable(ctx, ips, servicePort, networkName, tableName, 0, dbTableEntriesNumber)
cancel()
fmt.Fprintf(os.Stderr, "doWriteDeleteWaitLeaveJoin succeeded in %d msec", opTime)
}
// write-wait-leave networkName tableName numParallelWriters writeTimeSec
func doWriteWaitLeave(ips []string, args []string) {
networkName := args[0]
tableName := args[1]
parallelWriters, _ := strconv.Atoi(args[2])
writeTimeSec, _ := strconv.Atoi(args[3])
// Start parallel writers that will create and delete unique keys
doneCh := make(chan resultTuple, parallelWriters)
defer close(doneCh)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(writeTimeSec)*time.Second)
for i := 0; i < parallelWriters; i++ {
key := "key-" + strconv.Itoa(i) + "-"
logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
go writeUniqueKeys(ctx, ips[i], servicePort, networkName, tableName, key, doneCh)
}
// Sync with all the writers
keyMap := waitWriters(parallelWriters, true, doneCh)
cancel()
logrus.Infof("Written a total of %d keys on the cluster", keyMap[totalWrittenKeys])
// The writers will leave the network
for i := 0; i < parallelWriters; i++ {
logrus.Infof("worker leaveNetwork: %d on IP:%s", i, ips[i])
go leaveNetwork(ips[i], servicePort, networkName, doneCh)
}
waitWriters(parallelWriters, false, doneCh)
// check table entries for 2 minutes
ctx, cancel = context.WithTimeout(context.Background(), 2*time.Minute)
opTime := checkTable(ctx, ips, servicePort, networkName, tableName, 0, dbTableEntriesNumber)
cancel()
fmt.Fprintf(os.Stderr, "doWriteLeaveJoin succeeded in %d msec", opTime)
}
// write-wait-leave-join networkName tableName numParallelWriters writeTimeSec numParallelLeaver
func doWriteWaitLeaveJoin(ips []string, args []string) {
networkName := args[0]
tableName := args[1]
parallelWriters, _ := strconv.Atoi(args[2])
writeTimeSec, _ := strconv.Atoi(args[3])
parallelLeaver, _ := strconv.Atoi(args[4])
// Start parallel writers that will create and delete unique keys
doneCh := make(chan resultTuple, parallelWriters)
defer close(doneCh)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(writeTimeSec)*time.Second)
for i := 0; i < parallelWriters; i++ {
key := "key-" + strconv.Itoa(i) + "-"
logrus.Infof("Spawn worker: %d on IP:%s", i, ips[i])
go writeUniqueKeys(ctx, ips[i], servicePort, networkName, tableName, key, doneCh)
}
// Sync with all the writers
keyMap := waitWriters(parallelWriters, true, doneCh)
cancel()
logrus.Infof("Written a total of %d keys on the cluster", keyMap[totalWrittenKeys])
keysExpected := keyMap[totalWrittenKeys]
// The Leavers will leave the network
for i := 0; i < parallelLeaver; i++ {
logrus.Infof("worker leaveNetwork: %d on IP:%s", i, ips[i])
go leaveNetwork(ips[i], servicePort, networkName, doneCh)
// Once a node leave all the keys written previously will be deleted, so the expected keys will consider that as removed
keysExpected -= keyMap[ips[i]]
}
waitWriters(parallelLeaver, false, doneCh)
// Give some time
time.Sleep(100 * time.Millisecond)
// The writers will join the network
for i := 0; i < parallelLeaver; i++ {
logrus.Infof("worker joinNetwork: %d on IP:%s", i, ips[i])
go joinNetwork(ips[i], servicePort, networkName, doneCh)
}
waitWriters(parallelLeaver, false, doneCh)
// check table entries for 2 minutes
ctx, cancel = context.WithTimeout(context.Background(), 2*time.Minute)
opTime := checkTable(ctx, ips, servicePort, networkName, tableName, keysExpected, dbTableEntriesNumber)
cancel()
fmt.Fprintf(os.Stderr, "doWriteWaitLeaveJoin succeeded in %d msec", opTime)
}
var cmdArgChec = map[string]int{
"debug": 0,
"fail": 0,
"ready": 2,
"join": 2,
"leave": 2,
"join-network": 3,
"leave-network": 3,
"cluster-peers": 5,
"network-peers": 5,
"write-delete-unique-keys": 7,
}
// Client is a client
func Client(args []string) {
logrus.Infof("[CLIENT] Starting with arguments %v", args)
command := args[0]
if len(args) < cmdArgChec[command] {
log.Fatalf("Command %s requires %d arguments, passed %d, aborting...", command, cmdArgChec[command], len(args))
}
switch command {
case "debug":
time.Sleep(1 * time.Hour)
os.Exit(0)
case "fail":
log.Fatalf("Test error condition with message: error error error")
}
serviceName := args[1]
ips, _ := net.LookupHost("tasks." + serviceName)
logrus.Infof("got the ips %v", ips)
if len(ips) == 0 {
log.Fatalf("Cannot resolve any IP for the service tasks.%s", serviceName)
}
servicePort = args[2]
commandArgs := args[3:]
logrus.Infof("Executing %s with args:%v", command, commandArgs)
switch command {
case "ready":
doReady(ips)
case "join":
doJoin(ips)
case "leave":
case "cluster-peers":
// cluster-peers maxRetry
doClusterPeers(ips, commandArgs)
case "join-network":
// join-network networkName
doJoinNetwork(ips, commandArgs)
case "leave-network":
// leave-network networkName
doLeaveNetwork(ips, commandArgs)
case "network-peers":
// network-peers networkName expectedNumberPeers maxRetry
doNetworkPeers(ips, commandArgs)
// case "network-stats-entries":
// // network-stats-entries networkName maxRetry
// doNetworkPeers(ips, commandArgs)
case "network-stats-queue":
// network-stats-queue networkName <lt/gt> queueSize
doNetworkStatsQueue(ips, commandArgs)
case "write-keys":
// write-keys networkName tableName parallelWriters numberOfKeysEach
doWriteKeys(ips, commandArgs)
case "delete-keys":
// delete-keys networkName tableName parallelWriters numberOfKeysEach
doDeleteKeys(ips, commandArgs)
case "write-unique-keys":
// write-delete-unique-keys networkName tableName numParallelWriters writeTimeSec
doWriteUniqueKeys(ips, commandArgs)
case "write-delete-unique-keys":
// write-delete-unique-keys networkName tableName numParallelWriters writeTimeSec
doWriteDeleteUniqueKeys(ips, commandArgs)
case "write-delete-leave-join":
// write-delete-leave-join networkName tableName numParallelWriters writeTimeSec
doWriteDeleteLeaveJoin(ips, commandArgs)
case "write-delete-wait-leave-join":
// write-delete-wait-leave-join networkName tableName numParallelWriters writeTimeSec
doWriteDeleteWaitLeaveJoin(ips, commandArgs)
case "write-wait-leave":
// write-wait-leave networkName tableName numParallelWriters writeTimeSec
doWriteWaitLeave(ips, commandArgs)
case "write-wait-leave-join":
// write-wait-leave networkName tableName numParallelWriters writeTimeSec
doWriteWaitLeaveJoin(ips, commandArgs)
default:
log.Fatalf("Command %s not recognized", command)
}
}

View file

@ -0,0 +1,111 @@
package dbserver
import (
"errors"
"fmt"
"log"
"net"
"net/http"
"os"
"strconv"
"github.com/docker/docker/libnetwork/cmd/networkdb-test/dummyclient"
"github.com/docker/docker/libnetwork/diagnostic"
"github.com/docker/docker/libnetwork/networkdb"
"github.com/sirupsen/logrus"
)
var nDB *networkdb.NetworkDB
var server *diagnostic.Server
var ipAddr string
var testerPaths2Func = map[string]diagnostic.HTTPHandlerFunc{
"/myip": ipaddress,
}
func ipaddress(ctx interface{}, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s\n", ipAddr)
}
// Server starts the server
func Server(args []string) {
logrus.Infof("[SERVER] Starting with arguments %v", args)
if len(args) < 1 {
log.Fatal("Port number is a mandatory argument, aborting...")
}
port, _ := strconv.Atoi(args[0])
var localNodeName string
var ok bool
if localNodeName, ok = os.LookupEnv("TASK_ID"); !ok {
log.Fatal("TASK_ID environment variable not set, aborting...")
}
logrus.Infof("[SERVER] Starting node %s on port %d", localNodeName, port)
ip, err := getIPInterface("eth0")
if err != nil {
logrus.Errorf("%s There was a problem with the IP %s\n", localNodeName, err)
return
}
ipAddr = ip
logrus.Infof("%s uses IP %s\n", localNodeName, ipAddr)
server = diagnostic.New()
server.Init()
conf := networkdb.DefaultConfig()
conf.Hostname = localNodeName
conf.AdvertiseAddr = ipAddr
conf.BindAddr = ipAddr
nDB, err = networkdb.New(conf)
if err != nil {
logrus.Infof("%s error in the DB init %s\n", localNodeName, err)
return
}
// Register network db handlers
server.RegisterHandler(nDB, networkdb.NetDbPaths2Func)
server.RegisterHandler(nil, testerPaths2Func)
server.RegisterHandler(nDB, dummyclient.DummyClientPaths2Func)
server.EnableDiagnostic("", port)
// block here
select {}
}
func getIPInterface(name string) (string, error) {
ifaces, err := net.Interfaces()
if err != nil {
return "", err
}
for _, iface := range ifaces {
if iface.Name != name {
continue // not the name specified
}
if iface.Flags&net.FlagUp == 0 {
return "", errors.New("Interfaces is down")
}
addrs, err := iface.Addrs()
if err != nil {
return "", err
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue
}
return ip.String(), nil
}
return "", errors.New("Interfaces does not have a valid IPv4")
}
return "", errors.New("Interface not found")
}

View file

@ -0,0 +1,120 @@
package dummyclient
import (
"fmt"
"log"
"net/http"
"github.com/docker/docker/libnetwork/diagnostic"
"github.com/docker/docker/libnetwork/networkdb"
events "github.com/docker/go-events"
"github.com/sirupsen/logrus"
)
// DummyClientPaths2Func exported paths for the client
var DummyClientPaths2Func = map[string]diagnostic.HTTPHandlerFunc{
"/watchtable": watchTable,
"/watchedtableentries": watchTableEntries,
}
const (
missingParameter = "missing parameter"
)
type tableHandler struct {
cancelWatch func()
entries map[string]string
}
var clientWatchTable = map[string]tableHandler{}
func watchTable(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm() // nolint:errcheck
diagnostic.DebugHTTPForm(r)
if len(r.Form["tname"]) < 1 {
rsp := diagnostic.WrongCommand(missingParameter, fmt.Sprintf("%s?tname=table_name", r.URL.Path))
diagnostic.HTTPReply(w, rsp, &diagnostic.JSONOutput{}) // nolint:errcheck
return
}
tableName := r.Form["tname"][0]
if _, ok := clientWatchTable[tableName]; ok {
fmt.Fprintf(w, "OK\n")
return
}
nDB, ok := ctx.(*networkdb.NetworkDB)
if ok {
ch, cancel := nDB.Watch(tableName, "", "")
clientWatchTable[tableName] = tableHandler{cancelWatch: cancel, entries: make(map[string]string)}
go handleTableEvents(tableName, ch)
fmt.Fprintf(w, "OK\n")
}
}
func watchTableEntries(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm() // nolint:errcheck
diagnostic.DebugHTTPForm(r)
if len(r.Form["tname"]) < 1 {
rsp := diagnostic.WrongCommand(missingParameter, fmt.Sprintf("%s?tname=table_name", r.URL.Path))
diagnostic.HTTPReply(w, rsp, &diagnostic.JSONOutput{}) // nolint:errcheck
return
}
tableName := r.Form["tname"][0]
table, ok := clientWatchTable[tableName]
if !ok {
fmt.Fprintf(w, "Table %s not watched\n", tableName)
return
}
fmt.Fprintf(w, "total elements: %d\n", len(table.entries))
i := 0
for k, v := range table.entries {
fmt.Fprintf(w, "%d) k:`%s` -> v:`%s`\n", i, k, v)
i++
}
}
func handleTableEvents(tableName string, ch *events.Channel) {
var (
// nid string
eid string
value []byte
isAdd bool
)
logrus.Infof("Started watching table:%s", tableName)
for {
select {
case <-ch.Done():
logrus.Infof("End watching %s", tableName)
return
case evt := <-ch.C:
logrus.Infof("Recevied new event on:%s", tableName)
switch event := evt.(type) {
case networkdb.CreateEvent:
// nid = event.NetworkID
eid = event.Key
value = event.Value
isAdd = true
case networkdb.DeleteEvent:
// nid = event.NetworkID
eid = event.Key
value = event.Value
isAdd = false
default:
log.Fatalf("Unexpected table event = %#v", event)
}
if isAdd {
// logrus.Infof("Add %s %s", tableName, eid)
clientWatchTable[tableName].entries[eid] = string(value)
} else {
// logrus.Infof("Del %s %s", tableName, eid)
delete(clientWatchTable[tableName].entries, eid)
}
}
}
}

View file

@ -0,0 +1,28 @@
package main
import (
"log"
"os"
"github.com/docker/docker/libnetwork/cmd/networkdb-test/dbclient"
"github.com/docker/docker/libnetwork/cmd/networkdb-test/dbserver"
"github.com/sirupsen/logrus"
)
func main() {
formatter := &logrus.TextFormatter{
FullTimestamp: true,
}
logrus.SetFormatter(formatter)
logrus.Infof("Starting the image with these args: %v", os.Args)
if len(os.Args) < 1 {
log.Fatal("You need at least 1 argument [client/server]")
}
switch os.Args[1] {
case "server":
dbserver.Server(os.Args[2:])
case "client":
dbclient.Client(os.Args[2:])
}
}

Some files were not shown because too many files have changed in this diff Show more