docker.io (1.8.3~ds1-2) unstable; urgency=medium
authorTianon Gravi <tianon@debian.org>
Wed, 4 Nov 2015 08:09:02 +0000 (08:09 +0000)
committerTianon Gravi <tianon@debian.org>
Wed, 4 Nov 2015 08:09:02 +0000 (08:09 +0000)
  * Move "overlay" higher in priority (Closes: #799087)
  * Adjust "native.cgroupdriver" to default to "cgroupfs" (Closes: #798778)

[dgit import unpatched docker.io 1.8.3~ds1-2]

212 files changed:
1  2  3 
debian/Dockerfile
debian/README.Debian
debian/changelog
debian/compat
debian/control
debian/copyright
debian/docker.io.bash-completion
debian/docker.io.docker.default
debian/docker.io.docker.init
debian/docker.io.docker.upstart
debian/docker.io.install
debian/docker.io.lintian-overrides
debian/docker.io.maintscript
debian/docker.io.manpages
debian/docker.io.postinst
debian/docker.io.udev
debian/docs
debian/gbp.conf
debian/helpers/download-libcontainer
debian/helpers/gitcommit.sh
debian/patches/15404.patch
debian/patches/cgroupdriver-cgroupfs.patch
debian/patches/cgroupfs-mount-convenience-copy.patch
debian/patches/change-system-unit-env-file.patch
debian/patches/check-v1.patch
debian/patches/distribution-2.1.1.patch
debian/patches/fatal-error-old-kernels.patch
debian/patches/overlay.patch
debian/patches/runc-0.0.4.patch
debian/patches/series
debian/repack.sh
debian/repack/prune/vendor
debian/rules
debian/source.lintian-overrides
debian/source/format
debian/tests/control
debian/tests/integration
debian/upstream-version-gitcommits
debian/vim-syntax-docker.install
debian/vim-syntax-docker.yaml
debian/watch
libnetwork/.gitignore
libnetwork/Godeps/Godeps.json
libnetwork/Godeps/Readme
libnetwork/LICENSE
libnetwork/MAINTAINERS
libnetwork/Makefile
libnetwork/README.md
libnetwork/ROADMAP.md
libnetwork/api/api.go
libnetwork/api/api_test.go
libnetwork/api/types.go
libnetwork/bitseq/sequence.go
libnetwork/bitseq/sequence_test.go
libnetwork/bitseq/store.go
libnetwork/circle.yml
libnetwork/client/client.go
libnetwork/client/client_service_test.go
libnetwork/client/client_test.go
libnetwork/client/network.go
libnetwork/client/service.go
libnetwork/client/types.go
libnetwork/cmd/dnet/dnet.go
libnetwork/cmd/dnet/dnet_test.go
libnetwork/cmd/dnet/flags.go
libnetwork/cmd/dnet/libnetwork.toml
libnetwork/cmd/ovrouter/ovrouter.go
libnetwork/cmd/readme_test/readme.go
libnetwork/cmd/test/libnetwork.toml
libnetwork/cmd/test/main.go
libnetwork/config/config.go
libnetwork/config/config_test.go
libnetwork/config/libnetwork.toml
libnetwork/controller.go
libnetwork/datastore/datastore.go
libnetwork/datastore/datastore_test.go
libnetwork/datastore/mock_store.go
libnetwork/docs/Vagrantfile
libnetwork/docs/bridge.md
libnetwork/docs/design.md
libnetwork/docs/legacy.md
libnetwork/docs/overlay.md
libnetwork/docs/remote.md
libnetwork/docs/vagrant.md
libnetwork/driverapi/driverapi.go
libnetwork/driverapi/errors.go
libnetwork/drivers/bridge/bridge.go
libnetwork/drivers/bridge/bridge_test.go
libnetwork/drivers/bridge/errors.go
libnetwork/drivers/bridge/interface.go
libnetwork/drivers/bridge/interface_test.go
libnetwork/drivers/bridge/link.go
libnetwork/drivers/bridge/link_test.go
libnetwork/drivers/bridge/netlink_deprecated_linux.go
libnetwork/drivers/bridge/netlink_deprecated_linux_armppc64.go
libnetwork/drivers/bridge/netlink_deprecated_linux_notarm.go
libnetwork/drivers/bridge/netlink_deprecated_unsupported.go
libnetwork/drivers/bridge/network_test.go
libnetwork/drivers/bridge/port_mapping.go
libnetwork/drivers/bridge/port_mapping_test.go
libnetwork/drivers/bridge/resolvconf.go
libnetwork/drivers/bridge/resolvconf_test.go
libnetwork/drivers/bridge/setup.go
libnetwork/drivers/bridge/setup_bridgenetfiltering.go
libnetwork/drivers/bridge/setup_bridgenetfiltering_test.go
libnetwork/drivers/bridge/setup_device.go
libnetwork/drivers/bridge/setup_device_test.go
libnetwork/drivers/bridge/setup_firewalld.go
libnetwork/drivers/bridge/setup_fixedcidrv4.go
libnetwork/drivers/bridge/setup_fixedcidrv4_test.go
libnetwork/drivers/bridge/setup_fixedcidrv6.go
libnetwork/drivers/bridge/setup_fixedcidrv6_test.go
libnetwork/drivers/bridge/setup_ip_forwarding.go
libnetwork/drivers/bridge/setup_ip_forwarding_test.go
libnetwork/drivers/bridge/setup_ip_tables.go
libnetwork/drivers/bridge/setup_ip_tables_test.go
libnetwork/drivers/bridge/setup_ipv4.go
libnetwork/drivers/bridge/setup_ipv4_test.go
libnetwork/drivers/bridge/setup_ipv6.go
libnetwork/drivers/bridge/setup_ipv6_test.go
libnetwork/drivers/bridge/setup_verify.go
libnetwork/drivers/bridge/setup_verify_test.go
libnetwork/drivers/host/host.go
libnetwork/drivers/host/host_test.go
libnetwork/drivers/null/null.go
libnetwork/drivers/null/null_test.go
libnetwork/drivers/overlay/joinleave.go
libnetwork/drivers/overlay/ov_endpoint.go
libnetwork/drivers/overlay/ov_network.go
libnetwork/drivers/overlay/ov_serf.go
libnetwork/drivers/overlay/ov_utils.go
libnetwork/drivers/overlay/overlay.go
libnetwork/drivers/overlay/overlay_test.go
libnetwork/drivers/overlay/peerdb.go
libnetwork/drivers/remote/driver.go
libnetwork/drivers/remote/driver_test.go
libnetwork/drivers/remote/messages.go
libnetwork/drivers/windows/windows.go
libnetwork/drivers_freebsd.go
libnetwork/drivers_linux.go
libnetwork/drivers_windows.go
libnetwork/endpoint.go
libnetwork/endpoint_info.go
libnetwork/error.go
libnetwork/errors_test.go
libnetwork/etchosts/etchosts.go
libnetwork/etchosts/etchosts_test.go
libnetwork/hostdiscovery/hostdiscovery.go
libnetwork/hostdiscovery/hostdiscovery_api.go
libnetwork/hostdiscovery/hostdiscovery_disabled.go
libnetwork/hostdiscovery/hostdiscovery_test.go
libnetwork/hostdiscovery/libnetwork.toml
libnetwork/idm/idm.go
libnetwork/idm/idm_test.go
libnetwork/ipallocator/allocator.go
libnetwork/ipallocator/allocator_test.go
libnetwork/ipam/allocator.go
libnetwork/ipam/allocator_test.go
libnetwork/ipam/contract.go
libnetwork/ipam/store.go
libnetwork/iptables/firewalld.go
libnetwork/iptables/firewalld_test.go
libnetwork/iptables/iptables.go
libnetwork/iptables/iptables_test.go
libnetwork/libnetwork_internal_test.go
libnetwork/libnetwork_test.go
libnetwork/netlabel/labels.go
libnetwork/netutils/test_utils.go
libnetwork/netutils/utils.go
libnetwork/netutils/utils_test.go
libnetwork/network.go
libnetwork/options/options.go
libnetwork/options/options_test.go
libnetwork/portallocator/portallocator.go
libnetwork/portallocator/portallocator_test.go
libnetwork/portmapper/mapper.go
libnetwork/portmapper/mapper_test.go
libnetwork/portmapper/mock_proxy.go
libnetwork/portmapper/proxy.go
libnetwork/resolvconf/README.md
libnetwork/resolvconf/dns/resolvconf.go
libnetwork/resolvconf/resolvconf.go
libnetwork/resolvconf/resolvconf_test.go
libnetwork/sandbox/interface_freebsd.go
libnetwork/sandbox/interface_linux.go
libnetwork/sandbox/interface_windows.go
libnetwork/sandbox/namespace_linux.go
libnetwork/sandbox/namespace_unsupported.go
libnetwork/sandbox/namespace_windows.go
libnetwork/sandbox/neigh_freebsd.go
libnetwork/sandbox/neigh_linux.go
libnetwork/sandbox/neigh_windows.go
libnetwork/sandbox/options_linux.go
libnetwork/sandbox/route_linux.go
libnetwork/sandbox/sandbox.go
libnetwork/sandbox/sandbox_freebsd.go
libnetwork/sandbox/sandbox_linux_test.go
libnetwork/sandbox/sandbox_test.go
libnetwork/sandbox/sandbox_unsupported.go
libnetwork/sandbox/sandbox_unsupported_test.go
libnetwork/sandbox/test_freebsd.go
libnetwork/sandbox/test_linux.go
libnetwork/sandbox/test_windows.go
libnetwork/sandboxdata.go
libnetwork/sandboxdata_test.go
libnetwork/store.go
libnetwork/test/integration/README.md
libnetwork/test/integration/daemon-configs.bats
libnetwork/test/integration/daemon.cfg
libnetwork/test/integration/helpers.bash
libnetwork/types/types.go
libnetwork/types/types_test.go

index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..80639c66ca97c7b068d86e419d99527459d0437a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,21 @@@@
+++FROM tianon/debian-devel
+++
+++RUN echo 'deb http://incoming.debian.org/debian-buildd buildd-unstable main contrib non-free' > /etc/apt/sources.list.d/incoming.list
+++
+++# start by adding just "debian/control" so we can get mk-build-deps with maximum caching
+++COPY control /usr/src/docker.io/debian/
+++WORKDIR /usr/src/docker.io
+++
+++# get all the build deps of _this_ package in a nice repeatable way
+++RUN apt-get update && mk-build-deps -irt'apt-get --no-install-recommends -yV' debian/control && dpkg-checkbuilddeps
+++
+++# need our debian/ directory to compile _this_ package
+++COPY . /usr/src/docker.io/debian
+++
+++# go download and unpack our upstream source
+++RUN uscan --force-download --verbose --download-current-version
+++RUN DOCKER_TARBALLS=.. ./debian/helpers/download-libcontainer
+++RUN /tianon/extract-origtargz.sh
+++
+++# tianon is _really_ lazy, and likes a preseeded bash history
+++RUN echo '/tianon/extract-origtargz.sh && dpkg-buildpackage -us -uc && lintian -EvIL+pedantic' >> /root/.bash_history
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..2f41e350759ae1b2848a1eee38fb30c65916e2e8
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,34 @@@@
+++Docker on Debian
+++================
+++
+++To enable docker memory limitation, the kernel needs to be loaded with
+++boot parameters: cgroup_enable=memory swapaccount=1.
+++
+++This is because enabling memory cgroup support has some run-time overhead,
+++and kernel maintainers don't want to slow down systems unnecessarily.
+++
+++http://www.mail-archive.com/debian-bugs-dist@lists.debian.org/msg764104.html
+++https://github.com/docker/docker/issues/396
+++
+++To instruct the kernel to enable memory cgroup support, edit
+++/etc/default/grub and extend GRUB_CMDLINE_LINUX_DEFAULT like:
+++
+++GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1"
+++
+++Then run update-grub, and reboot.
+++
+++
+++As noted in the upstream documentation (https://docs.docker.io), Docker will
+++allow non-root users in the "docker" group to access "docker.sock" and thus
+++communicate with the daemon.  To add yourself to the "docker" group, use
+++something like:
+++
+++adduser YOURUSER docker
+++
+++As also noted in the upstream documentation, the "docker" group (and any other
+++means of accessing the Docker API) is root-equivalent.  If you don't trust a
+++user with root on your box, you shouldn't trust them with Docker either.  If you
+++are interested in further information about the security aspects of Docker,
+++please be sure to read the "Docker Security"
+++(http://docs.docker.io/en/latest/articles/security/) article in the
+++upstream documentation.
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..bd72cc54486a96ba3e2f95688bbf62b69ab35562
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,343 @@@@
+++docker.io (1.8.3~ds1-2) unstable; urgency=medium
+++
+++  * Move "overlay" higher in priority (Closes: #799087)
+++  * Adjust "native.cgroupdriver" to default to "cgroupfs" (Closes: #798778)
+++
+++ -- Tianon Gravi <tianon@debian.org>  Wed, 04 Nov 2015 00:09:02 -0800
+++
+++docker.io (1.8.3~ds1-1) unstable; urgency=medium
+++
+++  * Update to 1.8.3 upstream release (CVE-2014-8178, CVE-2014-8179)
+++
+++ -- Tianon Gravi <tianon@debian.org>  Thu, 29 Oct 2015 19:40:51 -0700
+++
+++docker.io (1.8.2~ds1-2) unstable; urgency=medium
+++
+++  * Swap Build-Depends order to appease buildds (Closes: #803136)
+++
+++ -- Tianon Gravi <tianon@debian.org>  Thu, 29 Oct 2015 07:23:10 -0700
+++
+++docker.io (1.8.2~ds1-1) unstable; urgency=medium
+++
+++  * Update to 1.8.2 upstream release
+++  * Rename golang-docker-dev package to golang-github-docker-docker-dev
+++  * Add SELinux support (Closes: #799620)
+++
+++ -- Tianon Gravi <tianon@debian.org>  Wed, 28 Oct 2015 14:21:00 -0700
+++
+++docker.io (1.7.1~dfsg1-1) unstable; urgency=medium
+++
+++  * Update to 1.7.1 upstream release
+++  * Remove patches applied upstream; refresh other patches
+++  * Update Build-Depends
+++
+++ -- Tianon Gravi <tianon@debian.org>  Wed, 26 Aug 2015 10:13:48 -0700
+++
+++docker.io (1.6.2~dfsg1-2) unstable; urgency=medium
+++
+++  * Add DEP8 tests
+++    - integration: runs upstream's integration tests
+++  * Replace "code.google.com/p/go.net" with canonical "golang.org/x/net"
+++    (Closes: #789736)
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Wed, 01 Jul 2015 07:45:19 -0600
+++
+++docker.io (1.6.2~dfsg1-1) unstable; urgency=medium
+++
+++  * Update to 1.6.2 upstream release
+++  * Update deps in d/control to match upstream's hack/vendor.sh specifications
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Thu, 21 May 2015 00:47:43 -0600
+++
+++docker.io (1.6.1+dfsg1-2) unstable; urgency=medium
+++
+++  * Add --no-restart-on-upgrade to dh_installinit so that we don't force
+++    a stop on upgrade, which can cause other units to fall over. Many thanks
+++    to Michael Stapelberg (sECuRE) for the tip!
+++
+++ -- Paul Tagliamonte <paultag@debian.org>  Sun, 10 May 2015 13:02:54 -0400
+++
+++docker.io (1.6.1+dfsg1-1) unstable; urgency=high
+++
+++  * Update to 1.6.1 upstream release (Closes: #784726)
+++    - CVE-2015-3627
+++      Insecure opening of file-descriptor 1 leading to privilege escalation
+++    - CVE-2015-3629
+++      Symlink traversal on container respawn allows local privilege escalation
+++    - CVE-2015-3630
+++      Read/write proc paths allow host modification & information disclosure
+++    - CVE-2015-3631
+++      Volume mounts allow LSM profile escalation
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Fri, 08 May 2015 17:57:10 -0600
+++
+++docker.io (1.6.0+dfsg1-1) unstable; urgency=medium
+++
+++  * Upload to unstable
+++  * Backport PR 12943 to support golang-go-patricia 2.*
+++  * Remove convenience copies of cgroupfs-mount in init.d / upstart scripts
+++    (Re: #783143)
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Tue, 05 May 2015 15:10:49 -0600
+++
+++docker.io (1.6.0+dfsg1-1~exp1) experimental; urgency=medium
+++
+++  * Update to 1.6.0 upstream release
+++  * Adjust "repack.sh" to be more tolerant of "dfsg" suffixes
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Thu, 16 Apr 2015 18:00:21 -0600
+++
+++docker.io (1.6.0~rc7~dfsg1-1~exp1) experimental; urgency=low
+++
+++  * Update to 1.6.0-rc7 upstream release
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Wed, 15 Apr 2015 19:35:46 -0600
+++
+++docker.io (1.6.0~rc4~dfsg1-1) experimental; urgency=low
+++
+++  [ Tianon Gravi ]
+++  * Update to 1.6.0-rc4 upstream release
+++    - drop golang 1.2 support (no longer supported upstream)
+++    - update Homepage to https://dockerproject.com
+++    - add check-config.sh to /usr/share/docker.io/contrib
+++    - add "distribution" as a new multitarball orig
+++    - backport auto "btrfs_noversion" patch from
+++      https://github.com/docker/docker/pull/12048
+++      (simplifying our logic for detecting whether to use it)
+++    - switch from dh-golang to direct install since we're not actually using the
+++      features it offers (due to upstream's build system)
+++    - enable "docker.service" on boot by default for restart policies to work
+++
+++  [ Felipe Sateler ]
+++  * Add Built-Using for glibc (Closes: #769351).
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Mon, 06 Apr 2015 17:11:33 -0600
+++
+++docker.io (1.5.0~dfsg1-1) experimental; urgency=low
+++
+++  * Update to 1.5.0 upstream release (Closes: #773495)
+++  * Remove several patches applied upstream!
+++    - 9637-fix-nuke-bashism.patch
+++    - enable-non-amd64-arches.patch
+++  * Fix btrfs-tools handling to allow for building with btrfs-tools < 1.16.1
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Tue, 10 Mar 2015 22:58:49 -0600
+++
+++docker.io (1.3.3~dfsg1-2) unstable; urgency=medium
+++
+++  * Add fatal-error-old-kernels.patch to make Docker refuse to start on old,
+++    unsupported kernels (Closes: #774376)
+++  * Fix dh_auto_clean to clean up after the build properly, especially to avoid
+++    FTBFS when built twice (Closes: #774482)
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Sat, 03 Jan 2015 00:11:47 -0700
+++
+++docker.io (1.3.3~dfsg1-1) unstable; urgency=medium
+++
+++  [ Tianon Gravi ]
+++  * Update to 1.3.3 upstream release (Closes: #772909)
+++    - Fix for CVE-2014-9356 (Path traversal during processing of absolute
+++      symlinks)
+++    - Fix for CVE-2014-9357 (Escalation of privileges during decompression of
+++      LZMA (.xz) archives)
+++    - Fix for CVE-2014-9358 (Path traversal and spoofing opportunities presented
+++      through image identifiers)
+++  * Fix bashism in nuke-graph-directory.sh (Closes: #772261)
+++
+++  [ Didier Roche ]
+++  * Support starting systemd service without /etc/default/docker
+++    (Closes: #770293)
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Thu, 18 Dec 2014 21:54:12 -0700
+++
+++docker.io (1.3.2~dfsg1-1) unstable; urgency=high
+++
+++  * Severity is set to high due to the sensitive nature of the CVEs this
+++    upload fixes.
+++  * Update to 1.3.2 upstream release
+++    - Fix for CVE-2014-6407 (Archive extraction host privilege escalation)
+++    - Fix for CVE-2014-6408 (Security options applied to image could lead
+++                             to container escalation)
+++  * Remove Daniel Mizyrycki from Uploaders. Thanks for all your work!
+++
+++ -- Paul Tagliamonte <paultag@debian.org>  Mon, 24 Nov 2014 19:14:28 -0500
+++
+++docker.io (1.3.1~dfsg1-2) unstable; urgency=medium
+++
+++  * Remove deprecated /usr/bin/docker.io symlink
+++    - added as a temporary shim in 1.0.0~dfsg1-1 (13 Jun 2014)
+++    - unused by package-installed files in 1.2.0~dfsg1-1 (13 Sep 2014)
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Fri, 07 Nov 2014 13:11:34 -0700
+++
+++docker.io (1.3.1~dfsg1-1) unstable; urgency=high
+++
+++  * Update to 1.3.1 upstream release
+++    - fix for CVE-2014-5277
+++    - https://groups.google.com/d/topic/docker-user/oYm0i3xShJU/discussion
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Mon, 03 Nov 2014 08:26:29 -0700
+++
+++docker.io (1.3.0~dfsg1-1) unstable; urgency=medium
+++
+++  * Updated to 1.3.0 upstream release.
+++  * Enable systemd socket activation (Closes: #752555).
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Fri, 17 Oct 2014 00:56:07 -0600
+++
+++docker.io (1.2.0~dfsg1-2) unstable; urgency=medium
+++
+++  * Added "golang-docker-dev" package for the reusable bits of Docker's source.
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Thu, 09 Oct 2014 00:08:11 +0000
+++
+++docker.io (1.2.0~dfsg1-1) unstable; urgency=medium
+++
+++  * Updated to 1.2.0 upstream release (Closes: #757183, #757023, #757024).
+++  * Added upstream man pages.
+++  * Updated bash and zsh completions to be installed as "docker" and "_docker".
+++  * Updated init scripts to also be installed as "docker".
+++  * Fixed "equivalent" typo in README.Debian (Closes: #756395). Thanks Reuben!
+++  * Removed "docker.io" mention in README.Debian (Closes: #756290). Thanks
+++    Olivier!
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Sat, 13 Sep 2014 11:43:17 -0600
+++
+++docker.io (1.0.0~dfsg1-1) unstable; urgency=medium
+++
+++  * Updated to 1.0.0 upstream release. Huzzah!
+++  * I've removed what is commonly called a `button' of patches against
+++    the docker package. Exact patches:
+++     - bash-completion-docker.io.patch
+++     - systemd-docker.io.patch
+++     - sysvinit-provides-docker.io.patch
+++     - zsh-completion-docker.io.patch
+++     - mkimage-docker.io.patch
+++  * I know y'all are guessing why; and the answer's pretty simple -- we're
+++    no longer docker.io(1). Since the src:docker package now ships wmdocker(1),
+++    we can safely declare a breaks/replaces on the pre-wmdocker version of the
+++    package, allowing existing users to safely update, both src:docker and
+++    src:docker.io side. This brings us into line with other distros, which
+++    now ship wmdocker(1) and docker(1).
+++  * As a stop-gap, I'm still shipping a docker.io(1) symlink to allow
+++    migration away.
+++
+++ -- Paul Tagliamonte <paultag@debian.org>  Fri, 13 Jun 2014 21:04:53 -0400
+++
+++docker.io (0.11.1~dfsg1-1) unstable; urgency=medium
+++
+++  [ Paul Tagliamonte ]
+++  * Use EnvironmentFile with the systemd unit file. (Closes: #746774)
+++  * Patch out version checking code. (Closes: #747140)
+++  * Remove all host checking for non-amd64 host arches. Let docker build
+++    and run on all platforms now. (Closes: #747139, #739914)
+++
+++  [ Tianon Gravi ]
+++  * Updated to 0.11.1 upstream release.
+++  * Added backported upstream patch for removing RemoteAddr assumptions
+++    that cause events to not be delivered to more than one unix socket
+++    listener.
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Fri, 09 May 2014 17:30:45 -0400
+++
+++docker.io (0.9.1~dfsg1-2) unstable; urgency=medium
+++
+++  * Added upstream apparmor patch to fix newer apparmor versions (such as the
+++    version appearing in Ubuntu 14.04).
+++  * Added mkimage-* docker.io binary name patches (Closes: #740855).
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Tue, 08 Apr 2014 23:19:08 -0400
+++
+++docker.io (0.9.1~dfsg1-1) unstable; urgency=medium
+++
+++  * Updated to 0.9.1 upstream release (Closes: #743424).
+++  * Added cgroupfs-mount dependency (Closes: #742641).
+++  * Added Suggests entries for optional features, chiefly lxc (Closes: #742081).
+++  * Added notes about "root-equivalence" to README.Debian (Closes: #742387).
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Thu, 03 Apr 2014 21:38:30 -0400
+++
+++docker.io (0.9.0+dfsg1-1) unstable; urgency=medium
+++
+++  * Updated README.Debian to not be quite so outdated (Closes: #740850).
+++  * Updated to 0.9.0 upstream release.
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Tue, 11 Mar 2014 22:24:31 -0400
+++
+++docker.io (0.8.1+dfsg1-1) unstable; urgency=medium
+++
+++  * Updated to 0.8.1 upstream release.
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Tue, 25 Feb 2014 20:56:31 -0500
+++
+++docker.io (0.8.0+dfsg1-2) unstable; urgency=medium
+++
+++  [ Tianon Gravi ]
+++  * Added more license notes to debian/copyright (Closes: #738627).
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Sat, 15 Feb 2014 17:51:58 -0500
+++
+++docker.io (0.8.0+dfsg1-1) unstable; urgency=medium
+++
+++  [ Prach Pongpanich ]
+++  * Added zsh completion.
+++
+++  [ Tianon Gravi ]
+++  * Updated to 0.8.0 upstream release.
+++  * Added vim syntax files in new vim-syntax-docker package.
+++  * Added note about minimum recommended kernel version to Description.
+++  * Added contrib/*-integration files in /usr/share/docker.io/contrib.
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Mon, 10 Feb 2014 20:41:10 -0500
+++
+++docker.io (0.7.6+dfsg1-1) unstable; urgency=medium
+++
+++  [ Johan Euphrosine ]
+++  * Updated to 0.7.6.
+++  * Added dependency to gocapability.
+++  * Clean patches.
+++
+++  [ Tianon Gravi ]
+++  * Added contrib/mk* scripts from upstream into /usr/share/docker.io/contrib
+++    (Closes: #736068).
+++  * Added upstream udev rules file to stop device-mapper devices and mounts from
+++    appearing in desktop environments through udisks.
+++
+++ -- Johan Euphrosine <proppy@google.com>  Wed, 22 Jan 2014 22:50:47 -0500
+++
+++docker.io (0.7.1+dfsg1-1) unstable; urgency=medium
+++
+++  [ Prach Pongpanich ]
+++  * Fixed "docker: command not found" errors while using bash tab completion
+++    (Closes: #735372).
+++
+++  [ Tianon Gravi ]
+++  * Updated to 0.7.1 upstream release (while we wait for gocapability to be
+++    packaged).
+++  * Added xz-utils recommend which is required for decompressing certain images
+++    from the index.
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Wed, 15 Jan 2014 20:22:34 -0500
+++
+++docker.io (0.6.7+dfsg1-3) unstable; urgency=medium
+++
+++  * Fixed FTBFS on non-amd64 platforms by setting the correct GOPATH.
+++  * Fixed issues with Docker finding a valid dockerinit (Closes: #734758).
+++  * Added aufs-tools dependency.
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Thu, 09 Jan 2014 20:10:20 -0500
+++
+++docker.io (0.6.7+dfsg1-2) unstable; urgency=medium
+++
+++  * Added iptables dependency required for Docker to start.
+++  * Added ca-certificates recommend required for pulling from the index.
+++
+++ -- Tianon Gravi <admwiggin@gmail.com>  Wed, 08 Jan 2014 19:14:02 -0500
+++
+++docker.io (0.6.7+dfsg1-1) unstable; urgency=medium
+++
+++  * Initial release (Closes: #706060, #730569)
+++  * Document missing licenses in the source tree. Bad, paultag. Thanks
+++    alteholz.
+++
+++ -- Paul Tagliamonte <paultag@debian.org>  Tue, 07 Jan 2014 21:06:10 -0500
diff --cc debian/compat
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ec635144f60048986bc560c5576355344005e6e7
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,1 @@@@
+++9
diff --cc debian/control
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b7e15ddb646d7540458117f78a428a28df125d0a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,109 @@@@
+++Source: docker.io
+++Section: admin
+++Priority: optional
+++Maintainer: Paul Tagliamonte <paultag@debian.org>
+++Uploaders: Docker Packaging Team <docker-maint@lists.alioth.debian.org>,
+++           Tianon Gravi <tianon@debian.org>,
+++           Johan Euphrosine <proppy@google.com>
+++# partially generated via https://gist.github.com/tianon/92ebbd1793864b9586bc
+++Build-Depends: bash-completion,
+++               btrfs-tools,
+++               debhelper (>=9),
+++               dh-systemd,
+++               go-md2man (>= 1.0.3~),
+++               golang-context-dev (>= 0.0~git20140604~) | golang-github-gorilla-context-dev (>= 0.0~git20140604~),
+++               golang-dbus-dev (>= 2~) | golang-github-godbus-dbus-dev (>= 2~),
+++               golang-etcd-dev (>= 2.0.0~) | golang-github-coreos-go-etcd-dev (>= 2.0.0~),
+++               golang-fsnotify-dev (>= 1.2.0~) | golang-gopkg-fsnotify.v1-dev (>= 1.2.0~),
+++               golang-github-armon-go-metrics-dev (>= 0.0~git20150106~),
+++               golang-github-coreos-go-systemd-dev (>= 2~),
+++               golang-github-docker-distribution-dev (>= 0.0~git20150827~),
+++               golang-github-docker-libkv-dev (>= 0.0~git20150619~),
+++               golang-github-docker-libtrust-dev (>= 0.0~git20150526~),
+++               golang-github-docker-notary-dev (>= 0.0~git20150801~),
+++               golang-github-endophage-gotuf-dev (>= 0.0~git20150811~),
+++               golang-github-fluent-fluent-logger-golang-dev (>= 1.0.0~),
+++               golang-github-gorilla-mux-dev (>= 0.0~git20140926~),
+++               golang-github-graylog2-go-gelf-dev (>= 0.0~git20150610~),
+++               golang-github-hashicorp-go-msgpack-dev (>= 0.0~git20140221~),
+++               golang-github-hashicorp-serf-dev (>= 0.0~git20150212~),
+++               golang-github-mattn-go-sqlite3-dev (>= 0.0~git20150629~),
+++               golang-github-opencontainers-runc-dev (>= 0.0.2.1~),
+++               golang-github-samuel-go-zookeeper-dev (>= 0.0~git20150415~),
+++               golang-github-sirupsen-logrus-dev (>= 0.8.2~),
+++               golang-github-tent-canonical-json-go-dev (>= 0.0~git20130607~),
+++               golang-github-vbatts-tar-split-dev (>= 0.9.6~),
+++               golang-github-vishvananda-netlink-dev (>= 0.0~git20150815~),
+++               golang-github-vishvananda-netns-dev (>= 0.0~git20150630~),
+++               golang-go-patricia-dev (>= 2.1.0~) | golang-github-tchap-go-patricia-dev (>= 2.1.0~),
+++               golang-go-zfs-dev (>= 2.1.1~) | golang-github-mistifyio-go-zfs-dev (>= 2.1.1~),
+++               golang-gocapability-dev (>= 0.0~git20150506~) | golang-github-syndtr-gocapability-dev (>= 0.0~git20150506~),
+++               golang-golang-x-net-dev (>= 0.0~git20150610~),
+++               golang-goprotobuf-dev (>= 0.0~git20150325~) | golang-github-golang-protobuf-dev (>= 0.0~git20150325~),
+++               golang-gosqlite-dev (>= 0.0~hg20130530~) | golang-code.google-p-gosqlite-dev (>= 0.0~hg20130530~),
+++               golang-pty-dev (>= 0.0~git20150511~) | golang-github-kr-pty-dev (>= 0.0~git20150511~),
+++               golang-toml-dev (>= 0.0~git20150127~) | golang-github-burntsushi-toml-dev (>= 0.0~git20150127~),
+++               libapparmor-dev,
+++               libdevmapper-dev (>= 2:1.02.68~)
+++Standards-Version: 3.9.6
+++Homepage: https://dockerproject.com
+++Vcs-Git: git://anonscm.debian.org/docker/docker.io.git
+++Vcs-Browser: http://anonscm.debian.org/cgit/docker/docker.io.git
+++
+++Package: docker.io
+++Architecture: linux-any
+++Depends: adduser, iptables, ${misc:Depends}, ${perl:Depends}, ${shlibs:Depends}
+++Recommends: ca-certificates,
+++            cgroupfs-mount | cgroup-lite,
+++            git,
+++            xz-utils,
+++            ${apparmor:Recommends}
+++Replaces: docker (<< 1.5~)
+++Breaks: docker (<< 1.5~)
+++Suggests: aufs-tools, btrfs-tools, debootstrap, lxc, rinse, zfs-fuse | zfsutils
+++Built-Using: ${misc:Built-Using}, ${libc:Built-Using}
+++Description: Linux container runtime
+++ Docker complements kernel namespacing with a high-level API which operates at
+++ the process level. It runs unix processes with strong guarantees of isolation
+++ and repeatability across servers.
+++ .
+++ Docker is a great building block for automating distributed systems:
+++ large-scale web deployments, database clusters, continuous deployment systems,
+++ private PaaS, service-oriented architectures, etc.
+++ .
+++ This package contains the daemon and client. Using docker.io on non-amd64 hosts
+++ is not supported at this time. Please be careful when using it on anything
+++ besides amd64.
+++ .
+++ Also, note that kernel version 3.8 or above is required for proper operation of
+++ the daemon process, and that any lower versions may have subtle and/or glaring
+++ issues.
+++
+++Package: vim-syntax-docker
+++Architecture: all
+++Depends: vim, ${misc:Depends}
+++Recommends: vim-addon-manager
+++Suggests: docker.io
+++Description: Docker container engine - Vim highlighting syntax files
+++ This package provides syntax files for the Vim editor for editing Dockerfiles
+++ from the Docker container engine.
+++
+++Package: golang-github-docker-docker-dev
+++Architecture: all
+++Depends: ${misc:Depends}
+++Built-Using: ${misc:Built-Using}
+++Replaces: golang-docker-dev (<< 1.8.2~ds1-1~)
+++Breaks: golang-docker-dev (<< 1.8.2~ds1-1~)
+++Provides: golang-docker-dev
+++Description: Externally reusable Go packages included with Docker
+++ These packages are intentionally developed by upstream in such a way that they
+++ are reusable to projects outside Docker and only rely on each other or other
+++ external dependencies to be built.
+++
+++Package: golang-docker-dev
+++Section: oldlibs
+++Architecture: all
+++Depends: golang-github-docker-docker-dev, ${misc:Depends}
+++Description: Transitional package for golang-github-docker-docker-dev
+++ This is a transitional package to ease upgrades to the
+++ golang-github-docker-docker-dev package. It can safely be removed.
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a00f78e23fd9368f9ccb1da77752e40bd877cf8f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,137 @@@@
+++Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+++Upstream-Name: Docker
+++Upstream-Contact: Docker, Inc. <support@docker.com>
+++Source: https://github.com/docker/docker
+++
+++Files: *
+++Copyright: 2012-2014 Docker, Inc. <support@docker.com>
+++License: Apache-2.0
+++
+++Files: debian/*
+++Copyright: 2013-2014 Daniel Mizyrycki <daniel@docker.com>
+++           2013-2014 Paul Tagliamonte <paultag@debian.org>
+++           2012-2014 Michael Stapelberg <stapelberg@debian.org>
+++           2013-2014 Tianon Gravi <tianon@debian.org>
+++           2013-2014 Johan Euphrosine <proppy@google.com>
+++           2014 Prach Pongpanich <prachpub@gmail.com>
+++License: Apache-2.0
+++
+++Files: contrib/init/openrc/docker.initd
+++Copyright: 1999-2013 Gentoo Foundation
+++License: GPL-2
+++
+++Files: contrib/syntax/vim/*
+++Copyright: 2013 Honza Pokorny
+++License: BSD-2-clause
+++
+++Files: pkg/mflag/*
+++Copyright: 2014 The Docker & Go Authors
+++License: BSD-3-clause-Google
+++
+++Files: contrib/completion/zsh/*
+++Copyright: 2013-2014 Felix Riedel
+++License: BSD-3-clause-Generic
+++
+++License: Apache-2.0
+++ Licensed under the Apache License, Version 2.0 (the "License");
+++ you may not use this file except in compliance with the License.
+++ You may obtain a copy of the License at
+++ .
+++ http://www.apache.org/licenses/LICENSE-2.0
+++ .
+++ Unless required by applicable law or agreed to in writing, software
+++ distributed under the License is distributed on an "AS IS" BASIS,
+++ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+++ See the License for the specific language governing permissions and
+++ limitations under the License.
+++ .
+++ On Debian systems, the complete text of the Apache version 2.0 license
+++ can be found in "/usr/share/common-licenses/Apache-2.0".
+++
+++License: GPL-2
+++ This file is part of Buildbot.  Buildbot is free software: you can
+++ redistribute it and/or modify it under the terms of the GNU General Public
+++ License as published by the Free Software Foundation, version 2.
+++ .
+++ This program is distributed in the hope that it will be useful, but WITHOUT
+++ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+++ FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+++ details.
+++ .
+++ You should have received a copy of the GNU General Public License along with
+++ this program; if not, write to the Free Software Foundation, Inc., 51
+++ Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+++ .
+++ On Debian systems, the complete text of the Apache version 2.0 license
+++ can be found in "/usr/share/common-licenses/GPL-2".
+++
+++License: BSD-2-clause
+++ Redistribution and use in source and binary forms, with or without
+++ modification, are permitted provided that the following conditions are met:
+++ .
+++ 1. Redistributions of source code must retain the above copyright
+++    notice, this list of conditions and the following disclaimer.
+++ 2. 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.
+++ .
+++ 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.
+++
+++License: BSD-3-clause-Google
+++ 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.
+++
+++License: BSD-3-clause-Generic
+++ 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 the <organization> 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 <COPYRIGHT HOLDER> 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.
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..6ea111930886df72c9c7083a4f277d824328ddc9
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,1 @@@@
+++contrib/completion/bash/docker
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4312e3554902c5753b370ac23abd0c0396e4e22c
new file mode 120000 (symlink)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,1 @@@@
+++../contrib/init/sysvinit-debian/docker.default
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e27f04950f5cc3c7371be7240489cff1bd0e688d
new file mode 120000 (symlink)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,1 @@@@
+++../contrib/init/sysvinit-debian/docker
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4df3220e7353f65be870c831e28988c2b5acbed8
new file mode 120000 (symlink)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,1 @@@@
+++../contrib/init/upstart/docker.conf
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..f9735ac799ab902d917f26d394a0aaa7ad9537a0
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,7 @@@@
+++contrib/*-integration usr/share/docker.io/contrib/
+++contrib/check-config.sh usr/share/docker.io/contrib/
+++contrib/completion/zsh/_docker usr/share/zsh/vendor-completions/
+++contrib/init/systemd/docker.service lib/systemd/system/
+++contrib/init/systemd/docker.socket lib/systemd/system/
+++contrib/mk* usr/share/docker.io/contrib/
+++contrib/nuke-graph-directory.sh usr/share/docker.io/contrib/
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..37dde6ad1a96086bad3306c66360df81a4947728
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,2 @@@@
+++docker.io binary: statically-linked-binary usr/lib/docker.io/dockerinit
+++# Yes, I assure you this is normal. Damnit, go.
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..adf69fc13a5de69355afd5dd6f9347dee827d452
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,4 @@@@
+++mv_conffile /etc/bash_completion.d/docker.io /etc/bash_completion.d/docker 1.2.0~
+++mv_conffile /etc/default/docker.io /etc/default/docker 1.2.0~
+++mv_conffile /etc/init.d/docker.io /etc/init.d/docker 1.2.0~
+++mv_conffile /etc/init/docker.io.conf /etc/init/docker.conf 1.2.0~
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1aa62186a68f959c7dfbbf4a29d7d3b8fef797cf
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,1 @@@@
+++man/man*/*
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..5fd8847277fc8b1c03ae4c79f55bab6a4da1e34d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,18 @@@@
+++#!/bin/sh
+++set -e
+++
+++case "$1" in
+++     configure)
+++             if [ -z "$2" ]; then
+++                     addgroup --system docker
+++             fi
+++             ;;
+++     abort-*)
+++             # How'd we get here??
+++             exit 1
+++             ;;
+++     *)
+++             ;;
+++esac
+++
+++#DEBHELPER#
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d7fd2eb6f98c101b4768455cd7eab6dcc2d9a171
new file mode 120000 (symlink)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,1 @@@@
+++../contrib/udev/80-docker.rules
diff --cc debian/docs
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b43bf86b50fd8d3529a0dc062c30006ed38f309e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,1 @@@@
+++README.md
diff --cc debian/gbp.conf
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..09ea3f176963509df1d407827c43f2c5346f8e77
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,13 @@@@
+++[DEFAULT]
+++cleaner = fakeroot debian/rules clean
+++pristine-tar = False
+++
+++[git-buildpackage]
+++export-dir = ../build-area/
+++tarball-dir = ../tarballs/
+++
+++[git-dch]
+++id-length = 7
+++meta = True
+++auto = True
+++full = True
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..bd1cdc34e525164a50458c99f1b3065fe63aec5c
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,60 @@@@
+++#!/bin/bash
+++set -e
+++
+++mkdir -p "${DOCKER_TARBALLS:=../tarballs}"
+++
+++# MUST STAY IN SYNC WITH LIST IN "debian/rules"
+++multiOrigGopkgs=(
+++     github.com/docker/libnetwork
+++)
+++
+++pkg="$(dpkg-parsechangelog -SSource)"
+++ver="$(dpkg-parsechangelog -SVersion)"
+++origVer="${ver%-*}" # strip everything from the last dash
+++origVer="$(echo "$origVer" | sed -r 's/^[0-9]+://')" # strip epoch
+++upstreamVer="${origVer%%[+~]ds*}"
+++origTarballPrefix="${DOCKER_TARBALLS}/${pkg}_${origVer}.orig"
+++unprunedTarballPrefix="${DOCKER_TARBALLS}/${pkg}_${upstreamVer}.orig"
+++
+++if command -v curl &> /dev/null; then
+++     curl='curl -fsSL'
+++elif command -v wget &> /dev/null; then
+++     curl='wget -qO-'
+++else
+++     echo >&2 'error: missing "curl" or "wget" - install one or the other'
+++     exit 1
+++fi
+++
+++get_hack_vendor() {
+++     for path in hack/vendor.sh project/vendor.sh; do
+++             if [ -e "${origTarballPrefix}.tar.gz" ]; then
+++                     # if we have the main orig tarball handy, let's prefer that
+++                     if tar -xzOf "${origTarballPrefix}.tar.gz" --wildcards '*/'"$path"; then
+++                             return
+++                     fi
+++             else
+++                     # but fall back to grabbing it raw from github otherwise
+++                     if $curl "https://raw.githubusercontent.com/docker/docker/v${upstreamVer}/$path"; then
+++                             return
+++                     fi
+++             fi
+++     done
+++}
+++
+++ret=0
+++for gopkg in "${multiOrigGopkgs[@]}"; do
+++     commit="$(get_hack_vendor | awk '$1 == "clone" && $2 == "git" && $3 == "'"$gopkg"'" { print $4 }')"
+++     if [ "$commit" ]; then
+++             origTar="$unprunedTarballPrefix-$(basename "$gopkg").tar.gz"
+++             $curl "https://$gopkg/archive/$commit.tar.gz" > "$origTar"
+++
+++             echo "successfully fetched $origTar"
+++             echo "  (from $gopkg commit $commit)"
+++
+++             "$(dirname "$(readlink -f "$BASH_SOURCE")")/../repack.sh" --upstream-version "$upstreamVer" "$origTar"
+++     else
+++             echo >&2 "error: cannot find $gopkg commit!"
+++             ret=1
+++     fi
+++done
+++exit $ret
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..3ca038d1952ea8d1d5331062450993166e215209
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,34 @@@@
+++#!/bin/bash
+++set -e
+++
+++uVersion="$1"
+++dVersion="$2"
+++
+++if [ -z "$uVersion" ]; then
+++     uVersion="$(cat VERSION)"
+++fi
+++if [ -z "$dVersion" ]; then
+++     dVersion="$(dpkg-parsechangelog --show-field Version)"
+++fi
+++
+++if [ "${uVersion%-dev}" = "$uVersion" ]; then
+++     # this is a straight-up release!  easy-peasy
+++     exec awk -F ': ' '$1 == "'"$uVersion"'" { print $2 }' debian/upstream-version-gitcommits
+++fi
+++
+++# must be a nightly, so let's look for clues about what the git commit is
+++
+++if git rev-parse &> /dev/null; then
+++     # well, this will be easy ;)
+++     exec git rev-parse --short HEAD
+++fi
+++
+++if [ "${dVersion#*+*+}" != "$dVersion" ]; then
+++     # must be something like "1.1.2+10013+8c38a3d-1~utopic1" (nightly!)
+++     commit="${dVersion#*+*+}"
+++     commit="${commit%%-*}"
+++     exec echo "$commit"
+++fi
+++
+++# unknown...
+++echo >&2 'warning: unable to determine DOCKER_GITCOMMIT'
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..7cda1f933c44efd6d83fc4c666c1dbf062c10a34
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,85 @@@@
+++From f83d05c3be3c3bcc84f6fa229504848ee8078321 Mon Sep 17 00:00:00 2001
+++From: Vincent Batts <vbatts@redhat.com>
+++Date: Fri, 7 Aug 2015 10:18:20 -0400
+++Subject: [PATCH] devicemapper: fix zero-sized field access
+++
+++Fixes: #15279
+++
+++Due to
+++https://github.com/golang/go/commit/7904946eeb35faece61bbf6f5b3cc8be2f519c17
+++the devices field is dropped.
+++
+++This solution works on go1.4 and go1.5
+++
+++Signed-off-by: Vincent Batts <vbatts@redhat.com>
+++---
+++ daemon/graphdriver/devmapper/deviceset.go | 14 +++++++++-----
+++ pkg/devicemapper/devmapper_wrapper.go     | 18 +++++++++++++++---
+++ 2 files changed, 24 insertions(+), 8 deletions(-)
+++
+++diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go
+++index 6dddeb1..97e2032 100644
+++--- a/daemon/graphdriver/devmapper/deviceset.go
++++++ b/daemon/graphdriver/devmapper/deviceset.go
+++@@ -1509,12 +1509,16 @@ func (devices *DeviceSet) deactivatePool() error {
+++     if err != nil {
+++             return err
+++     }
+++-    if d, err := devicemapper.GetDeps(devname); err == nil {
+++-            // Access to more Debug output
+++-            logrus.Debugf("[devmapper] devicemapper.GetDeps() %s: %#v", devname, d)
++++
++++    if devinfo.Exists == 0 {
++++            return nil
+++     }
+++-    if devinfo.Exists != 0 {
+++-            return devicemapper.RemoveDevice(devname)
++++    if err := devicemapper.RemoveDevice(devname); err != nil {
++++            return err
++++    }
++++
++++    if d, err := devicemapper.GetDeps(devname); err == nil {
++++            logrus.Warnf("[devmapper] device %s still has %d active dependents", devname, d.Count)
+++     }
+++ 
+++     return nil
+++diff --git a/pkg/devicemapper/devmapper_wrapper.go b/pkg/devicemapper/devmapper_wrapper.go
+++index 87c2003..44ca772 100644
+++--- a/pkg/devicemapper/devmapper_wrapper.go
++++++ b/pkg/devicemapper/devmapper_wrapper.go
+++@@ -38,7 +38,10 @@ static void       log_with_errno_init()
+++ */
+++ import "C"
+++ 
+++-import "unsafe"
++++import (
++++    "reflect"
++++    "unsafe"
++++)
+++ 
+++ type (
+++     CDmTask C.struct_dm_task
+++@@ -184,12 +187,21 @@ func dmTaskGetDepsFct(task *CDmTask) *Deps {
+++     if Cdeps == nil {
+++             return nil
+++     }
++++
++++    // golang issue: https://github.com/golang/go/issues/11925
++++    hdr := reflect.SliceHeader{
++++            Data: uintptr(unsafe.Pointer(uintptr(unsafe.Pointer(Cdeps)) + unsafe.Sizeof(*Cdeps))),
++++            Len:  int(Cdeps.count),
++++            Cap:  int(Cdeps.count),
++++    }
++++    devices := *(*[]C.uint64_t)(unsafe.Pointer(&hdr))
++++
+++     deps := &Deps{
+++             Count:  uint32(Cdeps.count),
+++             Filler: uint32(Cdeps.filler),
+++     }
+++-    for _, device := range Cdeps.device {
+++-            deps.Device = append(deps.Device, (uint64)(device))
++++    for _, device := range devices {
++++            deps.Device = append(deps.Device, uint64(device))
+++     }
+++     return deps
+++ }
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..97d08bacea4fdadf35a8548d244da2c12142e276
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,22 @@@@
+++Description: adjust "native.cgroupdriver" default value to always be "cgroupfs" irrespective of systemd since Docker cannot handle the new unified hierarchy
+++Author: Tianon Gravi <tianon@debian.org>
+++Bug: https://github.com/docker/docker/issues/16256
+++Bug-Debian: https://bugs.debian.org/798778
+++
+++diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go
+++index 94f200a..b09c86c 100644
+++--- a/daemon/execdriver/native/driver.go
++++++ b/daemon/execdriver/native/driver.go
+++@@ -74,9 +74,9 @@ func NewDriver(root, initPath string, options []string) (*Driver, error) {
+++     // this makes sure there are no breaking changes to people
+++     // who upgrade from versions without native.cgroupdriver opt
+++     cgm := libcontainer.Cgroupfs
+++-    if systemd.UseSystemd() {
+++-            cgm = libcontainer.SystemdCgroups
+++-    }
++++    //if systemd.UseSystemd() {
++++    //      cgm = libcontainer.SystemdCgroups
++++    //}
+++ 
+++     // parse the options
+++     for _, option := range options {
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..938a55c355bc6873b9ea0f4cf21d1c8e77c6cc48
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,81 @@@@
+++Author: Tianon Gravi <admwiggin@gmail.com>
+++Description: remove convenience copies of cgroupfs-mount in init.d / upstart
+++Forwarded: not-needed
+++Bug-Debian: https://bugs.debian.org/783143
+++
+++diff --git a/contrib/init/sysvinit-debian/docker b/contrib/init/sysvinit-debian/docker
+++index 11500a0..3ad9e2e 100755
+++--- a/contrib/init/sysvinit-debian/docker
++++++ b/contrib/init/sysvinit-debian/docker
+++@@ -59,37 +59,12 @@ fail_unless_root() {
+++     fi
+++ }
+++ 
+++-cgroupfs_mount() {
+++-    # see also https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount
+++-    if grep -v '^#' /etc/fstab | grep -q cgroup \
+++-            || [ ! -e /proc/cgroups ] \
+++-            || [ ! -d /sys/fs/cgroup ]; then
+++-            return
+++-    fi
+++-    if ! mountpoint -q /sys/fs/cgroup; then
+++-            mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup
+++-    fi
+++-    (
+++-            cd /sys/fs/cgroup
+++-            for sys in $(awk '!/^#/ { if ($4 == 1) print $1 }' /proc/cgroups); do
+++-                    mkdir -p $sys
+++-                    if ! mountpoint -q $sys; then
+++-                            if ! mount -n -t cgroup -o $sys cgroup $sys; then
+++-                                    rmdir $sys || true
+++-                            fi
+++-                    fi
+++-            done
+++-    )
+++-}
+++-
+++ case "$1" in
+++     start)
+++             check_init
+++             
+++             fail_unless_root
+++ 
+++-            cgroupfs_mount
+++-
+++             touch "$DOCKER_LOGFILE"
+++             chgrp docker "$DOCKER_LOGFILE"
+++ 
+++diff --git a/contrib/init/upstart/docker.conf b/contrib/init/upstart/docker.conf
+++index ec50b35..75858f8 100644
+++--- a/contrib/init/upstart/docker.conf
++++++ b/contrib/init/upstart/docker.conf
+++@@ -9,29 +9,6 @@ respawn
+++ 
+++ kill timeout 20
+++ 
+++-pre-start script
+++-    # see also https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount
+++-    if grep -v '^#' /etc/fstab | grep -q cgroup \
+++-            || [ ! -e /proc/cgroups ] \
+++-            || [ ! -d /sys/fs/cgroup ]; then
+++-            exit 0
+++-    fi
+++-    if ! mountpoint -q /sys/fs/cgroup; then
+++-            mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup
+++-    fi
+++-    (
+++-            cd /sys/fs/cgroup
+++-            for sys in $(awk '!/^#/ { if ($4 == 1) print $1 }' /proc/cgroups); do
+++-                    mkdir -p $sys
+++-                    if ! mountpoint -q $sys; then
+++-                            if ! mount -n -t cgroup -o $sys cgroup $sys; then
+++-                                    rmdir $sys || true
+++-                            fi
+++-                    fi
+++-            done
+++-    )
+++-end script
+++-
+++ script
+++     # modify these in /etc/default/$UPSTART_JOB (/etc/default/docker)
+++     DOCKER=/usr/bin/$UPSTART_JOB
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..5503c4aa02cdc0d01719bc9f358ac45badae3897
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,20 @@@@
+++Author: Paul R. Tagliamonte <paultag@debian.org>
+++Last-Update: 2014-05-07
+++Description: Use EnvironmentFile with the systemd unit file.
+++Bug-Debian: http://bugs.debian.org/746774
+++Forwarded: no
+++
+++diff --git a/contrib/init/systemd/docker.service b/contrib/init/systemd/docker.service
+++index f09c2d3..8370631 100644
+++--- a/contrib/init/systemd/docker.service
++++++ b/contrib/init/systemd/docker.service
+++@@ -6,7 +6,8 @@ Requires=docker.socket
+++ 
+++ [Service]
+++ Type=notify
+++-ExecStart=/usr/bin/docker daemon -H fd://
++++EnvironmentFile=-/etc/default/docker
++++ExecStart=/usr/bin/docker daemon -H fd:// $DOCKER_OPTS
+++ MountFlags=slave
+++ LimitNOFILE=1048576
+++ LimitNPROC=1048576
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e194cac6a6bbd745bd2082f67ce527c7b56c7262
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,1053 @@@@
+++diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go
+++index defa02e..f9e2c67 100644
+++--- a/integration-cli/check_test.go
++++++ b/integration-cli/check_test.go
+++@@ -3,7 +3,7 @@ package main
+++ import (
+++     "testing"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func Test(t *testing.T) {
+++diff --git a/integration-cli/docker_api_attach_test.go b/integration-cli/docker_api_attach_test.go
+++index 06a1c48..001631e 100644
+++--- a/integration-cli/docker_api_attach_test.go
++++++ b/integration-cli/docker_api_attach_test.go
+++@@ -8,7 +8,7 @@ import (
+++     "strings"
+++     "time"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++     "golang.org/x/net/websocket"
+++ )
+++ 
+++diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go
+++index d8dc448..571af60 100644
+++--- a/integration-cli/docker_api_containers_test.go
++++++ b/integration-cli/docker_api_containers_test.go
+++@@ -17,7 +17,7 @@ import (
+++     "github.com/docker/docker/api/types"
+++     "github.com/docker/docker/pkg/stringid"
+++     "github.com/docker/docker/runconfig"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestContainerApiGetAll(c *check.C) {
+++diff --git a/integration-cli/docker_api_events_test.go b/integration-cli/docker_api_events_test.go
+++index 8ae7bf7..9120aa0 100644
+++--- a/integration-cli/docker_api_events_test.go
++++++ b/integration-cli/docker_api_events_test.go
+++@@ -4,7 +4,7 @@ import (
+++     "net/http"
+++     "time"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestEventsApiEmptyOutput(c *check.C) {
+++diff --git a/integration-cli/docker_api_exec_resize_test.go b/integration-cli/docker_api_exec_resize_test.go
+++index 01061ca..817c734 100644
+++--- a/integration-cli/docker_api_exec_resize_test.go
++++++ b/integration-cli/docker_api_exec_resize_test.go
+++@@ -4,7 +4,7 @@ import (
+++     "net/http"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestExecResizeApiHeightWidthNoInt(c *check.C) {
+++diff --git a/integration-cli/docker_api_exec_test.go b/integration-cli/docker_api_exec_test.go
+++index 3d99fe6..0446230 100644
+++--- a/integration-cli/docker_api_exec_test.go
++++++ b/integration-cli/docker_api_exec_test.go
+++@@ -8,7 +8,7 @@ import (
+++     "fmt"
+++     "net/http"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // Regression test for #9414
+++diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go
+++index 339a2f4..ab21d4b 100644
+++--- a/integration-cli/docker_api_images_test.go
++++++ b/integration-cli/docker_api_images_test.go
+++@@ -7,7 +7,7 @@ import (
+++     "strings"
+++ 
+++     "github.com/docker/docker/api/types"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestApiImagesFilter(c *check.C) {
+++diff --git a/integration-cli/docker_api_info_test.go b/integration-cli/docker_api_info_test.go
+++index 4084289..1616e0e 100644
+++--- a/integration-cli/docker_api_info_test.go
++++++ b/integration-cli/docker_api_info_test.go
+++@@ -4,7 +4,7 @@ import (
+++     "net/http"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestInfoApi(c *check.C) {
+++diff --git a/integration-cli/docker_api_inspect_test.go b/integration-cli/docker_api_inspect_test.go
+++index 2287e7d..a57e687 100644
+++--- a/integration-cli/docker_api_inspect_test.go
++++++ b/integration-cli/docker_api_inspect_test.go
+++@@ -6,7 +6,7 @@ import (
+++     "net/http"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestInspectApiContainerResponse(c *check.C) {
+++diff --git a/integration-cli/docker_api_logs_test.go b/integration-cli/docker_api_logs_test.go
+++index d478447..d22f363 100644
+++--- a/integration-cli/docker_api_logs_test.go
++++++ b/integration-cli/docker_api_logs_test.go
+++@@ -8,7 +8,7 @@ import (
+++     "strings"
+++     "time"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestLogsApiWithStdout(c *check.C) {
+++diff --git a/integration-cli/docker_api_network_test.go b/integration-cli/docker_api_network_test.go
+++index 44d2b31..2da958f 100644
+++--- a/integration-cli/docker_api_network_test.go
++++++ b/integration-cli/docker_api_network_test.go
+++@@ -7,7 +7,7 @@ import (
+++     "fmt"
+++     "net/http"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func isNetworkAvailable(c *check.C, name string) bool {
+++diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go
+++index c7a577b..e421403 100644
+++--- a/integration-cli/docker_api_resize_test.go
++++++ b/integration-cli/docker_api_resize_test.go
+++@@ -4,7 +4,7 @@ import (
+++     "net/http"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestResizeApiResponse(c *check.C) {
+++diff --git a/integration-cli/docker_api_service_test.go b/integration-cli/docker_api_service_test.go
+++index df07219..27b63c8 100644
+++--- a/integration-cli/docker_api_service_test.go
++++++ b/integration-cli/docker_api_service_test.go
+++@@ -7,7 +7,7 @@ import (
+++     "fmt"
+++     "net/http"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func isServiceAvailable(c *check.C, name string, network string) bool {
+++diff --git a/integration-cli/docker_api_stats_test.go b/integration-cli/docker_api_stats_test.go
+++index f019e00..d1868fd 100644
+++--- a/integration-cli/docker_api_stats_test.go
++++++ b/integration-cli/docker_api_stats_test.go
+++@@ -10,7 +10,7 @@ import (
+++     "time"
+++ 
+++     "github.com/docker/docker/api/types"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestCliStatsNoStreamGetCpu(c *check.C) {
+++diff --git a/integration-cli/docker_api_test.go b/integration-cli/docker_api_test.go
+++index 6cbf301..0572583 100644
+++--- a/integration-cli/docker_api_test.go
++++++ b/integration-cli/docker_api_test.go
+++@@ -8,7 +8,7 @@ import (
+++     "time"
+++ 
+++     "github.com/docker/docker/api"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestApiOptionsRoute(c *check.C) {
+++diff --git a/integration-cli/docker_api_version_test.go b/integration-cli/docker_api_version_test.go
+++index b756794..42fb872 100644
+++--- a/integration-cli/docker_api_version_test.go
++++++ b/integration-cli/docker_api_version_test.go
+++@@ -6,7 +6,7 @@ import (
+++ 
+++     "github.com/docker/docker/api/types"
+++     "github.com/docker/docker/autogen/dockerversion"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestGetVersion(c *check.C) {
+++diff --git a/integration-cli/docker_cli_attach_test.go b/integration-cli/docker_cli_attach_test.go
+++index f45f775..90e389c 100644
+++--- a/integration-cli/docker_cli_attach_test.go
++++++ b/integration-cli/docker_cli_attach_test.go
+++@@ -9,7 +9,7 @@ import (
+++     "sync"
+++     "time"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ const attachWait = 5 * time.Second
+++diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go
+++index 9718dc0..c30ff45 100644
+++--- a/integration-cli/docker_cli_attach_unix_test.go
++++++ b/integration-cli/docker_cli_attach_unix_test.go
+++@@ -9,7 +9,7 @@ import (
+++     "time"
+++ 
+++     "github.com/docker/docker/pkg/stringid"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++     "github.com/kr/pty"
+++ )
+++ 
+++diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go
+++index 6dc24df..ad56d5d 100644
+++--- a/integration-cli/docker_cli_build_test.go
++++++ b/integration-cli/docker_cli_build_test.go
+++@@ -21,7 +21,7 @@ import (
+++     "github.com/docker/docker/builder/command"
+++     "github.com/docker/docker/pkg/archive"
+++     "github.com/docker/docker/pkg/stringutils"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestBuildJSONEmptyRun(c *check.C) {
+++diff --git a/integration-cli/docker_cli_build_unix_test.go b/integration-cli/docker_cli_build_unix_test.go
+++index fc7bd92..c246dbd 100644
+++--- a/integration-cli/docker_cli_build_unix_test.go
++++++ b/integration-cli/docker_cli_build_unix_test.go
+++@@ -7,7 +7,7 @@ import (
+++     "strings"
+++ 
+++     "github.com/docker/docker/pkg/ulimit"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) {
+++diff --git a/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go
+++index 71f8b1a..a53e3db 100644
+++--- a/integration-cli/docker_cli_by_digest_test.go
++++++ b/integration-cli/docker_cli_by_digest_test.go
+++@@ -9,7 +9,7 @@ import (
+++     "github.com/docker/distribution/digest"
+++     "github.com/docker/distribution/manifest"
+++     "github.com/docker/docker/utils"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ var (
+++diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go
+++index 125b2e3..7e0d6f5 100644
+++--- a/integration-cli/docker_cli_commit_test.go
++++++ b/integration-cli/docker_cli_commit_test.go
+++@@ -3,7 +3,7 @@ package main
+++ import (
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestCommitAfterContainerIsDone(c *check.C) {
+++diff --git a/integration-cli/docker_cli_config_test.go b/integration-cli/docker_cli_config_test.go
+++index 2b08f47..1b574fb 100644
+++--- a/integration-cli/docker_cli_config_test.go
++++++ b/integration-cli/docker_cli_config_test.go
+++@@ -11,7 +11,7 @@ import (
+++ 
+++     "github.com/docker/docker/autogen/dockerversion"
+++     "github.com/docker/docker/pkg/homedir"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestConfigHttpHeader(c *check.C) {
+++diff --git a/integration-cli/docker_cli_cp_from_container_test.go b/integration-cli/docker_cli_cp_from_container_test.go
+++index 945a34f..f6f3159 100644
+++--- a/integration-cli/docker_cli_cp_from_container_test.go
++++++ b/integration-cli/docker_cli_cp_from_container_test.go
+++@@ -4,7 +4,7 @@ import (
+++     "os"
+++     "path/filepath"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // docker cp CONTAINER:PATH LOCALPATH
+++diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go
+++index 64ae0b5..94f6935 100644
+++--- a/integration-cli/docker_cli_cp_test.go
++++++ b/integration-cli/docker_cli_cp_test.go
+++@@ -10,7 +10,7 @@ import (
+++     "path/filepath"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ const (
+++diff --git a/integration-cli/docker_cli_cp_to_container_test.go b/integration-cli/docker_cli_cp_to_container_test.go
+++index 341121d..a705ad1 100644
+++--- a/integration-cli/docker_cli_cp_to_container_test.go
++++++ b/integration-cli/docker_cli_cp_to_container_test.go
+++@@ -3,7 +3,7 @@ package main
+++ import (
+++     "os"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // docker cp LOCALPATH CONTAINER:PATH
+++diff --git a/integration-cli/docker_cli_cp_utils.go b/integration-cli/docker_cli_cp_utils.go
+++index c26ebfd..9ed8a7d 100644
+++--- a/integration-cli/docker_cli_cp_utils.go
++++++ b/integration-cli/docker_cli_cp_utils.go
+++@@ -10,7 +10,7 @@ import (
+++     "strings"
+++ 
+++     "github.com/docker/docker/pkg/archive"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ type fileType uint32
+++diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go
+++index 482e96f..9a0ad7e 100644
+++--- a/integration-cli/docker_cli_create_test.go
++++++ b/integration-cli/docker_cli_create_test.go
+++@@ -13,7 +13,7 @@ import (
+++     "io/ioutil"
+++ 
+++     "github.com/docker/docker/pkg/nat"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // Make sure we can create a simple container with some args
+++diff --git a/integration-cli/docker_cli_daemon_experimental_test.go b/integration-cli/docker_cli_daemon_experimental_test.go
+++index dc4f792..51c51d2 100644
+++--- a/integration-cli/docker_cli_daemon_experimental_test.go
++++++ b/integration-cli/docker_cli_daemon_experimental_test.go
+++@@ -6,7 +6,7 @@ import (
+++     "os/exec"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func assertNetwork(c *check.C, d *Daemon, name string) {
+++diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go
+++index 992cd83..2dec0ec 100644
+++--- a/integration-cli/docker_cli_daemon_test.go
++++++ b/integration-cli/docker_cli_daemon_test.go
+++@@ -17,7 +17,7 @@ import (
+++ 
+++     "github.com/docker/libnetwork/iptables"
+++     "github.com/docker/libtrust"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerDaemonSuite) TestDaemonRestartWithRunningContainersPorts(c *check.C) {
+++diff --git a/integration-cli/docker_cli_diff_test.go b/integration-cli/docker_cli_diff_test.go
+++index b5fc1bd..4041620 100644
+++--- a/integration-cli/docker_cli_diff_test.go
++++++ b/integration-cli/docker_cli_diff_test.go
+++@@ -3,7 +3,7 @@ package main
+++ import (
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // ensure that an added file shows up in docker diff
+++diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go
+++index 6742ea4..2bcbfcd 100644
+++--- a/integration-cli/docker_cli_events_test.go
++++++ b/integration-cli/docker_cli_events_test.go
+++@@ -13,7 +13,7 @@ import (
+++     "sync"
+++     "time"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestEventsTimestampFormats(c *check.C) {
+++diff --git a/integration-cli/docker_cli_events_unix_test.go b/integration-cli/docker_cli_events_unix_test.go
+++index 1a08f2b..fd6dcd6 100644
+++--- a/integration-cli/docker_cli_events_unix_test.go
++++++ b/integration-cli/docker_cli_events_unix_test.go
+++@@ -10,7 +10,7 @@ import (
+++     "os/exec"
+++     "unicode"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++     "github.com/kr/pty"
+++ )
+++ 
+++diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go
+++index 8e85988..a626a7d 100644
+++--- a/integration-cli/docker_cli_exec_test.go
++++++ b/integration-cli/docker_cli_exec_test.go
+++@@ -15,7 +15,7 @@ import (
+++     "sync"
+++     "time"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestExec(c *check.C) {
+++diff --git a/integration-cli/docker_cli_exec_unix_test.go b/integration-cli/docker_cli_exec_unix_test.go
+++index 28c202c..fa0b274 100644
+++--- a/integration-cli/docker_cli_exec_unix_test.go
++++++ b/integration-cli/docker_cli_exec_unix_test.go
+++@@ -9,7 +9,7 @@ import (
+++     "strings"
+++     "time"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++     "github.com/kr/pty"
+++ )
+++ 
+++diff --git a/integration-cli/docker_cli_experimental_test.go b/integration-cli/docker_cli_experimental_test.go
+++index 694222b..100b6c4 100644
+++--- a/integration-cli/docker_cli_experimental_test.go
++++++ b/integration-cli/docker_cli_experimental_test.go
+++@@ -5,7 +5,7 @@ package main
+++ import (
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestExperimentalVersion(c *check.C) {
+++diff --git a/integration-cli/docker_cli_export_import_test.go b/integration-cli/docker_cli_export_import_test.go
+++index a9e75de..f227755 100644
+++--- a/integration-cli/docker_cli_export_import_test.go
++++++ b/integration-cli/docker_cli_export_import_test.go
+++@@ -5,7 +5,7 @@ import (
+++     "os/exec"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // export an image and try to import it into a new one
+++diff --git a/integration-cli/docker_cli_help_test.go b/integration-cli/docker_cli_help_test.go
+++index 3113083..2c83671 100644
+++--- a/integration-cli/docker_cli_help_test.go
++++++ b/integration-cli/docker_cli_help_test.go
+++@@ -8,7 +8,7 @@ import (
+++     "unicode"
+++ 
+++     "github.com/docker/docker/pkg/homedir"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
+++diff --git a/integration-cli/docker_cli_history_test.go b/integration-cli/docker_cli_history_test.go
+++index 355e4c8..7faac52 100644
+++--- a/integration-cli/docker_cli_history_test.go
++++++ b/integration-cli/docker_cli_history_test.go
+++@@ -6,7 +6,7 @@ import (
+++     "strconv"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // This is a heisen-test.  Because the created timestamp of images and the behavior of
+++diff --git a/integration-cli/docker_cli_images_test.go b/integration-cli/docker_cli_images_test.go
+++index fe12f02..1bc04ef 100644
+++--- a/integration-cli/docker_cli_images_test.go
++++++ b/integration-cli/docker_cli_images_test.go
+++@@ -8,7 +8,7 @@ import (
+++     "time"
+++ 
+++     "github.com/docker/docker/pkg/stringid"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestImagesEnsureImageIsListed(c *check.C) {
+++diff --git a/integration-cli/docker_cli_import_test.go b/integration-cli/docker_cli_import_test.go
+++index ccfc452..5d25068 100644
+++--- a/integration-cli/docker_cli_import_test.go
++++++ b/integration-cli/docker_cli_import_test.go
+++@@ -7,7 +7,7 @@ import (
+++     "os/exec"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestImportDisplay(c *check.C) {
+++diff --git a/integration-cli/docker_cli_info_test.go b/integration-cli/docker_cli_info_test.go
+++index 86719f4..134c5c6 100644
+++--- a/integration-cli/docker_cli_info_test.go
++++++ b/integration-cli/docker_cli_info_test.go
+++@@ -4,7 +4,7 @@ import (
+++     "strings"
+++ 
+++     "github.com/docker/docker/utils"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // ensure docker info succeeds
+++diff --git a/integration-cli/docker_cli_inspect_experimental_test.go b/integration-cli/docker_cli_inspect_experimental_test.go
+++index fada86e..4c4f98b 100644
+++--- a/integration-cli/docker_cli_inspect_experimental_test.go
++++++ b/integration-cli/docker_cli_inspect_experimental_test.go
+++@@ -4,7 +4,7 @@ package main
+++ 
+++ import (
+++     "github.com/docker/docker/api/types"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestInspectNamedMountPoint(c *check.C) {
+++diff --git a/integration-cli/docker_cli_inspect_test.go b/integration-cli/docker_cli_inspect_test.go
+++index 3e42d0c..c52c2c8 100644
+++--- a/integration-cli/docker_cli_inspect_test.go
++++++ b/integration-cli/docker_cli_inspect_test.go
+++@@ -8,7 +8,7 @@ import (
+++     "time"
+++ 
+++     "github.com/docker/docker/api/types"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestInspectImage(c *check.C) {
+++diff --git a/integration-cli/docker_cli_kill_test.go b/integration-cli/docker_cli_kill_test.go
+++index 685f4f5..a3989b0 100644
+++--- a/integration-cli/docker_cli_kill_test.go
++++++ b/integration-cli/docker_cli_kill_test.go
+++@@ -5,7 +5,7 @@ import (
+++     "net/http"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestKillContainer(c *check.C) {
+++diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go
+++index 568f20a..e76f83d 100644
+++--- a/integration-cli/docker_cli_links_test.go
++++++ b/integration-cli/docker_cli_links_test.go
+++@@ -2,7 +2,7 @@ package main
+++ 
+++ import (
+++     "fmt"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++     "reflect"
+++     "regexp"
+++     "strings"
+++diff --git a/integration-cli/docker_cli_links_unix_test.go b/integration-cli/docker_cli_links_unix_test.go
+++index 67a4464..bb886df 100644
+++--- a/integration-cli/docker_cli_links_unix_test.go
++++++ b/integration-cli/docker_cli_links_unix_test.go
+++@@ -7,7 +7,7 @@ import (
+++     "os"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestLinksEtcHostsRegularFile(c *check.C) {
+++diff --git a/integration-cli/docker_cli_login_test.go b/integration-cli/docker_cli_login_test.go
+++index 3b4431d..409a007 100644
+++--- a/integration-cli/docker_cli_login_test.go
++++++ b/integration-cli/docker_cli_login_test.go
+++@@ -4,7 +4,7 @@ import (
+++     "bytes"
+++     "os/exec"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestLoginWithoutTTY(c *check.C) {
+++diff --git a/integration-cli/docker_cli_logs_test.go b/integration-cli/docker_cli_logs_test.go
+++index 6c94217..076e227 100644
+++--- a/integration-cli/docker_cli_logs_test.go
++++++ b/integration-cli/docker_cli_logs_test.go
+++@@ -11,7 +11,7 @@ import (
+++     "time"
+++ 
+++     "github.com/docker/docker/pkg/timeutils"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // This used to work, it test a log of PageSize-1 (gh#4851)
+++diff --git a/integration-cli/docker_cli_nat_test.go b/integration-cli/docker_cli_nat_test.go
+++index a0773fe..d1044f5 100644
+++--- a/integration-cli/docker_cli_nat_test.go
++++++ b/integration-cli/docker_cli_nat_test.go
+++@@ -6,7 +6,7 @@ import (
+++     "net"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func startServerContainer(c *check.C, msg string, port int) string {
+++diff --git a/integration-cli/docker_cli_network_test.go b/integration-cli/docker_cli_network_test.go
+++index 08b225d..d931add 100644
+++--- a/integration-cli/docker_cli_network_test.go
++++++ b/integration-cli/docker_cli_network_test.go
+++@@ -5,7 +5,7 @@ package main
+++ import (
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func assertNwIsAvailable(c *check.C, name string) {
+++diff --git a/integration-cli/docker_cli_pause_test.go b/integration-cli/docker_cli_pause_test.go
+++index 4e32dfc..72c9b4c 100644
+++--- a/integration-cli/docker_cli_pause_test.go
++++++ b/integration-cli/docker_cli_pause_test.go
+++@@ -4,7 +4,7 @@ import (
+++     "fmt"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestPause(c *check.C) {
+++diff --git a/integration-cli/docker_cli_port_test.go b/integration-cli/docker_cli_port_test.go
+++index 63bfc9a..4eb5d44 100644
+++--- a/integration-cli/docker_cli_port_test.go
++++++ b/integration-cli/docker_cli_port_test.go
+++@@ -6,7 +6,7 @@ import (
+++     "sort"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestPortList(c *check.C) {
+++diff --git a/integration-cli/docker_cli_port_unix_test.go b/integration-cli/docker_cli_port_unix_test.go
+++index 0988ca9..4cbf0ee 100644
+++--- a/integration-cli/docker_cli_port_unix_test.go
++++++ b/integration-cli/docker_cli_port_unix_test.go
+++@@ -6,7 +6,7 @@ import (
+++     "net"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestPortHostBinding(c *check.C) {
+++diff --git a/integration-cli/docker_cli_proxy_test.go b/integration-cli/docker_cli_proxy_test.go
+++index 8b55c67..45737f0 100644
+++--- a/integration-cli/docker_cli_proxy_test.go
++++++ b/integration-cli/docker_cli_proxy_test.go
+++@@ -5,7 +5,7 @@ import (
+++     "os/exec"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestCliProxyDisableProxyUnixSock(c *check.C) {
+++diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go
+++index 3b8f6f9..25a2e87 100644
+++--- a/integration-cli/docker_cli_ps_test.go
++++++ b/integration-cli/docker_cli_ps_test.go
+++@@ -11,7 +11,7 @@ import (
+++     "strings"
+++     "time"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestPsListContainers(c *check.C) {
+++diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go
+++index 8bfca8d..581366b 100644
+++--- a/integration-cli/docker_cli_pull_test.go
++++++ b/integration-cli/docker_cli_pull_test.go
+++@@ -11,7 +11,7 @@ import (
+++     "time"
+++ 
+++     "github.com/docker/distribution/digest"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // See issue docker/docker#8141
+++diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go
+++index 111e9f3..a087258 100644
+++--- a/integration-cli/docker_cli_push_test.go
++++++ b/integration-cli/docker_cli_push_test.go
+++@@ -9,7 +9,7 @@ import (
+++     "strings"
+++     "time"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // Pushing an image to a private registry.
+++diff --git a/integration-cli/docker_cli_rename_test.go b/integration-cli/docker_cli_rename_test.go
+++index cac9f3a..3ecc814 100644
+++--- a/integration-cli/docker_cli_rename_test.go
++++++ b/integration-cli/docker_cli_rename_test.go
+++@@ -4,7 +4,7 @@ import (
+++     "strings"
+++ 
+++     "github.com/docker/docker/pkg/stringid"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestRenameStoppedContainer(c *check.C) {
+++diff --git a/integration-cli/docker_cli_restart_test.go b/integration-cli/docker_cli_restart_test.go
+++index 4cabeb9..c558c3d 100644
+++--- a/integration-cli/docker_cli_restart_test.go
++++++ b/integration-cli/docker_cli_restart_test.go
+++@@ -4,7 +4,7 @@ import (
+++     "strings"
+++     "time"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestRestartStoppedContainer(c *check.C) {
+++diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go
+++index 0e57551..69104bf 100644
+++--- a/integration-cli/docker_cli_rm_test.go
++++++ b/integration-cli/docker_cli_rm_test.go
+++@@ -4,7 +4,7 @@ import (
+++     "os"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestRmContainerWithRemovedVolume(c *check.C) {
+++diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go
+++index 8d9f94f..48faf9c 100644
+++--- a/integration-cli/docker_cli_rmi_test.go
++++++ b/integration-cli/docker_cli_rmi_test.go
+++@@ -5,7 +5,7 @@ import (
+++     "os/exec"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestRmiWithContainerFails(c *check.C) {
+++diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go
+++index 9288957..a04d03f 100644
+++--- a/integration-cli/docker_cli_run_test.go
++++++ b/integration-cli/docker_cli_run_test.go
+++@@ -20,7 +20,7 @@ import (
+++ 
+++     "github.com/docker/docker/pkg/nat"
+++     "github.com/docker/libnetwork/resolvconf"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // "test123" should be printed by docker run
+++diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go
+++index 295bace..140b2ba 100644
+++--- a/integration-cli/docker_cli_run_unix_test.go
++++++ b/integration-cli/docker_cli_run_unix_test.go
+++@@ -14,7 +14,7 @@ import (
+++     "time"
+++ 
+++     "github.com/docker/docker/pkg/mount"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++     "github.com/kr/pty"
+++ )
+++ 
+++diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go
+++index 5b4b64d..eb0d63f 100644
+++--- a/integration-cli/docker_cli_save_load_test.go
++++++ b/integration-cli/docker_cli_save_load_test.go
+++@@ -10,7 +10,7 @@ import (
+++     "sort"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // save a repo using gz compression and try to load it using stdout
+++diff --git a/integration-cli/docker_cli_save_load_unix_test.go b/integration-cli/docker_cli_save_load_unix_test.go
+++index 2bca3b8..991b940 100644
+++--- a/integration-cli/docker_cli_save_load_unix_test.go
++++++ b/integration-cli/docker_cli_save_load_unix_test.go
+++@@ -8,7 +8,7 @@ import (
+++     "os"
+++     "os/exec"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++     "github.com/kr/pty"
+++ )
+++ 
+++diff --git a/integration-cli/docker_cli_search_test.go b/integration-cli/docker_cli_search_test.go
+++index d89c05c..40db0df 100644
+++--- a/integration-cli/docker_cli_search_test.go
++++++ b/integration-cli/docker_cli_search_test.go
+++@@ -3,7 +3,7 @@ package main
+++ import (
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // search for repos named  "registry" on the central registry
+++diff --git a/integration-cli/docker_cli_service_test.go b/integration-cli/docker_cli_service_test.go
+++index aaf5e81..91b7b8b 100644
+++--- a/integration-cli/docker_cli_service_test.go
++++++ b/integration-cli/docker_cli_service_test.go
+++@@ -6,7 +6,7 @@ import (
+++     "fmt"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func assertSrvIsAvailable(c *check.C, sname, name string) {
+++diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go
+++index ce5c48e..8027735 100644
+++--- a/integration-cli/docker_cli_start_test.go
++++++ b/integration-cli/docker_cli_start_test.go
+++@@ -5,7 +5,7 @@ import (
+++     "strings"
+++     "time"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // Regression test for https://github.com/docker/docker/issues/7843
+++diff --git a/integration-cli/docker_cli_start_volume_driver_unix_test.go b/integration-cli/docker_cli_start_volume_driver_unix_test.go
+++index 71f9f2d..1fddee0 100644
+++--- a/integration-cli/docker_cli_start_volume_driver_unix_test.go
++++++ b/integration-cli/docker_cli_start_volume_driver_unix_test.go
+++@@ -12,7 +12,7 @@ import (
+++     "path/filepath"
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func init() {
+++diff --git a/integration-cli/docker_cli_stats_test.go b/integration-cli/docker_cli_stats_test.go
+++index 2504028..7cd6367 100644
+++--- a/integration-cli/docker_cli_stats_test.go
++++++ b/integration-cli/docker_cli_stats_test.go
+++@@ -5,7 +5,7 @@ import (
+++     "strings"
+++     "time"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestCliStatsNoStream(c *check.C) {
+++diff --git a/integration-cli/docker_cli_tag_test.go b/integration-cli/docker_cli_tag_test.go
+++index 23f2aef..109dae3 100644
+++--- a/integration-cli/docker_cli_tag_test.go
++++++ b/integration-cli/docker_cli_tag_test.go
+++@@ -4,7 +4,7 @@ import (
+++     "strings"
+++ 
+++     "github.com/docker/docker/pkg/stringutils"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // tagging a named image in a new unprefixed repo should work
+++diff --git a/integration-cli/docker_cli_top_test.go b/integration-cli/docker_cli_top_test.go
+++index 667a6c8..cbca969 100644
+++--- a/integration-cli/docker_cli_top_test.go
++++++ b/integration-cli/docker_cli_top_test.go
+++@@ -3,7 +3,7 @@ package main
+++ import (
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func (s *DockerSuite) TestTopMultipleArgs(c *check.C) {
+++diff --git a/integration-cli/docker_cli_v2_only.go b/integration-cli/docker_cli_v2_only.go
+++index aa3d6a2..539f459 100644
+++--- a/integration-cli/docker_cli_v2_only.go
++++++ b/integration-cli/docker_cli_v2_only.go
+++@@ -6,7 +6,7 @@ import (
+++     "net/http"
+++     "os"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ func makefile(contents string) (string, func(), error) {
+++diff --git a/integration-cli/docker_cli_version_test.go b/integration-cli/docker_cli_version_test.go
+++index f2d8b65..b1ea96c 100644
+++--- a/integration-cli/docker_cli_version_test.go
++++++ b/integration-cli/docker_cli_version_test.go
+++@@ -3,7 +3,7 @@ package main
+++ import (
+++     "strings"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // ensure docker version works
+++diff --git a/integration-cli/docker_cli_wait_test.go b/integration-cli/docker_cli_wait_test.go
+++index 167ea1a..0503fbb 100644
+++--- a/integration-cli/docker_cli_wait_test.go
++++++ b/integration-cli/docker_cli_wait_test.go
+++@@ -6,7 +6,7 @@ import (
+++     "strings"
+++     "time"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // non-blocking wait with 0 exit code
+++diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go
+++index e80015d..fcb3242 100644
+++--- a/integration-cli/docker_utils.go
++++++ b/integration-cli/docker_utils.go
+++@@ -24,7 +24,7 @@ import (
+++     "github.com/docker/docker/opts"
+++     "github.com/docker/docker/pkg/ioutils"
+++     "github.com/docker/docker/pkg/stringutils"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ // Daemon represents a Docker daemon for the testing framework.
+++diff --git a/integration-cli/registry.go b/integration-cli/registry.go
+++index 35e1b4e..4aee021 100644
+++--- a/integration-cli/registry.go
++++++ b/integration-cli/registry.go
+++@@ -9,7 +9,7 @@ import (
+++     "path/filepath"
+++ 
+++     "github.com/docker/distribution/digest"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ const v2binary = "registry-v2"
+++diff --git a/integration-cli/registry_mock.go b/integration-cli/registry_mock.go
+++index e5fb64c..9599eb2 100644
+++--- a/integration-cli/registry_mock.go
++++++ b/integration-cli/registry_mock.go
+++@@ -7,7 +7,7 @@ import (
+++     "strings"
+++     "sync"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ type handlerFunc func(w http.ResponseWriter, r *http.Request)
+++diff --git a/integration-cli/requirements.go b/integration-cli/requirements.go
+++index ce080d5..a08febe 100644
+++--- a/integration-cli/requirements.go
++++++ b/integration-cli/requirements.go
+++@@ -10,7 +10,7 @@ import (
+++     "strings"
+++     "time"
+++ 
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ type testCondition func() bool
+++diff --git a/integration-cli/trust_server.go b/integration-cli/trust_server.go
+++index 89d88a8..dcd245d 100644
+++--- a/integration-cli/trust_server.go
++++++ b/integration-cli/trust_server.go
+++@@ -12,7 +12,7 @@ import (
+++     "time"
+++ 
+++     "github.com/docker/docker/pkg/tlsconfig"
+++-    "github.com/go-check/check"
++++    "gopkg.in/check.v1"
+++ )
+++ 
+++ var notaryBinary = "notary-server"
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1cc421b20f420ac9d4aa7a3a7d472342beed9107
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,17 @@@@
+++Description: Minor changes for github.com/docker/distribution 2.1.1
+++Forwarded: not-needed
+++Author: Tianon Gravi <tianon@debian.org>
+++
+++Index: pkg/registry/registry.go
+++===================================================================
+++--- pkg.orig/registry/registry.go
++++++ pkg/registry/registry.go
+++@@ -185,7 +185,7 @@ func addRequiredHeadersToRedirectedReque
+++ func shouldV2Fallback(err errcode.Error) bool {
+++     logrus.Debugf("v2 error: %T %v", err, err)
+++     switch err.Code {
+++-    case v2.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown:
++++    case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown:
+++             return true
+++     }
+++     return false
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..83cb247206c9257e0f11cffbdbbfe7f7e6895e26
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,18 @@@@
+++Description: Docker is unsupported on kernels < 3.10, so this turns the warning into a fatal error (hard failure)
+++Forwarded: not-needed
+++Author: Tianon Gravi <admwiggin@gmail.com>
+++Reviewed-by: Paul Tagliamonte <paultag@debian.org>
+++
+++diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go
+++index 1bc394c..96e3e62 100644
+++--- a/daemon/daemon_unix.go
++++++ b/daemon/daemon_unix.go
+++@@ -110,7 +110,7 @@ func checkKernel() error {
+++     } else {
+++             if kernel.CompareKernelVersion(k, &kernel.KernelVersionInfo{Kernel: 3, Major: 10, Minor: 0}) < 0 {
+++                     if os.Getenv("DOCKER_NOWARN_KERNEL_VERSION") == "" {
+++-                            logrus.Warnf("You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.10.0.", k.String())
++++                            logrus.Fatalf("ERROR: You are running Linux kernel version %s, which is unsupported for running Docker. Please upgrade your kernel to 3.10+.", k.String())
+++                     }
+++             }
+++     }
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..df6db677bba706f856620ce6bade1f39e55814a9
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,18 @@@@
+++Description: move "overlay" to a higher priority (now that AUFS is not supported in Debian kernels)
+++Author: Tianon Gravi <tianon@debian.org>
+++Forwarded: no
+++
+++diff --git a/daemon/graphdriver/driver_linux.go b/daemon/graphdriver/driver_linux.go
+++index 410a62f..eab78e7 100644
+++--- a/daemon/graphdriver/driver_linux.go
++++++ b/daemon/graphdriver/driver_linux.go
+++@@ -48,8 +48,8 @@ var (
+++             "aufs",
+++             "btrfs",
+++             "zfs",
+++-            "devicemapper",
+++             "overlay",
++++            "devicemapper",
+++             "vfs",
+++     }
+++ 
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9d50b56ef987016f31a42a13cdf55eeb3ed83f32
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,68 @@@@
+++Description: Minor changes for github.com/opencontainers/runc 0.0.4
+++Origin: https://github.com/docker/docker/pull/16244
+++Author: David Calavera <david.calavera@gmail.com>, Tianon Gravi <tianon@debian.org>
+++
+++diff --git a/daemon/container.go b/daemon/container.go
+++index 49c1f41..727bd69 100644
+++--- a/daemon/container.go
++++++ b/daemon/container.go
+++@@ -1088,12 +1088,9 @@ func copyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error)
+++ 
+++ func (container *Container) networkMounts() []execdriver.Mount {
+++     var mounts []execdriver.Mount
+++-    mode := "Z"
+++-    if container.hostConfig.NetworkMode.IsContainer() {
+++-            mode = "z"
+++-    }
++++    shared := container.hostConfig.NetworkMode.IsContainer()
+++     if container.ResolvConfPath != "" {
+++-            label.Relabel(container.ResolvConfPath, container.MountLabel, mode)
++++            label.Relabel(container.ResolvConfPath, container.MountLabel, shared)
+++             mounts = append(mounts, execdriver.Mount{
+++                     Source:      container.ResolvConfPath,
+++                     Destination: "/etc/resolv.conf",
+++@@ -1102,7 +1099,7 @@ func (container *Container) networkMounts() []execdriver.Mount {
+++             })
+++     }
+++     if container.HostnamePath != "" {
+++-            label.Relabel(container.HostnamePath, container.MountLabel, mode)
++++            label.Relabel(container.HostnamePath, container.MountLabel, shared)
+++             mounts = append(mounts, execdriver.Mount{
+++                     Source:      container.HostnamePath,
+++                     Destination: "/etc/hostname",
+++@@ -1111,7 +1108,7 @@ func (container *Container) networkMounts() []execdriver.Mount {
+++             })
+++     }
+++     if container.HostsPath != "" {
+++-            label.Relabel(container.HostsPath, container.MountLabel, mode)
++++            label.Relabel(container.HostsPath, container.MountLabel, shared)
+++             mounts = append(mounts, execdriver.Mount{
+++                     Source:      container.HostsPath,
+++                     Destination: "/etc/hosts",
+++diff --git a/daemon/create.go b/daemon/create.go
+++index a4a740f..655bd8f 100644
+++--- a/daemon/create.go
++++++ b/daemon/create.go
+++@@ -123,7 +123,7 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
+++             if err != nil {
+++                     return nil, nil, err
+++             }
+++-            if err := label.Relabel(v.Path(), container.MountLabel, "z"); err != nil {
++++            if err := label.Relabel(v.Path(), container.MountLabel, true); err != nil {
+++                     return nil, nil, err
+++             }
+++ 
+++diff --git a/daemon/volumes.go b/daemon/volumes.go
+++index 556e304..ba2b017 100644
+++--- a/daemon/volumes.go
++++++ b/daemon/volumes.go
+++@@ -231,7 +231,8 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
+++                     }
+++             }
+++ 
+++-            if err := label.Relabel(bind.Source, container.MountLabel, bind.Relabel); err != nil {
++++            shared := label.IsShared(bind.Relabel)
++++            if err := label.Relabel(bind.Source, container.MountLabel, shared); err != nil {
+++                     return err
+++             }
+++             binds[bind.Destination] = true
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b7b4fdf2dfc438fba0fa2c5cef70c8997de4b4d3
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,19 @@@@
+++cgroupfs-mount-convenience-copy.patch
+++
+++# If upstream ever resolves https://github.com/docker/docker/issues/8969 in a
+++# reasonable way, remove this patch.
+++fatal-error-old-kernels.patch
+++
+++# Once upstream adds EnvFile, remove this patch.
+++change-system-unit-env-file.patch
+++# See also https://github.com/docker/docker/pull/7220#issuecomment-50076589
+++
+++# change "github.com/go-check/check" to "gopkg.in/check.v1"
+++check-v1.patch
+++
+++15404.patch
+++distribution-2.1.1.patch
+++runc-0.0.4.patch
+++
+++overlay.patch
+++cgroupdriver-cgroupfs.patch
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..0429d3556108192c6a6a1b76a444dfc4a2f63b7d
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,53 @@@@
+++#!/bin/bash
+++# Taken from the X Strike Force Build System
+++
+++set -e
+++
+++debDir="$(pwd)/debian"
+++
+++if [ ! -d "$debDir/repack/prune" ]; then
+++     exit 0
+++fi
+++
+++if [ '--upstream-version' != "$1" ]; then
+++     exit 1
+++fi
+++
+++version="$2"
+++filename="$3"
+++
+++if [ -z "$version" ] || [ ! -f "$filename" ]; then
+++     exit 1
+++fi
+++
+++dir="$(dirname "$filename")"
+++filename="$(basename "$filename")"
+++dir="$(readlink -f "$dir")"
+++tempdir="$(mktemp -d)"
+++
+++cd "$tempdir"
+++tar xf "$dir/$filename"
+++cat "$debDir"/repack/prune/* | while read file; do
+++     if [ -e */"$file" ]; then
+++             echo "Pruning $file"
+++             rm -rf */"$file"
+++     fi
+++done
+++
+++dfsgfilename="$filename"
+++if [[ "$dfsgfilename" != *[~+]ds* ]]; then
+++     pkg="$(dpkg-parsechangelog -l"$debDir/changelog" -SSource)"
+++     ver="$(dpkg-parsechangelog -l"$debDir/changelog" -SVersion)"
+++     origVer="${ver%-*}" # strip everything from the last dash
+++     origVer="$(echo "$origVer" | sed -r 's/^[0-9]+://')" # strip epoch
+++     upstreamVer="${origVer%%[+~]ds*}"
+++     dfsgBits="${origVer#$upstreamVer}"
+++     
+++     dfsgfilename="${dfsgfilename/.orig/$dfsgBits.orig}"
+++fi
+++tar -czf "$dir/$dfsgfilename" *
+++cd "$dir"
+++rm -rf "$tempdir"
+++echo "Done pruning upstream tarball into $dfsgfilename"
+++
+++exit 0
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1acf52e9df192307d007a19c73c879c73a2d541b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,2 @@@@
+++./vendor
+++./Godeps/_workspace
diff --cc debian/rules
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d2583651b2aeac7079d3ab629055db312c11949c
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,122 @@@@
+++#!/usr/bin/make -f
+++# -*- makefile -*-
+++
+++DOCKER_GOPKG = github.com/docker/docker
+++
+++# MUST STAY IN SYNC WITH LIST IN "debian/helpers/download-libcontainer"
+++MULTI_ORIG_GOPKGS = \
+++     github.com/docker/libnetwork
+++
+++# the list of Go packages to include in golang-github-docker-docker-dev
+++DEV_GOPKGS = \
+++     github.com/docker/docker/autogen/dockerversion \
+++     github.com/docker/docker/pkg
+++
+++# temporary build path (see http://golang.org/doc/code.html#GOPATH)
+++export GOPATH = $(CURDIR)/.gopath
+++GOPATH_PACKAGED = /usr/share/gocode
+++
+++# a few helpful variables for deduplication
+++DOCKER_BINPATH = /usr/bin/docker
+++DOCKER_VERSION = $(shell cat VERSION)
+++
+++export DOCKER_GITCOMMIT = $(shell ./debian/helpers/gitcommit.sh $(DOCKER_VERSION))
+++export DOCKER_INITPATH = /usr/lib/docker.io/dockerinit
+++
+++# AppArmor can be optionally used in Debian and is there by default in Ubuntu, so we need support for it compiled into our binary
+++# same story with SELinux
+++export DOCKER_BUILDTAGS = apparmor selinux
+++
+++
+++APPARMOR_RECOMMENDS = $(shell dpkg-vendor --is Ubuntu && echo apparmor)
+++BUILT_LIBC := $(shell dpkg-query -f '$${source:Package} (= $${source:Version})' -W libc-dev-bin)
+++
+++override_dh_gencontrol:
+++     echo 'apparmor:Recommends=$(APPARMOR_RECOMMENDS)' >> debian/docker.io.substvars
+++     echo 'libc:Built-Using=$(BUILT_LIBC)' >> debian/docker.io.substvars
+++     dh_gencontrol
+++
+++
+++setup-gopath:
+++     # make sure we have our multitarball deps (from tarballs or from vendor/ in nightlies)
+++     @set -ex; \
+++             for package in $(MULTI_ORIG_GOPKGS); do \
+++                     [ -d $$(basename $$package) ] || { [ -d vendor/src/$$package ] && ln -sf vendor/src/$$package $$(basename $$package); }; \
+++             done
+++     
+++     # we need to make sure all deps are in our GOPATH
+++     mkdir -p "$(GOPATH)"
+++     @set -ex; \
+++             for package in $(DOCKER_GOPKG) $(MULTI_ORIG_GOPKGS); do \
+++                     [ $$package = $(DOCKER_GOPKG) ] \
+++                             && src=. \
+++                             || src=$$(basename $$package); \
+++                     mkdir -p "$$GOPATH/src/$$(dirname $$package)"; \
+++                     ln -sfT "$$(readlink -f "$$src")" "$$GOPATH/src/$$package"; \
+++             done
+++
+++override_dh_auto_build:
+++     @bash -c '{ [ "$$DOCKER_GITCOMMIT" ]; } || { echo; echo "error: missing DOCKER_GITCOMMIT - see debian/upstream-version-gitcommits"; echo; exit 2; } >&2'
+++     
+++     ./debian/rules setup-gopath
+++     
+++     # build "docker" and "dockerinit"
+++     GOPATH="$$GOPATH:$(GOPATH_PACKAGED)" \
+++     ./hack/make.sh dynbinary
+++     
+++     # compile man pages
+++     ./man/md2man-all.sh -q
+++
+++
+++override_dh_auto_install:
+++     # install docker binary
+++     mkdir -p debian/docker.io/$(dir $(DOCKER_BINPATH))
+++     cp -aT bundles/$(DOCKER_VERSION)/dynbinary/docker-$(DOCKER_VERSION) debian/docker.io/$(DOCKER_BINPATH)
+++     
+++     # install dockerinit binary
+++     mkdir -p debian/docker.io/$(dir $(DOCKER_INITPATH))
+++     cp -aT bundles/$(DOCKER_VERSION)/dynbinary/dockerinit-$(DOCKER_VERSION) debian/docker.io/$(DOCKER_INITPATH)
+++     
+++     # Most of the source of docker does not make a library,
+++     #   so only ship the reusable parts (and in a separate package).
+++     @set -ex; \
+++             for package in $(DEV_GOPKGS); do \
+++                     mkdir -p "debian/golang-github-docker-docker-dev/$(GOPATH_PACKAGED)/src/$$package"; \
+++                     cp -aT "$$(readlink -f "$$GOPATH/src/$$package")" "debian/golang-github-docker-docker-dev/$(GOPATH_PACKAGED)/src/$$package"; \
+++             done
+++
+++
+++override_dh_strip:
+++     # the SHA1 of dockerinit is important: don't strip it
+++     # also, Go has lots of problems with stripping, so just don't
+++
+++
+++override_dh_auto_test:
+++
+++
+++override_dh_installinit:
+++     dh_installinit --name=docker --no-restart-on-upgrade
+++
+++
+++override_dh_installudev:
+++     # use priority z80 to match the upstream priority of 80
+++     dh_installudev --priority=z80
+++
+++
+++override_dh_auto_clean:
+++     dh_auto_clean
+++     
+++     # GOPATH is created by us
+++     rm -rf "$(GOPATH)"
+++     
+++     # autogen is created by hack/make.sh
+++     # bundles is created by hack/make.sh
+++     # man/man*/ is created by man/md2man-all.sh
+++     rm -rf autogen bundles man/man*/
+++     
+++     # TODO FIGURE OUT WHY THIS IS CREATED AND SEND A PR UPSTREAM!!!
+++     rm -f a.out
+++
+++
+++%:
+++     dh $@ --with=systemd,bash-completion
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8b17d44dddc1172e3c528bf5aeaf5373ee7cdbed
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,6 @@@@
+++docker.io source: source-contains-unsafe-symlink utils/testdata/fs/g
+++docker.io source: source-contains-unsafe-symlink utils/testdata/fs/a/d
+++# Hilariously, these are used to test unsafe symlinks.
+++#                      much testing
+++#                                        wow
+++#     very unsafe
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..163aaf8d82b6c54f23c45f32895dbdfdcc27b047
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,1 @@@@
+++3.0 (quilt)
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..30e7631798aed086782a24f099a7ad30b1820d72
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,3 @@@@
+++Tests: integration
+++Depends: ca-certificates, curl, git, golang-check.v1-dev, @, @builddeps@
+++Restrictions: allow-stderr isolation-machine needs-root rw-build-tree
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..7c8c8d4ac390608fb78ee4eef844ed30fe58232d
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,23 @@@@
+++#!/bin/bash
+++set -e
+++
+++# apply patches
+++dpkg-source --before-build .
+++
+++# prepare the environment
+++./debian/rules setup-gopath
+++export GOPATH="$PWD/.gopath:/usr/share/gocode"
+++export DOCKER_GITCOMMIT="$(./debian/helpers/gitcommit.sh)"
+++
+++# docker's tests need an unprivileged user available at this username
+++useradd --system --gid docker --comment 'Docker Test Suite Unprivileged User' unprivilegeduser
+++
+++# run the tests
+++./hack/make.sh test-integration-cli
+++
+++# clean up cruft we've created
+++./debian/rules clean
+++userdel --force unprivilegeduser
+++
+++# unapply patches
+++dpkg-source --after-build .
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..423cef9104f798f977bfc88a4a590fcb3eff7e02
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,77 @@@@
+++# To determine the proper value for this, download
+++# https://get.docker.io/builds/Linux/x86_64/docker-VERSION, chmod +x, and then
+++# run ./docker-VERSION -v, which will list the exact build hash needed.
+++
+++0.5.3: 17c92b8
+++0.6.0: f4a4f1c
+++0.6.1: 5105263
+++0.6.2: 081543c
+++0.6.3: b0a49a3
+++0.6.4: 2f74b1c
+++0.6.5: 3ff8459
+++0.6.6: 6d42040
+++0.6.7: cb48ecc
+++0.7.0: 0d078b6
+++0.7.1: 88df052
+++0.7.2: 28b162e
+++0.7.3: 8502ad4
+++0.7.4: 010d74e
+++0.7.5: c348c04
+++0.7.6: bc3b2ec
+++0.8.0: cc3a8c8
+++0.8.1: a1598d1
+++0.9.0: 2b3fdf2
+++0.9.1: 3600720
+++0.10.0: dc9c28f
+++0.11.0: 15209c3
+++0.11.1: fb99f99
+++0.12.0: 14680bf
+++1.0.0: 63fe64c
+++1.0.1: 990021a
+++1.1.0: 79812e3
+++1.1.1: bd609d2
+++1.1.2: d84a070
+++1.2.0: fa7b24f
+++1.3.0: c78088f
+++1.3.1: 4e9bbfa
+++1.3.2: 39fa2fa
+++1.3.3: d344625
+++1.4.0: 4595d4f
+++1.4.1: 5bc2ff8
+++1.5.0-rc2: a393450
+++1.5.0-rc3: c02092d
+++1.5.0-rc4: a1cae77
+++1.5.0: a8a31ef
+++1.6.0-rc1: 746e830
+++1.6.0-rc2: c5ee149
+++1.6.0-rc3: 20d4e6f
+++1.6.0-rc4: e2e39fc
+++1.6.0-rc6: f181f77
+++1.6.0-rc7: 7e26e41
+++1.6.0: 4749651
+++1.6.1: 97cd073
+++1.6.2: 7c8fca2
+++1.7.0-rc1: 395cced
+++1.7.0-rc2: 7ddecf7
+++1.7.0-rc3: 94159c9
+++1.7.0-rc4: cc5c791
+++1.7.0-rc5: f417602
+++1.7.0: 0baf609
+++1.7.1-rc1: f8f912a
+++1.7.1-rc2: 3ff5c86
+++1.7.1-rc3: c2e658d
+++1.7.1: 786b29d
+++1.8.0-rc1: 4f1c66a
+++1.8.0-rc2: 8a9d4ea
+++1.8.0-rc3: 696147b
+++1.8.0: 0d03096
+++1.8.1: d12ea79
+++1.8.2-rc1: 28220ac
+++1.8.2: 0a8c2e3
+++1.8.3: f4bf5c7
+++1.9.0-rc1: 9291a0e
+++1.9.0-rc2: 60d36f7
+++1.9.0-rc3: 2100b94
+++1.9.0-rc4: e6f5a3c
+++1.9.0-rc5: 9318004
+++1.9.0: 76d6bc9
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..bcc58c5d2c62ba6d17e31f62fc97c5f786c54840
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,4 @@@@
+++contrib/syntax/vim/doc/* /usr/share/vim/addons/doc/
+++contrib/syntax/vim/ftdetect/* /usr/share/vim/addons/ftdetect/
+++contrib/syntax/vim/syntax/* /usr/share/vim/addons/syntax/
+++debian/vim-syntax-docker.yaml /usr/share/vim/registry/
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..517b591451530c8619987c53ed0fd60c66f70f42
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,6 @@@@
+++addon: dockerfile
+++description: "Addon to highlight Docker's Dockerfiles"
+++files:
+++  - doc/dockerfile.txt
+++  - ftdetect/dockerfile.vim
+++  - syntax/dockerfile.vim
diff --cc debian/watch
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..05c1f00e8532e4d9882ceb9accb4b62d29757884
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,6 @@@@
+++version=3
+++opts=\
+++dversionmangle=s/[+~](debian|dfsg|ds|deb)\d*$//,\
+++uversionmangle=s/(\d)[_\.\-\+]?((RC|rc|pre|dev|beta|alpha)\d*)$/$1~$2/,\
+++filenamemangle=s/.+\/v(\d\S*)\.tar\.gz/docker.io_$1.orig.tar.gz/ \
+++  https://github.com/docker/docker/tags .*/v(\d\S*)\.tar\.gz debian ./debian/repack.sh
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..c03c9653abf662e0b2ee5ab23e46ca754f01ab5d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,33 @@@@
+++# Compiled Object files, Static and Dynamic libs (Shared Objects)
+++*.o
+++*.a
+++*.so
+++
+++# Folders
+++_obj
+++_test
+++
+++# Architecture specific extensions/prefixes
+++*.[568vq]
+++[568vq].out
+++
+++*.cgo1.go
+++*.cgo2.c
+++_cgo_defun.c
+++_cgo_gotypes.go
+++_cgo_export.*
+++
+++_testmain.go
+++
+++*.exe
+++*.test
+++*.prof
+++
+++# Coverage
+++*.tmp
+++*.coverprofile
+++
+++# IDE files
+++.project
+++
+++libnetwork-build.created
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..23b929562c7c09cf3c47f5a71c533e624f3e06bf
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,137 @@@@
+++{
+++     "ImportPath": "github.com/docker/libnetwork",
+++     "GoVersion": "go1.4.2",
+++     "Packages": [
+++             "./..."
+++     ],
+++     "Deps": [
+++             {
+++                     "ImportPath": "github.com/BurntSushi/toml",
+++                     "Comment": "v0.1.0-16-gf706d00",
+++                     "Rev": "f706d00e3de6abe700c994cdd545a1a4915af060"
+++             },
+++             {
+++                     "ImportPath": "github.com/Sirupsen/logrus",
+++                     "Comment": "v0.6.4-12-g467d9d5",
+++                     "Rev": "467d9d55c2d2c17248441a8fc661561161f40d5e"
+++             },
+++             {
+++                     "ImportPath": "github.com/armon/go-metrics",
+++                     "Rev": "eb0af217e5e9747e41dd5303755356b62d28e3ec"
+++             },
+++             {
+++                     "ImportPath": "github.com/coreos/go-etcd/etcd",
+++                     "Comment": "v2.0.0-7-g73a8ef7",
+++                     "Rev": "73a8ef737e8ea002281a28b4cb92a1de121ad4c6"
+++             },
+++             {
+++                     "ImportPath": "github.com/docker/docker/pkg/homedir",
+++                     "Comment": "v1.4.1-4106-g637023a",
+++                     "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693"
+++             },
+++             {
+++                     "ImportPath": "github.com/docker/docker/pkg/ioutils",
+++                     "Comment": "v1.4.1-4106-g637023a",
+++                     "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693"
+++             },
+++             {
+++                     "ImportPath": "github.com/docker/docker/pkg/mflag",
+++                     "Comment": "v1.4.1-4106-g637023a",
+++                     "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693"
+++             },
+++             {
+++                     "ImportPath": "github.com/docker/docker/pkg/parsers",
+++                     "Comment": "v1.4.1-4106-g637023a",
+++                     "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693"
+++             },
+++             {
+++                     "ImportPath": "github.com/docker/docker/pkg/plugins",
+++                     "Comment": "v1.4.1-4106-g637023a",
+++                     "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693"
+++             },
+++             {
+++                     "ImportPath": "github.com/docker/docker/pkg/proxy",
+++                     "Comment": "v1.4.1-4106-g637023a",
+++                     "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693"
+++             },
+++             {
+++                     "ImportPath": "github.com/docker/docker/pkg/reexec",
+++                     "Comment": "v1.4.1-4106-g637023a",
+++                     "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693"
+++             },
+++             {
+++                     "ImportPath": "github.com/docker/docker/pkg/stringid",
+++                     "Comment": "v1.4.1-4106-g637023a",
+++                     "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693"
+++             },
+++             {
+++                     "ImportPath": "github.com/docker/docker/pkg/term",
+++                     "Comment": "v1.4.1-4106-g637023a",
+++                     "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693"
+++             },
+++             {
+++                     "ImportPath": "github.com/docker/libcontainer/user",
+++                     "Comment": "v1.4.0-495-g3e66118",
+++                     "Rev": "3e661186ba24f259d3860f067df052c7f6904bee"
+++             },
+++             {
+++                     "ImportPath": "github.com/docker/libkv",
+++                     "Rev": "60c7c881345b3c67defc7f93a8297debf041d43c"
+++             },
+++             {
+++                     "ImportPath": "github.com/godbus/dbus",
+++                     "Comment": "v2-3-g4160802",
+++                     "Rev": "41608027bdce7bfa8959d653a00b954591220e67"
+++             },
+++             {
+++                     "ImportPath": "github.com/gorilla/context",
+++                     "Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd"
+++             },
+++             {
+++                     "ImportPath": "github.com/gorilla/mux",
+++                     "Rev": "8096f47503459bcc74d1f4c487b7e6e42e5746b5"
+++             },
+++             {
+++                     "ImportPath": "github.com/hashicorp/consul/api",
+++                     "Comment": "v0.5.0rc1-66-g954aec6",
+++                     "Rev": "954aec66231b79c161a4122b023fbcad13047f79"
+++             },
+++             {
+++                     "ImportPath": "github.com/hashicorp/go-msgpack/codec",
+++                     "Rev": "71c2886f5a673a35f909803f38ece5810165097b"
+++             },
+++             {
+++                     "ImportPath": "github.com/hashicorp/memberlist",
+++                     "Rev": "9a1e242e454d2443df330bdd51a436d5a9058fc4"
+++             },
+++             {
+++                     "ImportPath": "github.com/hashicorp/serf/serf",
+++                     "Comment": "v0.6.4",
+++                     "Rev": "7151adcef72687bf95f451a2e0ba15cb19412bf2"
+++             },
+++             {
+++                     "ImportPath": "github.com/samuel/go-zookeeper/zk",
+++                     "Rev": "d0e0d8e11f318e000a8cc434616d69e329edc374"
+++             },
+++             {
+++                     "ImportPath": "github.com/stretchr/objx",
+++                     "Rev": "cbeaeb16a013161a98496fad62933b1d21786672"
+++             },
+++             {
+++                     "ImportPath": "github.com/stretchr/testify/assert",
+++                     "Rev": "dab07ac62d4905d3e48d17dc549c684ac3b7c15a"
+++             },
+++             {
+++                     "ImportPath": "github.com/stretchr/testify/mock",
+++                     "Rev": "dab07ac62d4905d3e48d17dc549c684ac3b7c15a"
+++             },
+++             {
+++                     "ImportPath": "github.com/vishvananda/netns",
+++                     "Rev": "493029407eeb434d0c2d44e02ea072ff2488d322"
+++             },
+++             {
+++                     "ImportPath": "github.com/vishvananda/netlink",
+++                     "Rev": "4b5dce31de6d42af5bb9811c6d265472199e0fec"
+++             }
+++     ]
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4cdaa53d56d71d3ec11dc34a2811ca57cb6fa35b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,5 @@@@
+++This directory tree is generated automatically by godep.
+++
+++Please do not edit.
+++
+++See https://github.com/tools/godep for more information.
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e06d2081865a766a8668acc12878f98b27fc9ea0
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,202 @@@@
+++Apache License
+++                           Version 2.0, January 2004
+++                        http://www.apache.org/licenses/
+++
+++   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+++
+++   1. Definitions.
+++
+++      "License" shall mean the terms and conditions for use, reproduction,
+++      and distribution as defined by Sections 1 through 9 of this document.
+++
+++      "Licensor" shall mean the copyright owner or entity authorized by
+++      the copyright owner that is granting the License.
+++
+++      "Legal Entity" shall mean the union of the acting entity and all
+++      other entities that control, are controlled by, or are under common
+++      control with that entity. For the purposes of this definition,
+++      "control" means (i) the power, direct or indirect, to cause the
+++      direction or management of such entity, whether by contract or
+++      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+++      outstanding shares, or (iii) beneficial ownership of such entity.
+++
+++      "You" (or "Your") shall mean an individual or Legal Entity
+++      exercising permissions granted by this License.
+++
+++      "Source" form shall mean the preferred form for making modifications,
+++      including but not limited to software source code, documentation
+++      source, and configuration files.
+++
+++      "Object" form shall mean any form resulting from mechanical
+++      transformation or translation of a Source form, including but
+++      not limited to compiled object code, generated documentation,
+++      and conversions to other media types.
+++
+++      "Work" shall mean the work of authorship, whether in Source or
+++      Object form, made available under the License, as indicated by a
+++      copyright notice that is included in or attached to the work
+++      (an example is provided in the Appendix below).
+++
+++      "Derivative Works" shall mean any work, whether in Source or Object
+++      form, that is based on (or derived from) the Work and for which the
+++      editorial revisions, annotations, elaborations, or other modifications
+++      represent, as a whole, an original work of authorship. For the purposes
+++      of this License, Derivative Works shall not include works that remain
+++      separable from, or merely link (or bind by name) to the interfaces of,
+++      the Work and Derivative Works thereof.
+++
+++      "Contribution" shall mean any work of authorship, including
+++      the original version of the Work and any modifications or additions
+++      to that Work or Derivative Works thereof, that is intentionally
+++      submitted to Licensor for inclusion in the Work by the copyright owner
+++      or by an individual or Legal Entity authorized to submit on behalf of
+++      the copyright owner. For the purposes of this definition, "submitted"
+++      means any form of electronic, verbal, or written communication sent
+++      to the Licensor or its representatives, including but not limited to
+++      communication on electronic mailing lists, source code control systems,
+++      and issue tracking systems that are managed by, or on behalf of, the
+++      Licensor for the purpose of discussing and improving the Work, but
+++      excluding communication that is conspicuously marked or otherwise
+++      designated in writing by the copyright owner as "Not a Contribution."
+++
+++      "Contributor" shall mean Licensor and any individual or Legal Entity
+++      on behalf of whom a Contribution has been received by Licensor and
+++      subsequently incorporated within the Work.
+++
+++   2. Grant of Copyright License. Subject to the terms and conditions of
+++      this License, each Contributor hereby grants to You a perpetual,
+++      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+++      copyright license to reproduce, prepare Derivative Works of,
+++      publicly display, publicly perform, sublicense, and distribute the
+++      Work and such Derivative Works in Source or Object form.
+++
+++   3. Grant of Patent License. Subject to the terms and conditions of
+++      this License, each Contributor hereby grants to You a perpetual,
+++      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+++      (except as stated in this section) patent license to make, have made,
+++      use, offer to sell, sell, import, and otherwise transfer the Work,
+++      where such license applies only to those patent claims licensable
+++      by such Contributor that are necessarily infringed by their
+++      Contribution(s) alone or by combination of their Contribution(s)
+++      with the Work to which such Contribution(s) was submitted. If You
+++      institute patent litigation against any entity (including a
+++      cross-claim or counterclaim in a lawsuit) alleging that the Work
+++      or a Contribution incorporated within the Work constitutes direct
+++      or contributory patent infringement, then any patent licenses
+++      granted to You under this License for that Work shall terminate
+++      as of the date such litigation is filed.
+++
+++   4. Redistribution. You may reproduce and distribute copies of the
+++      Work or Derivative Works thereof in any medium, with or without
+++      modifications, and in Source or Object form, provided that You
+++      meet the following conditions:
+++
+++      (a) You must give any other recipients of the Work or
+++          Derivative Works a copy of this License; and
+++
+++      (b) You must cause any modified files to carry prominent notices
+++          stating that You changed the files; and
+++
+++      (c) You must retain, in the Source form of any Derivative Works
+++          that You distribute, all copyright, patent, trademark, and
+++          attribution notices from the Source form of the Work,
+++          excluding those notices that do not pertain to any part of
+++          the Derivative Works; and
+++
+++      (d) If the Work includes a "NOTICE" text file as part of its
+++          distribution, then any Derivative Works that You distribute must
+++          include a readable copy of the attribution notices contained
+++          within such NOTICE file, excluding those notices that do not
+++          pertain to any part of the Derivative Works, in at least one
+++          of the following places: within a NOTICE text file distributed
+++          as part of the Derivative Works; within the Source form or
+++          documentation, if provided along with the Derivative Works; or,
+++          within a display generated by the Derivative Works, if and
+++          wherever such third-party notices normally appear. The contents
+++          of the NOTICE file are for informational purposes only and
+++          do not modify the License. You may add Your own attribution
+++          notices within Derivative Works that You distribute, alongside
+++          or as an addendum to the NOTICE text from the Work, provided
+++          that such additional attribution notices cannot be construed
+++          as modifying the License.
+++
+++      You may add Your own copyright statement to Your modifications and
+++      may provide additional or different license terms and conditions
+++      for use, reproduction, or distribution of Your modifications, or
+++      for any such Derivative Works as a whole, provided Your use,
+++      reproduction, and distribution of the Work otherwise complies with
+++      the conditions stated in this License.
+++
+++   5. Submission of Contributions. Unless You explicitly state otherwise,
+++      any Contribution intentionally submitted for inclusion in the Work
+++      by You to the Licensor shall be under the terms and conditions of
+++      this License, without any additional terms or conditions.
+++      Notwithstanding the above, nothing herein shall supersede or modify
+++      the terms of any separate license agreement you may have executed
+++      with Licensor regarding such Contributions.
+++
+++   6. Trademarks. This License does not grant permission to use the trade
+++      names, trademarks, service marks, or product names of the Licensor,
+++      except as required for reasonable and customary use in describing the
+++      origin of the Work and reproducing the content of the NOTICE file.
+++
+++   7. Disclaimer of Warranty. Unless required by applicable law or
+++      agreed to in writing, Licensor provides the Work (and each
+++      Contributor provides its Contributions) on an "AS IS" BASIS,
+++      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+++      implied, including, without limitation, any warranties or conditions
+++      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+++      PARTICULAR PURPOSE. You are solely responsible for determining the
+++      appropriateness of using or redistributing the Work and assume any
+++      risks associated with Your exercise of permissions under this License.
+++
+++   8. Limitation of Liability. In no event and under no legal theory,
+++      whether in tort (including negligence), contract, or otherwise,
+++      unless required by applicable law (such as deliberate and grossly
+++      negligent acts) or agreed to in writing, shall any Contributor be
+++      liable to You for damages, including any direct, indirect, special,
+++      incidental, or consequential damages of any character arising as a
+++      result of this License or out of the use or inability to use the
+++      Work (including but not limited to damages for loss of goodwill,
+++      work stoppage, computer failure or malfunction, or any and all
+++      other commercial damages or losses), even if such Contributor
+++      has been advised of the possibility of such damages.
+++
+++   9. Accepting Warranty or Additional Liability. While redistributing
+++      the Work or Derivative Works thereof, You may choose to offer,
+++      and charge a fee for, acceptance of support, warranty, indemnity,
+++      or other liability obligations and/or rights consistent with this
+++      License. However, in accepting such obligations, You may act only
+++      on Your own behalf and on Your sole responsibility, not on behalf
+++      of any other Contributor, and only if You agree to indemnify,
+++      defend, and hold each Contributor harmless for any liability
+++      incurred by, or claims asserted against, such Contributor by reason
+++      of your accepting any such warranty or additional liability.
+++
+++   END OF TERMS AND CONDITIONS
+++
+++   APPENDIX: How to apply the Apache License to your work.
+++
+++      To apply the Apache License to your work, attach the following
+++      boilerplate notice, with the fields enclosed by brackets "{}"
+++      replaced with your own identifying information. (Don't include
+++      the brackets!)  The text should be enclosed in the appropriate
+++      comment syntax for the file format. We also recommend that a
+++      file or class name and description of purpose be included on the
+++      same "printed page" as the copyright notice for easier
+++      identification within third-party archives.
+++
+++   Copyright {yyyy} {name of copyright owner}
+++
+++   Licensed under the Apache License, Version 2.0 (the "License");
+++   you may not use this file except in compliance with the License.
+++   You may obtain a copy of the License at
+++
+++       http://www.apache.org/licenses/LICENSE-2.0
+++
+++   Unless required by applicable law or agreed to in writing, software
+++   distributed under the License is distributed on an "AS IS" BASIS,
+++   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+++   See the License for the specific language governing permissions and
+++   limitations under the License.
+++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..69f1e9b880e13283077f51da0748e31cf17b4ce2
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,5 @@@@
+++Alessandro Boch <aboch@docker.com> (@aboch)
+++Alexandr Morozov <lk4d4@docker.com> (@LK4D4)
+++Arnaud Porterie <arnaud@docker.com> (@icecrime)
+++Jana Radhakrishnan <mrjana@docker.com> (@mrjana)
+++Madhu Venugopal <madhu@docker.com> (@mavenugo)
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..deb510cf8c52489c81befd461898bfe88e83df1b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,79 @@@@
+++.PHONY: all all-local build build-local check check-code check-format run-tests check-local install-deps coveralls circle-ci
+++SHELL=/bin/bash
+++build_image=libnetwork-build
+++dockerargs = --privileged -v $(shell pwd):/go/src/github.com/docker/libnetwork -w /go/src/github.com/docker/libnetwork
+++container_env = -e "INSIDECONTAINER=-incontainer=true"
+++docker = docker run --rm ${dockerargs} ${container_env} ${build_image}
+++ciargs = -e "COVERALLS_TOKEN=$$COVERALLS_TOKEN" -e "INSIDECONTAINER=-incontainer=true"
+++cidocker = docker run ${ciargs} ${dockerargs} golang:1.4
+++
+++all: ${build_image}.created
+++     ${docker} make all-local
+++
+++all-local: check-local build-local
+++
+++${build_image}.created:
+++     docker run --name=libnetworkbuild -v $(shell pwd):/go/src/github.com/docker/libnetwork -w /go/src/github.com/docker/libnetwork golang:1.4 make install-deps
+++     docker commit libnetworkbuild ${build_image}
+++     docker rm libnetworkbuild
+++     touch ${build_image}.created
+++
+++build: ${build_image}.created
+++     ${docker} make build-local
+++
+++build-local:
+++     $(shell which godep) go build -tags libnetwork_discovery ./...
+++
+++check: ${build_image}.created
+++     ${docker} make check-local
+++
+++check-code:
+++     @echo "Checking code... "
+++     test -z "$$(golint ./... | tee /dev/stderr)"
+++     go vet ./...
+++     @echo "Done checking code"
+++
+++check-format:
+++     @echo "Checking format... "
+++     test -z "$$(goimports -l . | grep -v Godeps/_workspace/src/ | tee /dev/stderr)"
+++     @echo "Done checking format"
+++
+++run-tests:
+++     @echo "Running tests... "
+++     @echo "mode: count" > coverage.coverprofile
+++     @for dir in $$(find . -maxdepth 10 -not -path './.git*' -not -path '*/_*' -type d); do \
+++         if ls $$dir/*.go &> /dev/null; then \
+++             pushd . &> /dev/null ; \
+++             cd $$dir ; \
+++             $(shell which godep) go test ${INSIDECONTAINER} -test.parallel 3 -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"
+++
+++check-local:         check-format check-code run-tests 
+++
+++install-deps:
+++     apt-get update && apt-get -y install iptables
+++     git clone https://github.com/golang/tools /go/src/golang.org/x/tools
+++     go install golang.org/x/tools/cmd/vet
+++     go install golang.org/x/tools/cmd/goimports
+++     go install golang.org/x/tools/cmd/cover
+++     go get github.com/tools/godep
+++     go get github.com/golang/lint/golint
+++     go get github.com/mattn/goveralls
+++
+++coveralls:
+++     -@goveralls -service circleci -coverprofile=coverage.coverprofile -repotoken $$COVERALLS_TOKEN
+++
+++# CircleCI's Docker fails when cleaning up using the --rm flag
+++# The following target is a workaround for this
+++
+++circle-ci:
+++     @${cidocker} make install-deps check-local coveralls
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..90fcbe017f9badd9f2a28f72e6e485350b9e1061
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,88 @@@@
+++# libnetwork - networking for containers
+++
+++[![Circle CI](https://circleci.com/gh/docker/libnetwork/tree/master.svg?style=svg)](https://circleci.com/gh/docker/libnetwork/tree/master) [![Coverage Status](https://coveralls.io/repos/docker/libnetwork/badge.svg)](https://coveralls.io/r/docker/libnetwork) [![GoDoc](https://godoc.org/github.com/docker/libnetwork?status.svg)](https://godoc.org/github.com/docker/libnetwork)
+++
+++Libnetwork provides a native Go implementation for connecting containers
+++
+++The goal of libnetwork is to deliver a robust Container Network Model that provides a consistent programming interface and the required network abstractions for applications.
+++
+++**NOTE**: libnetwork project is under heavy development and is not ready for general use.
+++
+++#### Design
+++Please refer to the [design](docs/design.md) for more information.
+++
+++#### Using libnetwork
+++
+++There are many networking solutions available to suit a broad range of use-cases. libnetwork uses a driver / plugin model to support all of these solutions while abstracting the complexity of the driver implementations by exposing a simple and consistent Network Model to users.
+++
+++
+++```go
+++        // Create a new controller instance
+++        controller, err := libnetwork.New()
+++        if err != nil {
+++                return
+++        }
+++
+++        // Select and configure the network driver
+++        networkType := "bridge"
+++
+++        driverOptions := options.Generic{}
+++        genericOption := make(map[string]interface{})
+++        genericOption[netlabel.GenericData] = driverOptions
+++        err := controller.ConfigureNetworkDriver(networkType, genericOption)
+++        if err != nil {
+++                return
+++        }
+++
+++        // Create a network for containers to join.
+++        // NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can make of
+++        network, err := controller.NewNetwork(networkType, "network1")
+++        if err != nil {
+++                return
+++        }
+++
+++        // For each new container: allocate IP and interfaces. The returned network
+++        // settings will be used for container infos (inspect and such), as well as
+++        // iptables rules for port publishing. This info is contained or accessible
+++        // from the returned endpoint.
+++        ep, err := network.CreateEndpoint("Endpoint1")
+++        if err != nil {
+++                return
+++        }
+++
+++        // A container can join the endpoint by providing the container ID to the join
+++        // api.
+++        // Join accepts Variadic arguments which will be made use of by libnetwork and Drivers
+++        err = ep.Join("container1",
+++                libnetwork.JoinOptionHostname("test"),
+++                libnetwork.JoinOptionDomainname("docker.io"))
+++        if err != nil {
+++                return
+++        }
+++
+++             // libnetwork client can check the endpoint's operational data via the Info() API
+++             epInfo, err := ep.DriverInfo()
+++             mapData, ok := epInfo[netlabel.PortMap]
+++             if ok {
+++                     portMapping, ok := mapData.([]netutils.PortBinding)
+++                     if ok {
+++                             fmt.Printf("Current port mapping for endpoint %s: %v", ep.Name(), portMapping)
+++                     }
+++             }
+++
+++```
+++#### Current Status
+++Please watch this space for updates on the progress.
+++
+++Currently libnetwork is nothing more than an attempt to modularize the Docker platform's networking subsystem by moving it into libnetwork as a library.
+++
+++## Future
+++Please refer to [roadmap](ROADMAP.md) for more information.
+++
+++## Contributing
+++
+++Want to hack on libnetwork? [Docker's contributions guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md) apply.
+++
+++## Copyright and license
+++Code and documentation copyright 2015 Docker, inc. Code released under the Apache 2.0 license. Docs released under Creative commons.
+++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9cb3174ec5225c75297361dc900d91421cde748e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,20 @@@@
+++# Roadmap
+++
+++This document defines the high-level goals of the libnetwork project. See [Project Planning](#project-planning) for information on Releases.
+++
+++## Long-term Goal
+++
+++libnetwork project will follow Docker and Linux philosophy of delivering small, highly modular and composable tools that works well independently. 
+++libnetwork aims to satisfy that composable need for Networking in Containers.
+++
+++## Short-term Goals
+++
+++- Modularize the networking logic in Docker Engine and libcontainer in to a single, reusable library
+++- Replace the networking subsystem of Docker Engine, with libnetwork
+++- Define a flexible model that allows local and remote drivers to provide networking to containers
+++- Provide a stand-alone tool "dnet" for managing and testing libnetwork
+++
+++Project Planning
+++================
+++
+++[Project Pages](https://github.com/docker/libnetwork/wiki) define the goals for each Milestone and identify the release-relationship to the Docker Platform.
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..2b5b5773542ef7ea60b13d8f1b7c647fe93cdba9
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,807 @@@@
+++package api
+++
+++import (
+++     "encoding/json"
+++     "fmt"
+++     "io/ioutil"
+++     "net/http"
+++     "strings"
+++
+++     "github.com/docker/libnetwork"
+++     "github.com/docker/libnetwork/netlabel"
+++     "github.com/docker/libnetwork/types"
+++     "github.com/gorilla/mux"
+++)
+++
+++var (
+++     successResponse  = responseStatus{Status: "Success", StatusCode: http.StatusOK}
+++     createdResponse  = responseStatus{Status: "Created", StatusCode: http.StatusCreated}
+++     mismatchResponse = responseStatus{Status: "Body/URI parameter mismatch", StatusCode: http.StatusBadRequest}
+++     badQueryResponse = responseStatus{Status: "Unsupported query", StatusCode: http.StatusBadRequest}
+++)
+++
+++const (
+++     // Resource name regex
+++     regex = "[a-zA-Z_0-9-]+"
+++     // Router URL variable definition
+++     nwName = "{" + urlNwName + ":" + regex + "}"
+++     nwID   = "{" + urlNwID + ":" + regex + "}"
+++     nwPID  = "{" + urlNwPID + ":" + regex + "}"
+++     epName = "{" + urlEpName + ":" + regex + "}"
+++     epID   = "{" + urlEpID + ":" + regex + "}"
+++     epPID  = "{" + urlEpPID + ":" + regex + "}"
+++     cnID   = "{" + urlCnID + ":" + regex + "}"
+++
+++     // Though this name can be anything, in order to support default network,
+++     // we will keep it as name
+++     urlNwName = "name"
+++     // Internal URL variable name, they can be anything
+++     urlNwID   = "network-id"
+++     urlNwPID  = "network-partial-id"
+++     urlEpName = "endpoint-name"
+++     urlEpID   = "endpoint-id"
+++     urlEpPID  = "endpoint-partial-id"
+++     urlCnID   = "container-id"
+++
+++     // BridgeNetworkDriver is the built-in default for Network Driver
+++     BridgeNetworkDriver = "bridge"
+++)
+++
+++// 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", nwName}, procGetNetworks},
+++                     {"/networks", []string{"partial-id", nwPID}, procGetNetworks},
+++                     {"/networks", nil, procGetNetworks},
+++                     {"/networks/" + nwID, nil, procGetNetwork},
+++                     {"/networks/" + nwID + "/endpoints", []string{"name", epName}, procGetEndpoints},
+++                     {"/networks/" + nwID + "/endpoints", []string{"partial-id", epPID}, procGetEndpoints},
+++                     {"/networks/" + nwID + "/endpoints", nil, procGetEndpoints},
+++                     {"/networks/" + nwID + "/endpoints/" + epID, nil, procGetEndpoint},
+++                     {"/services", []string{"network", nwName}, procGetServices},
+++                     {"/services", []string{"name", epName}, procGetServices},
+++                     {"/services", []string{"partial-id", epPID}, procGetServices},
+++                     {"/services", nil, procGetServices},
+++                     {"/services/" + epID, nil, procGetService},
+++                     {"/services/" + epID + "/backend", nil, procGetContainers},
+++             },
+++             "POST": {
+++                     {"/networks", nil, procCreateNetwork},
+++                     {"/networks/" + nwID + "/endpoints", nil, procCreateEndpoint},
+++                     {"/networks/" + nwID + "/endpoints/" + epID + "/containers", nil, procJoinEndpoint},
+++                     {"/services", nil, procPublishService},
+++                     {"/services/" + epID + "/backend", nil, procAttachBackend},
+++             },
+++             "DELETE": {
+++                     {"/networks/" + nwID, nil, procDeleteNetwork},
+++                     {"/networks/" + nwID + "/endpoints/" + epID, nil, procDeleteEndpoint},
+++                     {"/networks/" + nwID + "/endpoints/" + epID + "/containers/" + cnID, nil, procLeaveEndpoint},
+++                     {"/services/" + epID, nil, procUnpublishService},
+++                     {"/services/" + epID + "/backend/" + cnID, nil, procDetachBackend},
+++             },
+++     }
+++
+++     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
+++                     }
+++             }
+++
+++             mvars := mux.Vars(req)
+++             rvars := req.URL.Query()
+++             // workaround a mux issue which filters out valid queries with empty value
+++             for k := range rvars {
+++                     if _, ok := mvars[k]; !ok {
+++                             if rvars.Get(k) == "" {
+++                                     mvars[k] = ""
+++                             }
+++                     }
+++             }
+++
+++             res, rsp := fct(ctrl, mvars, 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 buildContainerResource(ci libnetwork.ContainerInfo) *containerResource {
+++     r := &containerResource{}
+++     if ci != nil {
+++             r.ID = ci.ID()
+++     }
+++     return r
+++}
+++
+++/****************
+++ Options Parsers
+++*****************/
+++
+++func (nc *networkCreate) parseOptions() []libnetwork.NetworkOption {
+++     var setFctList []libnetwork.NetworkOption
+++
+++     if nc.Options != nil {
+++             setFctList = append(setFctList, libnetwork.NetworkOptionGeneric(nc.Options))
+++     }
+++
+++     return setFctList
+++}
+++
+++func (ej *endpointJoin) parseOptions() []libnetwork.EndpointOption {
+++     var setFctList []libnetwork.EndpointOption
+++     if ej.HostName != "" {
+++             setFctList = append(setFctList, libnetwork.JoinOptionHostname(ej.HostName))
+++     }
+++     if ej.DomainName != "" {
+++             setFctList = append(setFctList, libnetwork.JoinOptionDomainname(ej.DomainName))
+++     }
+++     if ej.HostsPath != "" {
+++             setFctList = append(setFctList, libnetwork.JoinOptionHostsPath(ej.HostsPath))
+++     }
+++     if ej.ResolvConfPath != "" {
+++             setFctList = append(setFctList, libnetwork.JoinOptionResolvConfPath(ej.ResolvConfPath))
+++     }
+++     if ej.UseDefaultSandbox {
+++             setFctList = append(setFctList, libnetwork.JoinOptionUseDefaultSandbox())
+++     }
+++     if ej.DNS != nil {
+++             for _, d := range ej.DNS {
+++                     setFctList = append(setFctList, libnetwork.JoinOptionDNS(d))
+++             }
+++     }
+++     if ej.ExtraHosts != nil {
+++             for _, e := range ej.ExtraHosts {
+++                     setFctList = append(setFctList, libnetwork.JoinOptionExtraHost(e.Name, e.Address))
+++             }
+++     }
+++     if ej.ParentUpdates != nil {
+++             for _, p := range ej.ParentUpdates {
+++                     setFctList = append(setFctList, libnetwork.JoinOptionParentUpdate(p.EndpointID, p.Name, p.Address))
+++             }
+++     }
+++     return setFctList
+++}
+++
+++/******************
+++ Process functions
+++*******************/
+++
+++func processCreateDefaults(c libnetwork.NetworkController, nc *networkCreate) {
+++     if nc.NetworkType == "" {
+++             nc.NetworkType = c.Config().Daemon.DefaultDriver
+++     }
+++     if nc.NetworkType == BridgeNetworkDriver {
+++             if nc.Options == nil {
+++                     nc.Options = make(map[string]interface{})
+++             }
+++             genericData, ok := nc.Options[netlabel.GenericData]
+++             if !ok {
+++                     genericData = make(map[string]interface{})
+++             }
+++             gData := genericData.(map[string]interface{})
+++
+++             if _, ok := gData["BridgeName"]; !ok {
+++                     gData["BridgeName"] = nc.Name
+++             }
+++             if _, ok := gData["AllowNonDefaultBridge"]; !ok {
+++                     gData["AllowNonDefaultBridge"] = "true"
+++             }
+++             nc.Options[netlabel.GenericData] = genericData
+++     }
+++}
+++
+++/***************************
+++ 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 "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
+++     }
+++     processCreateDefaults(c, &create)
+++
+++     nw, err := c.NewNetwork(create.NetworkType, create.Name, create.parseOptions()...)
+++     if err != nil {
+++             return "", 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
+++}
+++
+++/******************
+++ 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
+++     if ec.ExposedPorts != nil {
+++             setFctList = append(setFctList, libnetwork.CreateOptionExposedPorts(ec.ExposedPorts))
+++     }
+++     if ec.PortMapping != nil {
+++             setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(ec.PortMapping))
+++     }
+++
+++     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
+++     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
+++     }
+++
+++     err = ep.Join(ej.ContainerID, ej.parseOptions()...)
+++     if err != nil {
+++             return nil, convertNetworkError(err)
+++     }
+++     return ep.Info().SandboxKey(), &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
+++     }
+++
+++     err := ep.Leave(vars[urlCnID])
+++     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()
+++     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 procGetContainers(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)
+++     }
+++     var list []*containerResource
+++     if sv.ContainerInfo() != nil {
+++             list = append(list, buildContainerResource(sv.ContainerInfo()))
+++     }
+++     return list, &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
+++     if sp.ExposedPorts != nil {
+++             setFctList = append(setFctList, libnetwork.CreateOptionExposedPorts(sp.ExposedPorts))
+++     }
+++     if sp.PortMapping != nil {
+++             setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(sp.PortMapping))
+++     }
+++
+++     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) {
+++     epT, epBy := detectEndpointTarget(vars)
+++     sv, errRsp := findService(c, epT, epBy)
+++     if !errRsp.isOK() {
+++             return nil, errRsp
+++     }
+++     err := sv.Delete()
+++     if 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
+++     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
+++     }
+++
+++     err = sv.Join(bk.ContainerID, bk.parseOptions()...)
+++     if err != nil {
+++             return nil, convertNetworkError(err)
+++     }
+++     return sv.Info().SandboxKey(), &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
+++     }
+++
+++     err := sv.Leave(vars[urlCnID])
+++     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 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 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)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9916747cc72c99137ea98655d54d862f6a0fe96b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,2130 @@@@
+++package api
+++
+++import (
+++     "bytes"
+++     "encoding/json"
+++     "errors"
+++     "fmt"
+++     "io"
+++     "net/http"
+++     "os"
+++     "runtime"
+++     "testing"
+++
+++     "github.com/docker/docker/pkg/reexec"
+++     "github.com/docker/libnetwork"
+++     "github.com/docker/libnetwork/netlabel"
+++     "github.com/docker/libnetwork/options"
+++     "github.com/docker/libnetwork/sandbox"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++const (
+++     bridgeNetType = "bridge"
+++     bridgeName    = "docker0"
+++)
+++
+++func getEmptyGenericOption() map[string]interface{} {
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = options.Generic{}
+++     return genericOption
+++}
+++
+++func i2s(i interface{}) string {
+++     s, ok := i.(string)
+++     if !ok {
+++             panic(fmt.Sprintf("Failed i2s for %v", i))
+++     }
+++     return s
+++}
+++
+++func i2e(i interface{}) *endpointResource {
+++     s, ok := i.(*endpointResource)
+++     if !ok {
+++             panic(fmt.Sprintf("Failed i2e for %v", i))
+++     }
+++     return s
+++}
+++
+++func i2eL(i interface{}) []*endpointResource {
+++     s, ok := i.([]*endpointResource)
+++     if !ok {
+++             panic(fmt.Sprintf("Failed i2eL for %v", i))
+++     }
+++     return s
+++}
+++
+++func i2n(i interface{}) *networkResource {
+++     s, ok := i.(*networkResource)
+++     if !ok {
+++             panic(fmt.Sprintf("Failed i2n for %v", i))
+++     }
+++     return s
+++}
+++
+++func i2nL(i interface{}) []*networkResource {
+++     s, ok := i.([]*networkResource)
+++     if !ok {
+++             panic(fmt.Sprintf("Failed i2nL for %v", i))
+++     }
+++     return s
+++}
+++
+++func i2cL(i interface{}) []*containerResource {
+++     s, ok := i.([]*containerResource)
+++     if !ok {
+++             panic(fmt.Sprintf("Failed i2cL for %v", i))
+++     }
+++     return s
+++}
+++
+++func createTestNetwork(t *testing.T, network string) (libnetwork.NetworkController, libnetwork.Network) {
+++     c, err := libnetwork.New()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = c.ConfigureNetworkDriver(bridgeNetType, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     netOption := options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            network,
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++     netGeneric := libnetwork.NetworkOptionGeneric(netOption)
+++     nw, err := c.NewNetwork(bridgeNetType, network, netGeneric)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     return c, nw
+++}
+++
+++func TestMain(m *testing.M) {
+++     if reexec.Init() {
+++             return
+++     }
+++     os.Exit(m.Run())
+++}
+++
+++func TestJoinOptionParser(t *testing.T) {
+++     hn := "host1"
+++     dn := "docker.com"
+++     hp := "/etc/hosts"
+++     rc := "/etc/resolv.conf"
+++     dnss := []string{"8.8.8.8", "172.28.34.5"}
+++     ehs := []endpointExtraHost{endpointExtraHost{Name: "extra1", Address: "172.28.9.1"}, endpointExtraHost{Name: "extra2", Address: "172.28.9.2"}}
+++     pus := []endpointParentUpdate{endpointParentUpdate{EndpointID: "abc123def456", Name: "serv1", Address: "172.28.30.123"}}
+++
+++     ej := endpointJoin{
+++             HostName:          hn,
+++             DomainName:        dn,
+++             HostsPath:         hp,
+++             ResolvConfPath:    rc,
+++             DNS:               dnss,
+++             ExtraHosts:        ehs,
+++             ParentUpdates:     pus,
+++             UseDefaultSandbox: true,
+++     }
+++
+++     if len(ej.parseOptions()) != 10 {
+++             t.Fatalf("Failed to generate all libnetwork.EndpointJoinOption methods libnetwork.EndpointJoinOption method")
+++     }
+++
+++}
+++
+++func TestJson(t *testing.T) {
+++     nc := networkCreate{NetworkType: bridgeNetType}
+++     b, err := json.Marshal(nc)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     var ncp networkCreate
+++     err = json.Unmarshal(b, &ncp)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if nc.NetworkType != ncp.NetworkType {
+++             t.Fatalf("Incorrect networkCreate after json encoding/deconding: %v", ncp)
+++     }
+++
+++     jl := endpointJoin{ContainerID: "abcdef456789"}
+++     b, err = json.Marshal(jl)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     var jld endpointJoin
+++     err = json.Unmarshal(b, &jld)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if jl.ContainerID != jld.ContainerID {
+++             t.Fatalf("Incorrect endpointJoin after json encoding/deconding: %v", jld)
+++     }
+++}
+++
+++func TestCreateDeleteNetwork(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     c, err := libnetwork.New()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     err = c.ConfigureNetworkDriver(bridgeNetType, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     badBody, err := json.Marshal("bad body")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     vars := make(map[string]string)
+++     _, errRsp := procCreateNetwork(c, nil, badBody)
+++     if errRsp == &createdResponse {
+++             t.Fatalf("Expected to fail but succeeded")
+++     }
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected StatusBadRequest status code, got: %v", errRsp)
+++     }
+++
+++     incompleteBody, err := json.Marshal(networkCreate{})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     _, errRsp = procCreateNetwork(c, vars, incompleteBody)
+++     if errRsp == &createdResponse {
+++             t.Fatalf("Expected to fail but succeeded")
+++     }
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected StatusBadRequest status code, got: %v", errRsp)
+++     }
+++
+++     ops := options.Generic{
+++             netlabel.EnableIPv6: true,
+++             netlabel.GenericData: map[string]string{
+++                     "BridgeName":            "abc",
+++                     "AllowNonDefaultBridge": "true",
+++                     "FixedCIDRv6":           "fe80::1/64",
+++                     "AddressIP":             "172.28.30.254/24",
+++             },
+++     }
+++     nc := networkCreate{Name: "network_1", NetworkType: bridgeNetType, Options: ops}
+++     goodBody, err := json.Marshal(nc)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     _, errRsp = procCreateNetwork(c, vars, goodBody)
+++     if errRsp != &createdResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     vars[urlNwName] = ""
+++     _, errRsp = procDeleteNetwork(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected to fail but succeeded")
+++     }
+++
+++     vars[urlNwName] = "abc"
+++     _, errRsp = procDeleteNetwork(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected to fail but succeeded")
+++     }
+++
+++     vars[urlNwName] = "network_1"
+++     _, errRsp = procDeleteNetwork(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++}
+++
+++func TestGetNetworksAndEndpoints(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     c, err := libnetwork.New()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     err = c.ConfigureNetworkDriver(bridgeNetType, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     ops := options.Generic{
+++             netlabel.GenericData: map[string]string{
+++                     "BridgeName":            "api_test_nw",
+++                     "AllowNonDefaultBridge": "true",
+++             },
+++     }
+++
+++     nc := networkCreate{Name: "sh", NetworkType: bridgeNetType, Options: ops}
+++     body, err := json.Marshal(nc)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     vars := make(map[string]string)
+++     inid, errRsp := procCreateNetwork(c, vars, body)
+++     if errRsp != &createdResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     nid, ok := inid.(string)
+++     if !ok {
+++             t.FailNow()
+++     }
+++
+++     ec1 := endpointCreate{
+++             Name: "ep1",
+++             ExposedPorts: []types.TransportPort{
+++                     types.TransportPort{Proto: types.TCP, Port: uint16(5000)},
+++                     types.TransportPort{Proto: types.UDP, Port: uint16(400)},
+++                     types.TransportPort{Proto: types.TCP, Port: uint16(600)},
+++             },
+++             PortMapping: []types.PortBinding{
+++                     types.PortBinding{Proto: types.TCP, Port: uint16(230), HostPort: uint16(23000)},
+++                     types.PortBinding{Proto: types.UDP, Port: uint16(200), HostPort: uint16(22000)},
+++                     types.PortBinding{Proto: types.TCP, Port: uint16(120), HostPort: uint16(12000)},
+++             },
+++     }
+++     b1, err := json.Marshal(ec1)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     ec2 := endpointCreate{Name: "ep2"}
+++     b2, err := json.Marshal(ec2)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     vars[urlNwName] = "sh"
+++     vars[urlEpName] = "ep1"
+++     ieid1, errRsp := procCreateEndpoint(c, vars, b1)
+++     if errRsp != &createdResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     eid1 := i2s(ieid1)
+++     vars[urlEpName] = "ep2"
+++     ieid2, errRsp := procCreateEndpoint(c, vars, b2)
+++     if errRsp != &createdResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     eid2 := i2s(ieid2)
+++
+++     vars[urlNwName] = ""
+++     vars[urlEpName] = "ep1"
+++     _, errRsp = procGetEndpoint(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure but succeeded: %v", errRsp)
+++     }
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected to fail with http.StatusBadRequest, but got: %d", errRsp.StatusCode)
+++     }
+++
+++     vars = make(map[string]string)
+++     vars[urlNwName] = "sh"
+++     vars[urlEpID] = ""
+++     _, errRsp = procGetEndpoint(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure but succeeded: %v", errRsp)
+++     }
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected to fail with http.StatusBadRequest, but got: %d", errRsp.StatusCode)
+++     }
+++
+++     vars = make(map[string]string)
+++     vars[urlNwID] = ""
+++     vars[urlEpID] = eid1
+++     _, errRsp = procGetEndpoint(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure but succeeded: %v", errRsp)
+++     }
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected to fail with http.StatusBadRequest, but got: %d", errRsp.StatusCode)
+++     }
+++
+++     // nw by name and ep by id
+++     vars[urlNwName] = "sh"
+++     i1, errRsp := procGetEndpoint(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     // nw by name and ep by name
+++     delete(vars, urlEpID)
+++     vars[urlEpName] = "ep1"
+++     i2, errRsp := procGetEndpoint(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     // nw by id and ep by name
+++     delete(vars, urlNwName)
+++     vars[urlNwID] = nid
+++     i3, errRsp := procGetEndpoint(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     // nw by id and ep by id
+++     delete(vars, urlEpName)
+++     vars[urlEpID] = eid1
+++     i4, errRsp := procGetEndpoint(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     id1 := i2e(i1).ID
+++     if id1 != i2e(i2).ID || id1 != i2e(i3).ID || id1 != i2e(i4).ID {
+++             t.Fatalf("Endpoints retireved via different query parameters differ: %v, %v, %v, %v", i1, i2, i3, i4)
+++     }
+++
+++     vars[urlNwName] = ""
+++     _, errRsp = procGetEndpoints(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     delete(vars, urlNwName)
+++     vars[urlNwID] = "fakeID"
+++     _, errRsp = procGetEndpoints(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     vars[urlNwID] = nid
+++     _, errRsp = procGetEndpoints(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     vars[urlNwName] = "sh"
+++     iepList, errRsp := procGetEndpoints(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     epList := i2eL(iepList)
+++     if len(epList) != 2 {
+++             t.Fatalf("Did not return the expected number (2) of endpoint resources: %d", len(epList))
+++     }
+++     if "sh" != epList[0].Network || "sh" != epList[1].Network {
+++             t.Fatalf("Did not find expected network name in endpoint resources")
+++     }
+++
+++     vars = make(map[string]string)
+++     vars[urlNwName] = ""
+++     _, errRsp = procGetNetwork(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Exepected failure, got: %v", errRsp)
+++     }
+++     vars[urlNwName] = "shhhhh"
+++     _, errRsp = procGetNetwork(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Exepected failure, got: %v", errRsp)
+++     }
+++     vars[urlNwName] = "sh"
+++     inr1, errRsp := procGetNetwork(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     nr1 := i2n(inr1)
+++
+++     delete(vars, urlNwName)
+++     vars[urlNwID] = "cacca"
+++     _, errRsp = procGetNetwork(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     vars[urlNwID] = nid
+++     inr2, errRsp := procGetNetwork(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("procgetNetworkByName() != procgetNetworkById(), %v vs %v", inr1, inr2)
+++     }
+++     nr2 := i2n(inr2)
+++     if nr1.Name != nr2.Name || nr1.Type != nr2.Type || nr1.ID != nr2.ID || len(nr1.Endpoints) != len(nr2.Endpoints) {
+++             t.Fatalf("Get by name and Get failure: %v", errRsp)
+++     }
+++
+++     if len(nr1.Endpoints) != 2 {
+++             t.Fatalf("Did not find the expected number (2) of endpoint resources in the network resource: %d", len(nr1.Endpoints))
+++     }
+++     for _, er := range nr1.Endpoints {
+++             if er.ID != eid1 && er.ID != eid2 {
+++                     t.Fatalf("Did not find the expected endpoint resources in the network resource: %v", nr1.Endpoints)
+++             }
+++     }
+++
+++     iList, errRsp := procGetNetworks(c, nil, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     netList := i2nL(iList)
+++     if len(netList) != 1 {
+++             t.Fatalf("Did not return the expected number of network resources")
+++     }
+++     if nid != netList[0].ID {
+++             t.Fatalf("Did not find expected network %s: %v", nid, netList)
+++     }
+++
+++     _, errRsp = procDeleteNetwork(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Exepected failure, got: %v", errRsp)
+++     }
+++
+++     vars[urlEpName] = "ep1"
+++     _, errRsp = procDeleteEndpoint(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     delete(vars, urlEpName)
+++     iepList, errRsp = procGetEndpoints(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     epList = i2eL(iepList)
+++     if len(epList) != 1 {
+++             t.Fatalf("Did not return the expected number (1) of endpoint resources: %d", len(epList))
+++     }
+++
+++     vars[urlEpName] = "ep2"
+++     _, errRsp = procDeleteEndpoint(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     iepList, errRsp = procGetEndpoints(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     epList = i2eL(iepList)
+++     if len(epList) != 0 {
+++             t.Fatalf("Did not return the expected number (0) of endpoint resources: %d", len(epList))
+++     }
+++
+++     _, errRsp = procDeleteNetwork(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     iList, errRsp = procGetNetworks(c, nil, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     netList = i2nL(iList)
+++     if len(netList) != 0 {
+++             t.Fatalf("Did not return the expected number of network resources")
+++     }
+++}
+++
+++func TestProcGetServices(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     c, err := libnetwork.New()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = c.ConfigureNetworkDriver(bridgeNetType, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     // Create 2 networks
+++     netName1 := "production"
+++     netOption := options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            netName1,
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++     nw1, err := c.NewNetwork(bridgeNetType, netName1, libnetwork.NetworkOptionGeneric(netOption))
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     netName2 := "work-dev"
+++     netOption = options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            netName2,
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++     nw2, err := c.NewNetwork(bridgeNetType, netName2, libnetwork.NetworkOptionGeneric(netOption))
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     vars := make(map[string]string)
+++     li, errRsp := procGetServices(c, vars, nil)
+++     if !errRsp.isOK() {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++     list := i2eL(li)
+++     if len(list) != 0 {
+++             t.Fatalf("Unexpected services in response: %v", list)
+++     }
+++
+++     // Add a couple of services on one network and one on the other network
+++     ep11, err := nw1.CreateEndpoint("db-prod")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     ep12, err := nw1.CreateEndpoint("web-prod")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     ep21, err := nw2.CreateEndpoint("db-dev")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     li, errRsp = procGetServices(c, vars, nil)
+++     if !errRsp.isOK() {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++     list = i2eL(li)
+++     if len(list) != 3 {
+++             t.Fatalf("Unexpected services in response: %v", list)
+++     }
+++
+++     // Filter by network
+++     vars[urlNwName] = netName1
+++     li, errRsp = procGetServices(c, vars, nil)
+++     if !errRsp.isOK() {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++     list = i2eL(li)
+++     if len(list) != 2 {
+++             t.Fatalf("Unexpected services in response: %v", list)
+++     }
+++
+++     vars[urlNwName] = netName2
+++     li, errRsp = procGetServices(c, vars, nil)
+++     if !errRsp.isOK() {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++     list = i2eL(li)
+++     if len(list) != 1 {
+++             t.Fatalf("Unexpected services in response: %v", list)
+++     }
+++
+++     vars[urlNwName] = "unknown-network"
+++     li, errRsp = procGetServices(c, vars, nil)
+++     if !errRsp.isOK() {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++     list = i2eL(li)
+++     if len(list) != 0 {
+++             t.Fatalf("Unexpected services in response: %v", list)
+++     }
+++
+++     // Query by name
+++     delete(vars, urlNwName)
+++     vars[urlEpName] = "db-prod"
+++     li, errRsp = procGetServices(c, vars, nil)
+++     if !errRsp.isOK() {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++     list = i2eL(li)
+++     if len(list) != 1 {
+++             t.Fatalf("Unexpected services in response: %v", list)
+++     }
+++
+++     vars[urlEpName] = "no-service"
+++     li, errRsp = procGetServices(c, vars, nil)
+++     if !errRsp.isOK() {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++     list = i2eL(li)
+++     if len(list) != 0 {
+++             t.Fatalf("Unexpected services in response: %v", list)
+++     }
+++
+++     // Query by id or partial id
+++     delete(vars, urlEpName)
+++     vars[urlEpPID] = ep12.ID()
+++     li, errRsp = procGetServices(c, vars, nil)
+++     if !errRsp.isOK() {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++     list = i2eL(li)
+++     if len(list) != 1 {
+++             t.Fatalf("Unexpected services in response: %v", list)
+++     }
+++     if list[0].ID != ep12.ID() {
+++             t.Fatalf("Unexpected element in response: %v", list)
+++     }
+++
+++     vars[urlEpPID] = "non-id"
+++     li, errRsp = procGetServices(c, vars, nil)
+++     if !errRsp.isOK() {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++     list = i2eL(li)
+++     if len(list) != 0 {
+++             t.Fatalf("Unexpected services in response: %v", list)
+++     }
+++
+++     delete(vars, urlEpPID)
+++     err = ep11.Delete()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     err = ep12.Delete()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     err = ep21.Delete()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     li, errRsp = procGetServices(c, vars, nil)
+++     if !errRsp.isOK() {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++     list = i2eL(li)
+++     if len(list) != 0 {
+++             t.Fatalf("Unexpected services in response: %v", list)
+++     }
+++}
+++
+++func TestProcGetService(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     c, nw := createTestNetwork(t, "network")
+++     ep1, err := nw.CreateEndpoint("db")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     ep2, err := nw.CreateEndpoint("web")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     vars := map[string]string{urlEpID: ""}
+++     _, errRsp := procGetService(c, vars, nil)
+++     if errRsp.isOK() {
+++             t.Fatalf("Expected failure, but suceeded")
+++     }
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected %d, but got: %d", http.StatusBadRequest, errRsp.StatusCode)
+++     }
+++
+++     vars[urlEpID] = "unknown-service-id"
+++     _, errRsp = procGetService(c, vars, nil)
+++     if errRsp.isOK() {
+++             t.Fatalf("Expected failure, but suceeded")
+++     }
+++     if errRsp.StatusCode != http.StatusNotFound {
+++             t.Fatalf("Expected %d, but got: %d. (%v)", http.StatusNotFound, errRsp.StatusCode, errRsp)
+++     }
+++
+++     vars[urlEpID] = ep1.ID()
+++     si, errRsp := procGetService(c, vars, nil)
+++     if !errRsp.isOK() {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++     sv := i2e(si)
+++     if sv.ID != ep1.ID() {
+++             t.Fatalf("Unexpected service resource returned: %v", sv)
+++     }
+++
+++     vars[urlEpID] = ep2.ID()
+++     si, errRsp = procGetService(c, vars, nil)
+++     if !errRsp.isOK() {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++     sv = i2e(si)
+++     if sv.ID != ep2.ID() {
+++             t.Fatalf("Unexpected service resource returned: %v", sv)
+++     }
+++}
+++
+++func TestProcPublishUnpublishService(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     c, _ := createTestNetwork(t, "network")
+++     vars := make(map[string]string)
+++
+++     vbad, err := json.Marshal("bad service create data")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     _, errRsp := procPublishService(c, vars, vbad)
+++     if errRsp == &createdResponse {
+++             t.Fatalf("Expected to fail but succeeded")
+++     }
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+++     }
+++
+++     b, err := json.Marshal(servicePublish{Name: ""})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     _, errRsp = procPublishService(c, vars, b)
+++     if errRsp == &createdResponse {
+++             t.Fatalf("Expected to fail but succeeded")
+++     }
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+++     }
+++
+++     b, err = json.Marshal(servicePublish{Name: "db"})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     _, errRsp = procPublishService(c, vars, b)
+++     if errRsp == &createdResponse {
+++             t.Fatalf("Expected to fail but succeeded")
+++     }
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+++     }
+++
+++     b, err = json.Marshal(servicePublish{Name: "db", Network: "unknown-network"})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     _, errRsp = procPublishService(c, vars, b)
+++     if errRsp == &createdResponse {
+++             t.Fatalf("Expected to fail but succeeded")
+++     }
+++     if errRsp.StatusCode != http.StatusNotFound {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
+++     }
+++
+++     b, err = json.Marshal(servicePublish{Name: "", Network: "network"})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     _, errRsp = procPublishService(c, vars, b)
+++     if errRsp == &createdResponse {
+++             t.Fatalf("Expected to fail but succeeded")
+++     }
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+++     }
+++
+++     b, err = json.Marshal(servicePublish{Name: "db", Network: "network"})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     _, errRsp = procPublishService(c, vars, b)
+++     if errRsp != &createdResponse {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++
+++     sp := servicePublish{
+++             Name:    "web",
+++             Network: "network",
+++             ExposedPorts: []types.TransportPort{
+++                     types.TransportPort{Proto: types.TCP, Port: uint16(6000)},
+++                     types.TransportPort{Proto: types.UDP, Port: uint16(500)},
+++                     types.TransportPort{Proto: types.TCP, Port: uint16(700)},
+++             },
+++             PortMapping: []types.PortBinding{
+++                     types.PortBinding{Proto: types.TCP, Port: uint16(1230), HostPort: uint16(37000)},
+++                     types.PortBinding{Proto: types.UDP, Port: uint16(1200), HostPort: uint16(36000)},
+++                     types.PortBinding{Proto: types.TCP, Port: uint16(1120), HostPort: uint16(35000)},
+++             },
+++     }
+++     b, err = json.Marshal(sp)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     si, errRsp := procPublishService(c, vars, b)
+++     if errRsp != &createdResponse {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++     sid := i2s(si)
+++
+++     vars[urlEpID] = ""
+++     _, errRsp = procUnpublishService(c, vars, nil)
+++     if errRsp.isOK() {
+++             t.Fatalf("Expected failure but succeeded")
+++     }
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+++     }
+++
+++     vars[urlEpID] = "unknown-service-id"
+++     _, errRsp = procUnpublishService(c, vars, nil)
+++     if errRsp.isOK() {
+++             t.Fatalf("Expected failure but succeeded")
+++     }
+++     if errRsp.StatusCode != http.StatusNotFound {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
+++     }
+++
+++     vars[urlEpID] = sid
+++     _, errRsp = procUnpublishService(c, vars, nil)
+++     if !errRsp.isOK() {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++
+++     _, errRsp = procGetService(c, vars, nil)
+++     if errRsp.isOK() {
+++             t.Fatalf("Expected failure, but suceeded")
+++     }
+++     if errRsp.StatusCode != http.StatusNotFound {
+++             t.Fatalf("Expected %d, but got: %d. (%v)", http.StatusNotFound, errRsp.StatusCode, errRsp)
+++     }
+++}
+++
+++func TestAttachDetachBackend(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     c, nw := createTestNetwork(t, "network")
+++     ep1, err := nw.CreateEndpoint("db")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     vars := make(map[string]string)
+++
+++     vbad, err := json.Marshal("bad data")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     _, errRsp := procAttachBackend(c, vars, vbad)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     vars[urlEpName] = "endpoint"
+++     bad, err := json.Marshal(endpointJoin{})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     _, errRsp = procAttachBackend(c, vars, bad)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++     if errRsp.StatusCode != http.StatusNotFound {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
+++     }
+++
+++     _, errRsp = procGetContainers(c, vars, nil)
+++     if errRsp.isOK() {
+++             t.Fatalf("Expected failure. Got %v", errRsp)
+++     }
+++     if errRsp.StatusCode != http.StatusNotFound {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
+++     }
+++
+++     vars[urlEpName] = "db"
+++     _, errRsp = procAttachBackend(c, vars, bad)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+++     }
+++
+++     cid := "abcdefghi"
+++     jl := endpointJoin{ContainerID: cid}
+++     jlb, err := json.Marshal(jl)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     _, errRsp = procAttachBackend(c, vars, jlb)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexpected failure, got: %v", errRsp)
+++     }
+++
+++     cli, errRsp := procGetContainers(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexpected failure, got: %v", errRsp)
+++     }
+++     cl := i2cL(cli)
+++     if len(cl) != 1 {
+++             t.Fatalf("Did not find expected number of containers attached to the service: %d", len(cl))
+++     }
+++     if cl[0].ID != cid {
+++             t.Fatalf("Did not find expected container attached to the service: %v", cl[0])
+++     }
+++
+++     _, errRsp = procUnpublishService(c, vars, nil)
+++     if errRsp.isOK() {
+++             t.Fatalf("Expected failure but succeeded")
+++     }
+++     if errRsp.StatusCode != http.StatusForbidden {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusForbidden, errRsp)
+++     }
+++
+++     vars[urlEpName] = "endpoint"
+++     _, errRsp = procDetachBackend(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++     if errRsp.StatusCode != http.StatusNotFound {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
+++     }
+++
+++     vars[urlEpName] = "db"
+++     _, errRsp = procDetachBackend(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+++     }
+++
+++     vars[urlCnID] = cid
+++     _, errRsp = procDetachBackend(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexpected failure, got: %v", errRsp)
+++     }
+++
+++     cli, errRsp = procGetContainers(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexpected failure, got: %v", errRsp)
+++     }
+++     cl = i2cL(cli)
+++     if len(cl) != 0 {
+++             t.Fatalf("Did not find expected number of containers attached to the service: %d", len(cl))
+++     }
+++
+++     err = ep1.Delete()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestDetectGetNetworksInvalidQueryComposition(t *testing.T) {
+++     c, err := libnetwork.New()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     vars := map[string]string{urlNwName: "x", urlNwPID: "y"}
+++     _, errRsp := procGetNetworks(c, vars, nil)
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+++     }
+++}
+++
+++func TestDetectGetEndpointsInvalidQueryComposition(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     c, _ := createTestNetwork(t, "network")
+++
+++     vars := map[string]string{urlNwName: "network", urlEpName: "x", urlEpPID: "y"}
+++     _, errRsp := procGetEndpoints(c, vars, nil)
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+++     }
+++}
+++
+++func TestDetectGetServicesInvalidQueryComposition(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     c, _ := createTestNetwork(t, "network")
+++
+++     vars := map[string]string{urlNwName: "network", urlEpName: "x", urlEpPID: "y"}
+++     _, errRsp := procGetServices(c, vars, nil)
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
+++     }
+++}
+++
+++func TestFindNetworkUtilPanic(t *testing.T) {
+++     defer checkPanic(t)
+++     findNetwork(nil, "", -1)
+++}
+++
+++func TestFindNetworkUtil(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     c, nw := createTestNetwork(t, "network")
+++     nid := nw.ID()
+++
+++     _, errRsp := findNetwork(c, "", byName)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected to fail but succeeded")
+++     }
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected %d, but got: %d", http.StatusBadRequest, errRsp.StatusCode)
+++     }
+++
+++     n, errRsp := findNetwork(c, nid, byID)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++     if n == nil {
+++             t.Fatalf("Unexpected nil libnetwork.Network")
+++     }
+++     if nid != n.ID() {
+++             t.Fatalf("Incorrect libnetwork.Network resource. It has different id: %v", n)
+++     }
+++     if "network" != n.Name() {
+++             t.Fatalf("Incorrect libnetwork.Network resource. It has different name: %v", n)
+++     }
+++
+++     n, errRsp = findNetwork(c, "network", byName)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexpected failure: %v", errRsp)
+++     }
+++     if n == nil {
+++             t.Fatalf("Unexpected nil libnetwork.Network")
+++     }
+++     if nid != n.ID() {
+++             t.Fatalf("Incorrect libnetwork.Network resource. It has different id: %v", n)
+++     }
+++     if "network" != n.Name() {
+++             t.Fatalf("Incorrect libnetwork.Network resource. It has different name: %v", n)
+++     }
+++
+++     if err := n.Delete(); err != nil {
+++             t.Fatalf("Failed to delete the network: %s", err.Error())
+++     }
+++
+++     _, errRsp = findNetwork(c, nid, byID)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected to fail but succeeded")
+++     }
+++     if errRsp.StatusCode != http.StatusNotFound {
+++             t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+++     }
+++
+++     _, errRsp = findNetwork(c, "network", byName)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected to fail but succeeded")
+++     }
+++     if errRsp.StatusCode != http.StatusNotFound {
+++             t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+++     }
+++}
+++
+++func TestCreateDeleteEndpoints(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     c, err := libnetwork.New()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     err = c.ConfigureNetworkDriver(bridgeNetType, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     nc := networkCreate{Name: "firstNet", NetworkType: bridgeNetType}
+++     body, err := json.Marshal(nc)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     vars := make(map[string]string)
+++     i, errRsp := procCreateNetwork(c, vars, body)
+++     if errRsp != &createdResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     nid := i2s(i)
+++
+++     vbad, err := json.Marshal("bad endppoint create data")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     vars[urlNwName] = "firstNet"
+++     _, errRsp = procCreateEndpoint(c, vars, vbad)
+++     if errRsp == &createdResponse {
+++             t.Fatalf("Expected to fail but succeeded")
+++     }
+++
+++     b, err := json.Marshal(endpointCreate{Name: ""})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     vars[urlNwName] = "secondNet"
+++     _, errRsp = procCreateEndpoint(c, vars, b)
+++     if errRsp == &createdResponse {
+++             t.Fatalf("Expected to fail but succeeded")
+++     }
+++
+++     vars[urlNwName] = "firstNet"
+++     _, errRsp = procCreateEndpoint(c, vars, b)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure but succeeded: %v", errRsp)
+++     }
+++
+++     b, err = json.Marshal(endpointCreate{Name: "firstEp"})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     i, errRsp = procCreateEndpoint(c, vars, b)
+++     if errRsp != &createdResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++     eid := i2s(i)
+++
+++     _, errRsp = findEndpoint(c, "myNet", "firstEp", byName, byName)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure but succeeded: %v", errRsp)
+++     }
+++
+++     ep0, errRsp := findEndpoint(c, nid, "firstEp", byID, byName)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     ep1, errRsp := findEndpoint(c, "firstNet", "firstEp", byName, byName)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     ep2, errRsp := findEndpoint(c, nid, eid, byID, byID)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     ep3, errRsp := findEndpoint(c, "firstNet", eid, byName, byID)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     if ep0.ID() != ep1.ID() || ep0.ID() != ep2.ID() || ep0.ID() != ep3.ID() {
+++             t.Fatalf("Diffenrent queries returned different endpoints: \nep0: %v\nep1: %v\nep2: %v\nep3: %v", ep0, ep1, ep2, ep3)
+++     }
+++
+++     vars = make(map[string]string)
+++     vars[urlNwName] = ""
+++     vars[urlEpName] = "ep1"
+++     _, errRsp = procDeleteEndpoint(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     vars[urlNwName] = "firstNet"
+++     vars[urlEpName] = ""
+++     _, errRsp = procDeleteEndpoint(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     vars[urlEpName] = "ep2"
+++     _, errRsp = procDeleteEndpoint(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     vars[urlEpName] = "firstEp"
+++     _, errRsp = procDeleteEndpoint(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     _, errRsp = findEndpoint(c, "firstNet", "firstEp", byName, byName)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++}
+++
+++func TestJoinLeave(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     c, err := libnetwork.New()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     err = c.ConfigureNetworkDriver(bridgeNetType, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     nb, err := json.Marshal(networkCreate{Name: "network", NetworkType: bridgeNetType})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     vars := make(map[string]string)
+++     _, errRsp := procCreateNetwork(c, vars, nb)
+++     if errRsp != &createdResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     eb, err := json.Marshal(endpointCreate{Name: "endpoint"})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     vars[urlNwName] = "network"
+++     _, errRsp = procCreateEndpoint(c, vars, eb)
+++     if errRsp != &createdResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     vbad, err := json.Marshal("bad data")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     _, errRsp = procJoinEndpoint(c, vars, vbad)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     vars[urlEpName] = "endpoint"
+++     bad, err := json.Marshal(endpointJoin{})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     _, errRsp = procJoinEndpoint(c, vars, bad)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     cid := "abcdefghi"
+++     jl := endpointJoin{ContainerID: cid}
+++     jlb, err := json.Marshal(jl)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     vars = make(map[string]string)
+++     vars[urlNwName] = ""
+++     vars[urlEpName] = ""
+++     _, errRsp = procJoinEndpoint(c, vars, jlb)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     vars[urlNwName] = "network"
+++     vars[urlEpName] = ""
+++     _, errRsp = procJoinEndpoint(c, vars, jlb)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     vars[urlEpName] = "epoint"
+++     _, errRsp = procJoinEndpoint(c, vars, jlb)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     vars[urlEpName] = "endpoint"
+++     key, errRsp := procJoinEndpoint(c, vars, jlb)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     keyStr := i2s(key)
+++     if keyStr == "" {
+++             t.Fatalf("Empty sandbox key")
+++     }
+++     _, errRsp = procDeleteEndpoint(c, vars, nil)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     vars[urlNwName] = "network2"
+++     _, errRsp = procLeaveEndpoint(c, vars, vbad)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++     _, errRsp = procLeaveEndpoint(c, vars, bad)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++     _, errRsp = procLeaveEndpoint(c, vars, jlb)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++     vars = make(map[string]string)
+++     vars[urlNwName] = ""
+++     vars[urlEpName] = ""
+++     _, errRsp = procLeaveEndpoint(c, vars, jlb)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++     vars[urlNwName] = "network"
+++     vars[urlEpName] = ""
+++     _, errRsp = procLeaveEndpoint(c, vars, jlb)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++     vars[urlEpName] = "2epoint"
+++     _, errRsp = procLeaveEndpoint(c, vars, jlb)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++     vars[urlEpName] = "epoint"
+++     vars[urlCnID] = "who"
+++     _, errRsp = procLeaveEndpoint(c, vars, jlb)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     delete(vars, urlCnID)
+++     vars[urlEpName] = "endpoint"
+++     _, errRsp = procLeaveEndpoint(c, vars, jlb)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     vars[urlCnID] = cid
+++     _, errRsp = procLeaveEndpoint(c, vars, jlb)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     _, errRsp = procLeaveEndpoint(c, vars, jlb)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, got: %v", errRsp)
+++     }
+++
+++     _, errRsp = procDeleteEndpoint(c, vars, nil)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++}
+++
+++func TestFindEndpointUtilPanic(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++     defer checkPanic(t)
+++     c, nw := createTestNetwork(t, "network")
+++     nid := nw.ID()
+++     findEndpoint(c, nid, "", byID, -1)
+++}
+++
+++func TestFindServiceUtilPanic(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++     defer checkPanic(t)
+++     c, _ := createTestNetwork(t, "network")
+++     findService(c, "random_service", -1)
+++}
+++
+++func TestFindEndpointUtil(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     c, nw := createTestNetwork(t, "network")
+++     nid := nw.ID()
+++
+++     ep, err := nw.CreateEndpoint("secondEp", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     eid := ep.ID()
+++
+++     _, errRsp := findEndpoint(c, nid, "", byID, byName)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, but got: %v", errRsp)
+++     }
+++     if errRsp.StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Expected %d, but got: %d", http.StatusBadRequest, errRsp.StatusCode)
+++     }
+++
+++     ep0, errRsp := findEndpoint(c, nid, "secondEp", byID, byName)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     ep1, errRsp := findEndpoint(c, "network", "secondEp", byName, byName)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     ep2, errRsp := findEndpoint(c, nid, eid, byID, byID)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     ep3, errRsp := findEndpoint(c, "network", eid, byName, byID)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     ep4, errRsp := findService(c, "secondEp", byName)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     ep5, errRsp := findService(c, eid, byID)
+++     if errRsp != &successResponse {
+++             t.Fatalf("Unexepected failure: %v", errRsp)
+++     }
+++
+++     if ep0 != ep1 || ep0 != ep2 || ep0 != ep3 || ep0 != ep4 || ep0 != ep5 {
+++             t.Fatalf("Diffenrent queries returned different endpoints")
+++     }
+++
+++     ep.Delete()
+++
+++     _, errRsp = findEndpoint(c, nid, "secondEp", byID, byName)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, but got: %v", errRsp)
+++     }
+++     if errRsp.StatusCode != http.StatusNotFound {
+++             t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+++     }
+++
+++     _, errRsp = findEndpoint(c, "network", "secondEp", byName, byName)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, but got: %v", errRsp)
+++     }
+++     if errRsp.StatusCode != http.StatusNotFound {
+++             t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+++     }
+++
+++     _, errRsp = findEndpoint(c, nid, eid, byID, byID)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, but got: %v", errRsp)
+++     }
+++     if errRsp.StatusCode != http.StatusNotFound {
+++             t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+++     }
+++
+++     _, errRsp = findEndpoint(c, "network", eid, byName, byID)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, but got: %v", errRsp)
+++     }
+++     if errRsp.StatusCode != http.StatusNotFound {
+++             t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+++     }
+++
+++     _, errRsp = findService(c, "secondEp", byName)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, but got: %v", errRsp)
+++     }
+++     if errRsp.StatusCode != http.StatusNotFound {
+++             t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+++     }
+++
+++     _, errRsp = findService(c, eid, byID)
+++     if errRsp == &successResponse {
+++             t.Fatalf("Expected failure, but got: %v", errRsp)
+++     }
+++     if errRsp.StatusCode != http.StatusNotFound {
+++             t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
+++     }
+++}
+++
+++func TestEndpointToService(t *testing.T) {
+++     r := &responseStatus{Status: "this is one endpoint", StatusCode: http.StatusOK}
+++     r = endpointToService(r)
+++     if r.Status != "this is one service" {
+++             t.Fatalf("endpointToService returned unexpected status string: %s", r.Status)
+++     }
+++
+++     r = &responseStatus{Status: "this is one network", StatusCode: http.StatusOK}
+++     r = endpointToService(r)
+++     if r.Status != "this is one network" {
+++             t.Fatalf("endpointToService returned unexpected status string: %s", r.Status)
+++     }
+++}
+++
+++func checkPanic(t *testing.T) {
+++     if r := recover(); r != nil {
+++             if _, ok := r.(runtime.Error); ok {
+++                     panic(r)
+++             }
+++     } else {
+++             t.Fatalf("Expected to panic, but suceeded")
+++     }
+++}
+++
+++func TestDetectNetworkTargetPanic(t *testing.T) {
+++     defer checkPanic(t)
+++     vars := make(map[string]string)
+++     detectNetworkTarget(vars)
+++}
+++
+++func TestDetectEndpointTargetPanic(t *testing.T) {
+++     defer checkPanic(t)
+++     vars := make(map[string]string)
+++     detectEndpointTarget(vars)
+++}
+++
+++func TestResponseStatus(t *testing.T) {
+++     list := []int{
+++             http.StatusBadGateway,
+++             http.StatusBadRequest,
+++             http.StatusConflict,
+++             http.StatusContinue,
+++             http.StatusExpectationFailed,
+++             http.StatusForbidden,
+++             http.StatusFound,
+++             http.StatusGatewayTimeout,
+++             http.StatusGone,
+++             http.StatusHTTPVersionNotSupported,
+++             http.StatusInternalServerError,
+++             http.StatusLengthRequired,
+++             http.StatusMethodNotAllowed,
+++             http.StatusMovedPermanently,
+++             http.StatusMultipleChoices,
+++             http.StatusNoContent,
+++             http.StatusNonAuthoritativeInfo,
+++             http.StatusNotAcceptable,
+++             http.StatusNotFound,
+++             http.StatusNotModified,
+++             http.StatusPartialContent,
+++             http.StatusPaymentRequired,
+++             http.StatusPreconditionFailed,
+++             http.StatusProxyAuthRequired,
+++             http.StatusRequestEntityTooLarge,
+++             http.StatusRequestTimeout,
+++             http.StatusRequestURITooLong,
+++             http.StatusRequestedRangeNotSatisfiable,
+++             http.StatusResetContent,
+++             http.StatusServiceUnavailable,
+++             http.StatusSwitchingProtocols,
+++             http.StatusTemporaryRedirect,
+++             http.StatusUnauthorized,
+++             http.StatusUnsupportedMediaType,
+++             http.StatusUseProxy,
+++     }
+++     for _, c := range list {
+++             r := responseStatus{StatusCode: c}
+++             if r.isOK() {
+++                     t.Fatalf("isOK() returned true for code% d", c)
+++             }
+++     }
+++
+++     r := responseStatus{StatusCode: http.StatusOK}
+++     if !r.isOK() {
+++             t.Fatalf("isOK() failed")
+++     }
+++
+++     r = responseStatus{StatusCode: http.StatusCreated}
+++     if !r.isOK() {
+++             t.Fatalf("isOK() failed")
+++     }
+++}
+++
+++// Local structs for end to end testing of api.go
+++type localReader struct {
+++     data  []byte
+++     beBad bool
+++}
+++
+++func newLocalReader(data []byte) *localReader {
+++     lr := &localReader{data: make([]byte, len(data))}
+++     copy(lr.data, data)
+++     return lr
+++}
+++
+++func (l *localReader) Read(p []byte) (n int, err error) {
+++     if l.beBad {
+++             return 0, errors.New("I am a bad reader")
+++     }
+++     if p == nil {
+++             return -1, fmt.Errorf("nil buffer passed")
+++     }
+++     if l.data == nil || len(l.data) == 0 {
+++             return 0, io.EOF
+++     }
+++     copy(p[:], l.data[:])
+++     return len(l.data), io.EOF
+++}
+++
+++type localResponseWriter struct {
+++     body       []byte
+++     statusCode int
+++}
+++
+++func newWriter() *localResponseWriter {
+++     return &localResponseWriter{}
+++}
+++
+++func (f *localResponseWriter) Header() http.Header {
+++     return make(map[string][]string, 0)
+++}
+++
+++func (f *localResponseWriter) Write(data []byte) (int, error) {
+++     if data == nil {
+++             return -1, fmt.Errorf("nil data passed")
+++     }
+++
+++     f.body = make([]byte, len(data))
+++     copy(f.body, data)
+++
+++     return len(f.body), nil
+++}
+++
+++func (f *localResponseWriter) WriteHeader(c int) {
+++     f.statusCode = c
+++}
+++
+++func TestwriteJSON(t *testing.T) {
+++     testCode := 55
+++     testData, err := json.Marshal("test data")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     rsp := newWriter()
+++     writeJSON(rsp, testCode, testData)
+++     if rsp.statusCode != testCode {
+++             t.Fatalf("writeJSON() failed to set the status code. Expected %d. Got %d", testCode, rsp.statusCode)
+++     }
+++     if !bytes.Equal(testData, rsp.body) {
+++             t.Fatalf("writeJSON() failed to set the body. Expected %s. Got %s", testData, rsp.body)
+++     }
+++
+++}
+++
+++func TestHttpHandlerUninit(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     c, err := libnetwork.New()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     h := &httpHandler{c: c}
+++     h.initRouter()
+++     if h.r == nil {
+++             t.Fatalf("initRouter() did not initialize the router")
+++     }
+++
+++     rsp := newWriter()
+++     req, err := http.NewRequest("GET", "/v1.19/networks", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     handleRequest := NewHTTPHandler(nil)
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusServiceUnavailable {
+++             t.Fatalf("Expected (%d). Got (%d): %s", http.StatusServiceUnavailable, rsp.statusCode, rsp.body)
+++     }
+++
+++     handleRequest = NewHTTPHandler(c)
+++
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusOK {
+++             t.Fatalf("Expected (%d). Got: (%d): %s", http.StatusOK, rsp.statusCode, rsp.body)
+++     }
+++
+++     var list []*networkResource
+++     err = json.Unmarshal(rsp.body, &list)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if len(list) != 0 {
+++             t.Fatalf("Expected empty list. Got %v", list)
+++     }
+++
+++     n, err := c.NewNetwork(bridgeNetType, "didietro", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     nwr := buildNetworkResource(n)
+++     expected, err := json.Marshal([]*networkResource{nwr})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusOK {
+++             t.Fatalf("Unexpectded failure: (%d): %s", rsp.statusCode, rsp.body)
+++     }
+++     if len(rsp.body) == 0 {
+++             t.Fatalf("Empty list of networks")
+++     }
+++     if bytes.Equal(rsp.body, expected) {
+++             t.Fatalf("Incorrect list of networks in response's body")
+++     }
+++}
+++
+++func TestHttpHandlerBadBody(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     rsp := newWriter()
+++
+++     c, err := libnetwork.New()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest := NewHTTPHandler(c)
+++
+++     req, err := http.NewRequest("POST", "/v1.19/networks", &localReader{beBad: true})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusBadRequest {
+++             t.Fatalf("Unexpected status code. Expected (%d). Got (%d): %s.", http.StatusBadRequest, rsp.statusCode, string(rsp.body))
+++     }
+++
+++     body := []byte{}
+++     lr := newLocalReader(body)
+++     req, err = http.NewRequest("POST", "/v1.19/networks", lr)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusBadRequest {
+++             t.Fatalf("Unexpected status code. Expected (%d). Got (%d): %s.", http.StatusBadRequest, rsp.statusCode, string(rsp.body))
+++     }
+++}
+++
+++func TestEndToEnd(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     rsp := newWriter()
+++
+++     c, err := libnetwork.New()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest := NewHTTPHandler(c)
+++
+++     ops := options.Generic{
+++             netlabel.EnableIPv6: true,
+++             netlabel.GenericData: map[string]string{
+++                     "BridgeName":            "cdef",
+++                     "FixedCIDRv6":           "fe80:2000::1/64",
+++                     "EnableIPv6":            "true",
+++                     "Mtu":                   "1460",
+++                     "EnableIPTables":        "true",
+++                     "AddressIP":             "172.28.30.254/16",
+++                     "EnableUserlandProxy":   "true",
+++                     "AllowNonDefaultBridge": "true",
+++             },
+++     }
+++
+++     // Create network
+++     nc := networkCreate{Name: "network-fiftyfive", NetworkType: bridgeNetType, Options: ops}
+++     body, err := json.Marshal(nc)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     lr := newLocalReader(body)
+++     req, err := http.NewRequest("POST", "/v1.19/networks", lr)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusCreated {
+++             t.Fatalf("Unexpectded status code. Expected (%d). Got (%d): %s.", http.StatusCreated, rsp.statusCode, string(rsp.body))
+++     }
+++     if len(rsp.body) == 0 {
+++             t.Fatalf("Empty response body")
+++     }
+++
+++     var nid string
+++     err = json.Unmarshal(rsp.body, &nid)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     // Query networks collection
+++     req, err = http.NewRequest("GET", "/v1.19/networks", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusOK {
+++             t.Fatalf("Expected StatusOK. Got (%d): %s", rsp.statusCode, rsp.body)
+++     }
+++
+++     b0 := make([]byte, len(rsp.body))
+++     copy(b0, rsp.body)
+++
+++     req, err = http.NewRequest("GET", "/v1.19/networks?name=network-fiftyfive", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusOK {
+++             t.Fatalf("Expected StatusOK. Got (%d): %s", rsp.statusCode, rsp.body)
+++     }
+++
+++     if !bytes.Equal(b0, rsp.body) {
+++             t.Fatalf("Expected same body from GET /networks and GET /networks?name=<nw> when only network <nw> exist.")
+++     }
+++
+++     // Query network by name
+++     req, err = http.NewRequest("GET", "/v1.19/networks?name=culo", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusOK {
+++             t.Fatalf("Expected StatusOK. Got (%d): %s", rsp.statusCode, rsp.body)
+++     }
+++
+++     var list []*networkResource
+++     err = json.Unmarshal(rsp.body, &list)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if len(list) != 0 {
+++             t.Fatalf("Expected empty list. Got %v", list)
+++     }
+++
+++     req, err = http.NewRequest("GET", "/v1.19/networks?name=network-fiftyfive", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusOK {
+++             t.Fatalf("Unexpectded failure: (%d): %s", rsp.statusCode, rsp.body)
+++     }
+++
+++     err = json.Unmarshal(rsp.body, &list)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if len(list) == 0 {
+++             t.Fatalf("Expected non empty list")
+++     }
+++     if list[0].Name != "network-fiftyfive" || nid != list[0].ID {
+++             t.Fatalf("Incongruent resource found: %v", list[0])
+++     }
+++
+++     // Query network by partial id
+++     chars := []byte(nid)
+++     partial := string(chars[0 : len(chars)/2])
+++     req, err = http.NewRequest("GET", "/v1.19/networks?partial-id="+partial, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusOK {
+++             t.Fatalf("Unexpectded failure: (%d): %s", rsp.statusCode, rsp.body)
+++     }
+++
+++     err = json.Unmarshal(rsp.body, &list)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if len(list) == 0 {
+++             t.Fatalf("Expected non empty list")
+++     }
+++     if list[0].Name != "network-fiftyfive" || nid != list[0].ID {
+++             t.Fatalf("Incongruent resource found: %v", list[0])
+++     }
+++
+++     // Get network by id
+++     req, err = http.NewRequest("GET", "/v1.19/networks/"+nid, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusOK {
+++             t.Fatalf("Unexpectded failure: (%d): %s", rsp.statusCode, rsp.body)
+++     }
+++
+++     var nwr networkResource
+++     err = json.Unmarshal(rsp.body, &nwr)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if nwr.Name != "network-fiftyfive" || nid != nwr.ID {
+++             t.Fatalf("Incongruent resource found: %v", nwr)
+++     }
+++
+++     // Create endpoint
+++     eb, err := json.Marshal(endpointCreate{Name: "ep-TwentyTwo"})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     lr = newLocalReader(eb)
+++     req, err = http.NewRequest("POST", "/v1.19/networks/"+nid+"/endpoints", lr)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusCreated {
+++             t.Fatalf("Unexpectded status code. Expected (%d). Got (%d): %s.", http.StatusCreated, rsp.statusCode, string(rsp.body))
+++     }
+++     if len(rsp.body) == 0 {
+++             t.Fatalf("Empty response body")
+++     }
+++
+++     var eid string
+++     err = json.Unmarshal(rsp.body, &eid)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     // Query endpoint(s)
+++     req, err = http.NewRequest("GET", "/v1.19/networks/"+nid+"/endpoints", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusOK {
+++             t.Fatalf("Expected StatusOK. Got (%d): %s", rsp.statusCode, rsp.body)
+++     }
+++
+++     req, err = http.NewRequest("GET", "/v1.19/networks/"+nid+"/endpoints?name=bla", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusOK {
+++             t.Fatalf("Unexpectded failure: (%d): %s", rsp.statusCode, rsp.body)
+++     }
+++     var epList []*endpointResource
+++     err = json.Unmarshal(rsp.body, &epList)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if len(epList) != 0 {
+++             t.Fatalf("Expected empty list. Got %v", epList)
+++     }
+++
+++     // Query endpoint by name
+++     req, err = http.NewRequest("GET", "/v1.19/networks/"+nid+"/endpoints?name=ep-TwentyTwo", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusOK {
+++             t.Fatalf("Unexpectded failure: (%d): %s", rsp.statusCode, rsp.body)
+++     }
+++
+++     err = json.Unmarshal(rsp.body, &epList)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if len(epList) == 0 {
+++             t.Fatalf("Empty response body")
+++     }
+++     if epList[0].Name != "ep-TwentyTwo" || eid != epList[0].ID {
+++             t.Fatalf("Incongruent resource found: %v", epList[0])
+++     }
+++
+++     // Query endpoint by partial id
+++     chars = []byte(eid)
+++     partial = string(chars[0 : len(chars)/2])
+++     req, err = http.NewRequest("GET", "/v1.19/networks/"+nid+"/endpoints?partial-id="+partial, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusOK {
+++             t.Fatalf("Unexpectded failure: (%d): %s", rsp.statusCode, rsp.body)
+++     }
+++
+++     err = json.Unmarshal(rsp.body, &epList)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if len(epList) == 0 {
+++             t.Fatalf("Empty response body")
+++     }
+++     if epList[0].Name != "ep-TwentyTwo" || eid != epList[0].ID {
+++             t.Fatalf("Incongruent resource found: %v", epList[0])
+++     }
+++
+++     // Get endpoint by id
+++     req, err = http.NewRequest("GET", "/v1.19/networks/"+nid+"/endpoints/"+eid, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     handleRequest(rsp, req)
+++     if rsp.statusCode != http.StatusOK {
+++             t.Fatalf("Unexpectded failure: (%d): %s", rsp.statusCode, rsp.body)
+++     }
+++
+++     var epr endpointResource
+++     err = json.Unmarshal(rsp.body, &epr)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if epr.Name != "ep-TwentyTwo" || epr.ID != eid {
+++             t.Fatalf("Incongruent resource found: %v", epr)
+++     }
+++}
+++
+++type bre struct{}
+++
+++func (b *bre) Error() string {
+++     return "I am a bad request error"
+++}
+++func (b *bre) BadRequest() {}
+++
+++type nfe struct{}
+++
+++func (n *nfe) Error() string {
+++     return "I am a not found error"
+++}
+++func (n *nfe) NotFound() {}
+++
+++type forb struct{}
+++
+++func (f *forb) Error() string {
+++     return "I am a bad request error"
+++}
+++func (f *forb) Forbidden() {}
+++
+++type notimpl struct{}
+++
+++func (nip *notimpl) Error() string {
+++     return "I am a not implemented error"
+++}
+++func (nip *notimpl) NotImplemented() {}
+++
+++type inter struct{}
+++
+++func (it *inter) Error() string {
+++     return "I am a internal error"
+++}
+++func (it *inter) Internal() {}
+++
+++type tout struct{}
+++
+++func (to *tout) Error() string {
+++     return "I am a timeout error"
+++}
+++func (to *tout) Timeout() {}
+++
+++type noserv struct{}
+++
+++func (nos *noserv) Error() string {
+++     return "I am a no service error"
+++}
+++func (nos *noserv) NoService() {}
+++
+++type notclassified struct{}
+++
+++func (noc *notclassified) Error() string {
+++     return "I am a non classified error"
+++}
+++
+++func TestErrorConversion(t *testing.T) {
+++     if convertNetworkError(new(bre)).StatusCode != http.StatusBadRequest {
+++             t.Fatalf("Failed to recognize BadRequest error")
+++     }
+++
+++     if convertNetworkError(new(nfe)).StatusCode != http.StatusNotFound {
+++             t.Fatalf("Failed to recognize NotFound error")
+++     }
+++
+++     if convertNetworkError(new(forb)).StatusCode != http.StatusForbidden {
+++             t.Fatalf("Failed to recognize Forbidden error")
+++     }
+++
+++     if convertNetworkError(new(notimpl)).StatusCode != http.StatusNotImplemented {
+++             t.Fatalf("Failed to recognize NotImplemented error")
+++     }
+++
+++     if convertNetworkError(new(inter)).StatusCode != http.StatusInternalServerError {
+++             t.Fatalf("Failed to recognize Internal error")
+++     }
+++
+++     if convertNetworkError(new(tout)).StatusCode != http.StatusRequestTimeout {
+++             t.Fatalf("Failed to recognize Timeout error")
+++     }
+++
+++     if convertNetworkError(new(noserv)).StatusCode != http.StatusServiceUnavailable {
+++             t.Fatalf("Failed to recognize No Service error")
+++     }
+++
+++     if convertNetworkError(new(notclassified)).StatusCode != http.StatusInternalServerError {
+++             t.Fatalf("Failed to recognize not classified error as Internal error")
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..72f20db24f90264748679e0156f4eb616e10bcf0
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,81 @@@@
+++package api
+++
+++import "github.com/docker/libnetwork/types"
+++
+++/***********
+++ Resources
+++************/
+++
+++// networkResource is the body of the "get network" http response message
+++type networkResource struct {
+++     Name      string              `json:"name"`
+++     ID        string              `json:"id"`
+++     Type      string              `json:"type"`
+++     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"`
+++}
+++
+++// containerResource is the body of "get service backend" response message
+++type containerResource struct {
+++     ID string `json:"id"`
+++     // will add more fields once labels change is in
+++}
+++
+++/***********
+++  Body types
+++  ************/
+++
+++// networkCreate is the expected body of the "create network" http request message
+++type networkCreate struct {
+++     Name        string                 `json:"name"`
+++     NetworkType string                 `json:"network_type"`
+++     Options     map[string]interface{} `json:"options"`
+++}
+++
+++// endpointCreate represents the body of the "create endpoint" http request message
+++type endpointCreate struct {
+++     Name         string                `json:"name"`
+++     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 {
+++     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        []endpointExtraHost    `json:"extra_hosts"`
+++     ParentUpdates     []endpointParentUpdate `json:"parent_updates"`
+++     UseDefaultSandbox bool                   `json:"use_default_sandbox"`
+++}
+++
+++// servicePublish represents the body of the "publish service" http request message
+++type servicePublish struct {
+++     Name         string                `json:"name"`
+++     Network      string                `json:"network_name"`
+++     ExposedPorts []types.TransportPort `json:"exposed_ports"`
+++     PortMapping  []types.PortBinding   `json:"port_mapping"`
+++}
+++
+++// EndpointExtraHost represents the extra host object
+++type endpointExtraHost struct {
+++     Name    string `json:"name"`
+++     Address string `json:"address"`
+++}
+++
+++// EndpointParentUpdate is the object carrying the information about the
+++// endpoint parent that needs to be updated
+++type endpointParentUpdate struct {
+++     EndpointID string `json:"endpoint_id"`
+++     Name       string `json:"name"`
+++     Address    string `json:"address"`
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d9c9ad52726414e20760095ea4d89f12e26d5c37
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,454 @@@@
+++// Package bitseq provides a structure and utilities for representing long bitmask
+++// as sequence of run-lenght encoded blocks. It operates direclty on the encoded
+++// representation, it does not decode/encode.
+++package bitseq
+++
+++import (
+++     "fmt"
+++     "sync"
+++
+++     "github.com/docker/libnetwork/datastore"
+++     "github.com/docker/libnetwork/netutils"
+++)
+++
+++// Block Sequence constants
+++// If needed we can think of making these configurable
+++const (
+++     blockLen      = 32
+++     blockBytes    = blockLen / 8
+++     blockMAX      = 1<<blockLen - 1
+++     blockFirstBit = 1 << (blockLen - 1)
+++)
+++
+++// Handle contains the sequece representing the bitmask and its identifier
+++type Handle struct {
+++     bits       uint32
+++     unselected uint32
+++     head       *Sequence
+++     app        string
+++     id         string
+++     dbIndex    uint64
+++     dbExists   bool
+++     store      datastore.DataStore
+++     sync.Mutex
+++}
+++
+++// NewHandle returns a thread-safe instance of the bitmask handler
+++func NewHandle(app string, ds datastore.DataStore, id string, numElements uint32) (*Handle, error) {
+++     h := &Handle{
+++             app:        app,
+++             id:         id,
+++             store:      ds,
+++             bits:       numElements,
+++             unselected: numElements,
+++             head: &Sequence{
+++                     Block: 0x0,
+++                     Count: getNumBlocks(numElements),
+++             },
+++     }
+++
+++     if h.store == nil {
+++             return h, nil
+++     }
+++
+++     // Register for status changes
+++     h.watchForChanges()
+++
+++     // Get the initial status from the ds if present.
+++     if err := h.store.GetObject(datastore.Key(h.Key()...), h); err != nil && err != datastore.ErrKeyNotFound {
+++             return nil, err
+++     }
+++
+++     return h, nil
+++}
+++
+++// Sequence reresents a recurring sequence of 32 bits long bitmasks
+++type Sequence struct {
+++     Block uint32    // block representing 4 byte long allocation bitmask
+++     Count uint32    // number of consecutive blocks
+++     Next  *Sequence // next sequence
+++}
+++
+++// NewSequence returns a sequence initialized to represent a bitmaks of numElements bits
+++func NewSequence(numElements uint32) *Sequence {
+++     return &Sequence{Block: 0x0, Count: getNumBlocks(numElements), Next: nil}
+++}
+++
+++// String returns a string representation of the block sequence starting from this block
+++func (s *Sequence) String() string {
+++     var nextBlock string
+++     if s.Next == nil {
+++             nextBlock = "end"
+++     } else {
+++             nextBlock = s.Next.String()
+++     }
+++     return fmt.Sprintf("(0x%x, %d)->%s", s.Block, s.Count, nextBlock)
+++}
+++
+++// GetAvailableBit returns the position of the first unset bit in the bitmask represented by this sequence
+++func (s *Sequence) GetAvailableBit() (bytePos, bitPos int) {
+++     if s.Block == blockMAX || s.Count == 0 {
+++             return -1, -1
+++     }
+++     bits := 0
+++     bitSel := uint32(blockFirstBit)
+++     for bitSel > 0 && s.Block&bitSel != 0 {
+++             bitSel >>= 1
+++             bits++
+++     }
+++     return bits / 8, bits % 8
+++}
+++
+++// GetCopy returns a copy of the linked list rooted at this node
+++func (s *Sequence) GetCopy() *Sequence {
+++     n := &Sequence{Block: s.Block, Count: s.Count}
+++     pn := n
+++     ps := s.Next
+++     for ps != nil {
+++             pn.Next = &Sequence{Block: ps.Block, Count: ps.Count}
+++             pn = pn.Next
+++             ps = ps.Next
+++     }
+++     return n
+++}
+++
+++// Equal checks if this sequence is equal to the passed one
+++func (s *Sequence) Equal(o *Sequence) bool {
+++     this := s
+++     other := o
+++     for this != nil {
+++             if other == nil {
+++                     return false
+++             }
+++             if this.Block != other.Block || this.Count != other.Count {
+++                     return false
+++             }
+++             this = this.Next
+++             other = other.Next
+++     }
+++     // Check if other is longer than this
+++     if other != nil {
+++             return false
+++     }
+++     return true
+++}
+++
+++// ToByteArray converts the sequence into a byte array
+++// TODO (aboch): manage network/host order stuff
+++func (s *Sequence) ToByteArray() ([]byte, error) {
+++     var bb []byte
+++
+++     p := s
+++     for p != nil {
+++             bb = append(bb, netutils.U32ToA(p.Block)...)
+++             bb = append(bb, netutils.U32ToA(p.Count)...)
+++             p = p.Next
+++     }
+++
+++     return bb, nil
+++}
+++
+++// FromByteArray construct the sequence from the byte array
+++// TODO (aboch): manage network/host order stuff
+++func (s *Sequence) FromByteArray(data []byte) error {
+++     l := len(data)
+++     if l%8 != 0 {
+++             return fmt.Errorf("cannot deserialize byte sequence of lenght %d (%v)", l, data)
+++     }
+++
+++     p := s
+++     i := 0
+++     for {
+++             p.Block = netutils.ATo32(data[i : i+4])
+++             p.Count = netutils.ATo32(data[i+4 : i+8])
+++             i += 8
+++             if i == l {
+++                     break
+++             }
+++             p.Next = &Sequence{}
+++             p = p.Next
+++     }
+++
+++     return nil
+++}
+++
+++// GetFirstAvailable returns the byte and bit position of the first unset bit
+++func (h *Handle) GetFirstAvailable() (int, int, error) {
+++     h.Lock()
+++     defer h.Unlock()
+++     return GetFirstAvailable(h.head)
+++}
+++
+++// CheckIfAvailable checks if the bit correspondent to the specified ordinal is unset
+++// If the ordinal is beyond the Sequence limits, a negative response is returned
+++func (h *Handle) CheckIfAvailable(ordinal int) (int, int, error) {
+++     h.Lock()
+++     defer h.Unlock()
+++     return CheckIfAvailable(h.head, ordinal)
+++}
+++
+++// PushReservation pushes the bit reservation inside the bitmask.
+++func (h *Handle) PushReservation(bytePos, bitPos int, release bool) error {
+++     // Create a copy of the current handler
+++     h.Lock()
+++     nh := &Handle{
+++             app:      h.app,
+++             id:       h.id,
+++             store:    h.store,
+++             dbIndex:  h.dbIndex,
+++             head:     h.head.GetCopy(),
+++             dbExists: h.dbExists,
+++     }
+++     h.Unlock()
+++
+++     nh.head = PushReservation(bytePos, bitPos, nh.head, release)
+++
+++     err := nh.writeToStore()
+++     if err == nil {
+++             // Commit went through, save locally
+++             h.Lock()
+++             h.head = nh.head
+++             if release {
+++                     h.unselected++
+++             } else {
+++                     h.unselected--
+++             }
+++             // Can't use SetIndex() since we're locked.
+++             h.dbIndex = nh.Index()
+++             h.dbExists = true
+++             h.Unlock()
+++     }
+++
+++     return err
+++}
+++
+++// Destroy removes from the datastore the data belonging to this handle
+++func (h *Handle) Destroy() {
+++     h.deleteFromStore()
+++}
+++
+++// ToByteArray converts this handle's data into a byte array
+++func (h *Handle) ToByteArray() ([]byte, error) {
+++     ba := make([]byte, 8)
+++
+++     h.Lock()
+++     defer h.Unlock()
+++     copy(ba[0:4], netutils.U32ToA(h.bits))
+++     copy(ba[4:8], netutils.U32ToA(h.unselected))
+++     bm, err := h.head.ToByteArray()
+++     if err != nil {
+++             return nil, fmt.Errorf("failed to serialize head: %s", err.Error())
+++     }
+++     ba = append(ba, bm...)
+++
+++     return ba, nil
+++}
+++
+++// FromByteArray reads his handle's data from a byte array
+++func (h *Handle) FromByteArray(ba []byte) error {
+++     if ba == nil {
+++             return fmt.Errorf("nil byte array")
+++     }
+++
+++     nh := &Sequence{}
+++     err := nh.FromByteArray(ba[8:])
+++     if err != nil {
+++             return fmt.Errorf("failed to deserialize head: %s", err.Error())
+++     }
+++
+++     h.Lock()
+++     h.head = nh
+++     h.bits = netutils.ATo32(ba[0:4])
+++     h.unselected = netutils.ATo32(ba[4:8])
+++     h.Unlock()
+++
+++     return nil
+++}
+++
+++// Bits returns the length of the bit sequence
+++func (h *Handle) Bits() uint32 {
+++     return h.bits
+++}
+++
+++// Unselected returns the number of bits which are not selected
+++func (h *Handle) Unselected() uint32 {
+++     h.Lock()
+++     defer h.Unlock()
+++     return h.unselected
+++}
+++
+++// GetFirstAvailable looks for the first unset bit in passed mask
+++func GetFirstAvailable(head *Sequence) (int, int, error) {
+++     byteIndex := 0
+++     current := head
+++     for current != nil {
+++             if current.Block != blockMAX {
+++                     bytePos, bitPos := current.GetAvailableBit()
+++                     return byteIndex + bytePos, bitPos, nil
+++             }
+++             byteIndex += int(current.Count * blockBytes)
+++             current = current.Next
+++     }
+++     return -1, -1, fmt.Errorf("no bit available")
+++}
+++
+++// CheckIfAvailable checks if the bit correspondent to the specified ordinal is unset
+++// If the ordinal is beyond the Sequence limits, a negative response is returned
+++func CheckIfAvailable(head *Sequence, ordinal int) (int, int, error) {
+++     bytePos := ordinal / 8
+++     bitPos := ordinal % 8
+++
+++     // Find the Sequence containing this byte
+++     current, _, _, inBlockBytePos := findSequence(head, bytePos)
+++
+++     if current != nil {
+++             // Check whether the bit corresponding to the ordinal address is unset
+++             bitSel := uint32(blockFirstBit >> uint(inBlockBytePos*8+bitPos))
+++             if current.Block&bitSel == 0 {
+++                     return bytePos, bitPos, nil
+++             }
+++     }
+++
+++     return -1, -1, fmt.Errorf("requested bit is not available")
+++}
+++
+++// Given the byte position and the sequences list head, return the pointer to the
+++// sequence containing the byte (current), the pointer to the previous sequence,
+++// the number of blocks preceding the block containing the byte inside the current sequence.
+++// If bytePos is outside of the list, function will return (nil, nil, 0, -1)
+++func findSequence(head *Sequence, bytePos int) (*Sequence, *Sequence, uint32, int) {
+++     // Find the Sequence containing this byte
+++     previous := head
+++     current := head
+++     n := bytePos
+++     for current.Next != nil && n >= int(current.Count*blockBytes) { // Nil check for less than 32 addresses masks
+++             n -= int(current.Count * blockBytes)
+++             previous = current
+++             current = current.Next
+++     }
+++
+++     // If byte is outside of the list, let caller know
+++     if n >= int(current.Count*blockBytes) {
+++             return nil, nil, 0, -1
+++     }
+++
+++     // Find the byte position inside the block and the number of blocks
+++     // preceding the block containing the byte inside this sequence
+++     precBlocks := uint32(n / blockBytes)
+++     inBlockBytePos := bytePos % blockBytes
+++
+++     return current, previous, precBlocks, inBlockBytePos
+++}
+++
+++// PushReservation pushes the bit reservation inside the bitmask.
+++// Given byte and bit positions, identify the sequence (current) which holds the block containing the affected bit.
+++// Create a new block with the modified bit according to the operation (allocate/release).
+++// Create a new Sequence containing the new Block and insert it in the proper position.
+++// Remove current sequence if empty.
+++// Check if new Sequence can be merged with neighbour (previous/Next) sequences.
+++//
+++//
+++// Identify "current" Sequence containing block:
+++//                                      [prev seq] [current seq] [Next seq]
+++//
+++// Based on block position, resulting list of sequences can be any of three forms:
+++//
+++//        Block position                        Resulting list of sequences
+++// A) Block is first in current:         [prev seq] [new] [modified current seq] [Next seq]
+++// B) Block is last in current:          [prev seq] [modified current seq] [new] [Next seq]
+++// C) Block is in the middle of current: [prev seq] [curr pre] [new] [curr post] [Next seq]
+++func PushReservation(bytePos, bitPos int, head *Sequence, release bool) *Sequence {
+++     // Store list's head
+++     newHead := head
+++
+++     // Find the Sequence containing this byte
+++     current, previous, precBlocks, inBlockBytePos := findSequence(head, bytePos)
+++     if current == nil {
+++             return newHead
+++     }
+++
+++     // Construct updated block
+++     bitSel := uint32(blockFirstBit >> uint(inBlockBytePos*8+bitPos))
+++     newBlock := current.Block
+++     if release {
+++             newBlock &^= bitSel
+++     } else {
+++             newBlock |= bitSel
+++     }
+++
+++     // Quit if it was a redundant request
+++     if current.Block == newBlock {
+++             return newHead
+++     }
+++
+++     // Current Sequence inevitably looses one block, upadate Count
+++     current.Count--
+++
+++     // Create new sequence
+++     newSequence := &Sequence{Block: newBlock, Count: 1}
+++
+++     // Insert the new sequence in the list based on block position
+++     if precBlocks == 0 { // First in sequence (A)
+++             newSequence.Next = current
+++             if current == head {
+++                     newHead = newSequence
+++                     previous = newHead
+++             } else {
+++                     previous.Next = newSequence
+++             }
+++             removeCurrentIfEmpty(&newHead, newSequence, current)
+++             mergeSequences(previous)
+++     } else if precBlocks == current.Count-2 { // Last in sequence (B)
+++             newSequence.Next = current.Next
+++             current.Next = newSequence
+++             mergeSequences(current)
+++     } else { // In between the sequence (C)
+++             currPre := &Sequence{Block: current.Block, Count: precBlocks, Next: newSequence}
+++             currPost := current
+++             currPost.Count -= precBlocks
+++             newSequence.Next = currPost
+++             if currPost == head {
+++                     newHead = currPre
+++             } else {
+++                     previous.Next = currPre
+++             }
+++             // No merging or empty current possible here
+++     }
+++
+++     return newHead
+++}
+++
+++// Removes the current sequence from the list if empty, adjusting the head pointer if needed
+++func removeCurrentIfEmpty(head **Sequence, previous, current *Sequence) {
+++     if current.Count == 0 {
+++             if current == *head {
+++                     *head = current.Next
+++             } else {
+++                     previous.Next = current.Next
+++                     current = current.Next
+++             }
+++     }
+++}
+++
+++// Given a pointer to a Sequence, it checks if it can be merged with any following sequences
+++// It stops when no more merging is possible.
+++// TODO: Optimization: only attempt merge from start to end sequence, no need to scan till the end of the list
+++func mergeSequences(seq *Sequence) {
+++     if seq != nil {
+++             // Merge all what possible from seq
+++             for seq.Next != nil && seq.Block == seq.Next.Block {
+++                     seq.Count += seq.Next.Count
+++                     seq.Next = seq.Next.Next
+++             }
+++             // Move to Next
+++             mergeSequences(seq.Next)
+++     }
+++}
+++
+++func getNumBlocks(numBits uint32) uint32 {
+++     numBlocks := numBits / blockLen
+++     if numBits%blockLen != 0 {
+++             numBlocks++
+++     }
+++     return numBlocks
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..54e505f77b6fa1f1ad3ae51ae31d543282dfb577
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,427 @@@@
+++package bitseq
+++
+++import (
+++     "testing"
+++)
+++
+++func TestSequenceGetAvailableBit(t *testing.T) {
+++     input := []struct {
+++             head    *Sequence
+++             bytePos int
+++             bitPos  int
+++     }{
+++             {&Sequence{Block: 0x0, Count: 0}, -1, -1},
+++             {&Sequence{Block: 0x0, Count: 1}, 0, 0},
+++             {&Sequence{Block: 0x0, Count: 100}, 0, 0},
+++
+++             {&Sequence{Block: 0x80000000, Count: 0}, -1, -1},
+++             {&Sequence{Block: 0x80000000, Count: 1}, 0, 1},
+++             {&Sequence{Block: 0x80000000, Count: 100}, 0, 1},
+++
+++             {&Sequence{Block: 0xFF000000, Count: 0}, -1, -1},
+++             {&Sequence{Block: 0xFF000000, Count: 1}, 1, 0},
+++             {&Sequence{Block: 0xFF000000, Count: 100}, 1, 0},
+++
+++             {&Sequence{Block: 0xFF800000, Count: 0}, -1, -1},
+++             {&Sequence{Block: 0xFF800000, Count: 1}, 1, 1},
+++             {&Sequence{Block: 0xFF800000, Count: 100}, 1, 1},
+++
+++             {&Sequence{Block: 0xFFC0FF00, Count: 0}, -1, -1},
+++             {&Sequence{Block: 0xFFC0FF00, Count: 1}, 1, 2},
+++             {&Sequence{Block: 0xFFC0FF00, Count: 100}, 1, 2},
+++
+++             {&Sequence{Block: 0xFFE0FF00, Count: 0}, -1, -1},
+++             {&Sequence{Block: 0xFFE0FF00, Count: 1}, 1, 3},
+++             {&Sequence{Block: 0xFFE0FF00, Count: 100}, 1, 3},
+++
+++             {&Sequence{Block: 0xFFFEFF00, Count: 0}, -1, -1},
+++             {&Sequence{Block: 0xFFFEFF00, Count: 1}, 1, 7},
+++             {&Sequence{Block: 0xFFFEFF00, Count: 100}, 1, 7},
+++
+++             {&Sequence{Block: 0xFFFFC0FF, Count: 0}, -1, -1},
+++             {&Sequence{Block: 0xFFFFC0FF, Count: 1}, 2, 2},
+++             {&Sequence{Block: 0xFFFFC0FF, Count: 100}, 2, 2},
+++
+++             {&Sequence{Block: 0xFFFFFF00, Count: 0}, -1, -1},
+++             {&Sequence{Block: 0xFFFFFF00, Count: 1}, 3, 0},
+++             {&Sequence{Block: 0xFFFFFF00, Count: 100}, 3, 0},
+++
+++             {&Sequence{Block: 0xFFFFFFFE, Count: 0}, -1, -1},
+++             {&Sequence{Block: 0xFFFFFFFE, Count: 1}, 3, 7},
+++             {&Sequence{Block: 0xFFFFFFFE, Count: 100}, 3, 7},
+++
+++             {&Sequence{Block: 0xFFFFFFFF, Count: 0}, -1, -1},
+++             {&Sequence{Block: 0xFFFFFFFF, Count: 1}, -1, -1},
+++             {&Sequence{Block: 0xFFFFFFFF, Count: 100}, -1, -1},
+++     }
+++
+++     for n, i := range input {
+++             b, bb := i.head.GetAvailableBit()
+++             if b != i.bytePos || bb != i.bitPos {
+++                     t.Fatalf("Error in Sequence.getAvailableBit() (%d).\nExp: (%d, %d)\nGot: (%d, %d),", n, i.bytePos, i.bitPos, b, bb)
+++             }
+++     }
+++}
+++
+++func TestSequenceEqual(t *testing.T) {
+++     input := []struct {
+++             first    *Sequence
+++             second   *Sequence
+++             areEqual bool
+++     }{
+++             {&Sequence{Block: 0x0, Count: 8, Next: nil}, &Sequence{Block: 0x0, Count: 8}, true},
+++             {&Sequence{Block: 0x0, Count: 0, Next: nil}, &Sequence{Block: 0x0, Count: 0}, true},
+++             {&Sequence{Block: 0x0, Count: 2, Next: nil}, &Sequence{Block: 0x0, Count: 1, Next: &Sequence{Block: 0x0, Count: 1}}, false},
+++             {&Sequence{Block: 0x0, Count: 2, Next: &Sequence{Block: 0x1, Count: 1}}, &Sequence{Block: 0x0, Count: 2}, false},
+++
+++             {&Sequence{Block: 0x12345678, Count: 8, Next: nil}, &Sequence{Block: 0x12345678, Count: 8}, true},
+++             {&Sequence{Block: 0x12345678, Count: 8, Next: nil}, &Sequence{Block: 0x12345678, Count: 9}, false},
+++             {&Sequence{Block: 0x12345678, Count: 1, Next: &Sequence{Block: 0XFFFFFFFF, Count: 1}}, &Sequence{Block: 0x12345678, Count: 1}, false},
+++             {&Sequence{Block: 0x12345678, Count: 1}, &Sequence{Block: 0x12345678, Count: 1, Next: &Sequence{Block: 0XFFFFFFFF, Count: 1}}, false},
+++     }
+++
+++     for n, i := range input {
+++             if i.areEqual != i.first.Equal(i.second) {
+++                     t.Fatalf("Error in Sequence.Equal() (%d).\nExp: %t\nGot: %t,", n, i.areEqual, !i.areEqual)
+++             }
+++     }
+++}
+++
+++func TestSequenceCopy(t *testing.T) {
+++     s := &Sequence{
+++             Block: 0x0,
+++             Count: 8,
+++             Next: &Sequence{
+++                     Block: 0x0,
+++                     Count: 8,
+++                     Next: &Sequence{
+++                             Block: 0x0,
+++                             Count: 0,
+++                             Next: &Sequence{
+++                                     Block: 0x0,
+++                                     Count: 0,
+++                                     Next: &Sequence{
+++                                             Block: 0x0,
+++                                             Count: 2,
+++                                             Next: &Sequence{
+++                                                     Block: 0x0,
+++                                                     Count: 1,
+++                                                     Next: &Sequence{
+++                                                             Block: 0x0,
+++                                                             Count: 1,
+++                                                             Next: &Sequence{
+++                                                                     Block: 0x0,
+++                                                                     Count: 2,
+++                                                                     Next: &Sequence{
+++                                                                             Block: 0x1,
+++                                                                             Count: 1,
+++                                                                             Next: &Sequence{
+++                                                                                     Block: 0x0,
+++                                                                                     Count: 2,
+++                                                                                     Next:  nil,
+++                                                                             },
+++                                                                     },
+++                                                             },
+++                                                     },
+++                                             },
+++                                     },
+++                             },
+++                     },
+++             },
+++     }
+++
+++     n := s.GetCopy()
+++     if !s.Equal(n) {
+++             t.Fatalf("copy of s failed")
+++     }
+++     if n == s {
+++             t.Fatalf("not true copy of s")
+++     }
+++}
+++
+++func TestGetFirstAvailable(t *testing.T) {
+++     input := []struct {
+++             mask    *Sequence
+++             bytePos int
+++             bitPos  int
+++     }{
+++             {&Sequence{Block: 0xffffffff, Count: 2048}, -1, -1},
+++             {&Sequence{Block: 0x0, Count: 8}, 0, 0},
+++             {&Sequence{Block: 0x80000000, Count: 8}, 0, 1},
+++             {&Sequence{Block: 0xC0000000, Count: 8}, 0, 2},
+++             {&Sequence{Block: 0xE0000000, Count: 8}, 0, 3},
+++             {&Sequence{Block: 0xF0000000, Count: 8}, 0, 4},
+++             {&Sequence{Block: 0xF8000000, Count: 8}, 0, 5},
+++             {&Sequence{Block: 0xFC000000, Count: 8}, 0, 6},
+++             {&Sequence{Block: 0xFE000000, Count: 8}, 0, 7},
+++
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0x00000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 4, 0},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0x80000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 4, 1},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xC0000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 4, 2},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xE0000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 4, 3},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xF0000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 4, 4},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xF8000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 4, 5},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFC000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 4, 6},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFE000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 4, 7},
+++
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFF000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 0},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFF800000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 1},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFC00000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 2},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFE00000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 3},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFF00000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 4},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFF80000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 5},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFFC0000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 6},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFFE0000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 7},
+++
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xfffffffe, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 7, 7},
+++
+++             {&Sequence{Block: 0xffffffff, Count: 2, Next: &Sequence{Block: 0x0, Count: 6}}, 8, 0},
+++     }
+++
+++     for n, i := range input {
+++             bytePos, bitPos, _ := GetFirstAvailable(i.mask)
+++             if bytePos != i.bytePos || bitPos != i.bitPos {
+++                     t.Fatalf("Error in (%d) getFirstAvailable(). Expected (%d, %d). Got (%d, %d)", n, i.bytePos, i.bitPos, bytePos, bitPos)
+++             }
+++     }
+++}
+++
+++func TestFindSequence(t *testing.T) {
+++     input := []struct {
+++             head           *Sequence
+++             bytePos        int
+++             precBlocks     uint32
+++             inBlockBytePos int
+++     }{
+++             {&Sequence{Block: 0xffffffff, Count: 0}, 0, 0, -1},
+++             {&Sequence{Block: 0xffffffff, Count: 0}, 31, 0, -1},
+++             {&Sequence{Block: 0xffffffff, Count: 0}, 100, 0, -1},
+++
+++             {&Sequence{Block: 0x0, Count: 1}, 0, 0, 0},
+++             {&Sequence{Block: 0x0, Count: 1}, 1, 0, 1},
+++             {&Sequence{Block: 0x0, Count: 1}, 31, 0, -1},
+++             {&Sequence{Block: 0x0, Count: 1}, 60, 0, -1},
+++
+++             {&Sequence{Block: 0xffffffff, Count: 10}, 0, 0, 0},
+++             {&Sequence{Block: 0xffffffff, Count: 10}, 3, 0, 3},
+++             {&Sequence{Block: 0xffffffff, Count: 10}, 4, 1, 0},
+++             {&Sequence{Block: 0xffffffff, Count: 10}, 7, 1, 3},
+++             {&Sequence{Block: 0xffffffff, Count: 10}, 8, 2, 0},
+++             {&Sequence{Block: 0xffffffff, Count: 10}, 39, 9, 3},
+++
+++             {&Sequence{Block: 0xffffffff, Count: 10, Next: &Sequence{Block: 0xcc000000, Count: 10}}, 79, 9, 3},
+++             {&Sequence{Block: 0xffffffff, Count: 10, Next: &Sequence{Block: 0xcc000000, Count: 10}}, 80, 0, -1},
+++     }
+++
+++     for n, i := range input {
+++             _, _, precBlocks, inBlockBytePos := findSequence(i.head, i.bytePos)
+++             if precBlocks != i.precBlocks || inBlockBytePos != i.inBlockBytePos {
+++                     t.Fatalf("Error in (%d) findSequence(). Expected (%d, %d). Got (%d, %d)", n, i.precBlocks, i.inBlockBytePos, precBlocks, inBlockBytePos)
+++             }
+++     }
+++}
+++
+++func TestCheckIfAvailable(t *testing.T) {
+++     input := []struct {
+++             head    *Sequence
+++             ordinal int
+++             bytePos int
+++             bitPos  int
+++     }{
+++             {&Sequence{Block: 0xffffffff, Count: 0}, 0, -1, -1},
+++             {&Sequence{Block: 0xffffffff, Count: 0}, 31, -1, -1},
+++             {&Sequence{Block: 0xffffffff, Count: 0}, 100, -1, -1},
+++
+++             {&Sequence{Block: 0x0, Count: 1}, 0, 0, 0},
+++             {&Sequence{Block: 0x0, Count: 1}, 1, 0, 1},
+++             {&Sequence{Block: 0x0, Count: 1}, 31, 3, 7},
+++             {&Sequence{Block: 0x0, Count: 1}, 60, -1, -1},
+++
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0x800000ff, Count: 1}}, 31, -1, -1},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0x800000ff, Count: 1}}, 32, -1, -1},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0x800000ff, Count: 1}}, 33, 4, 1},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xC00000ff, Count: 1}}, 33, -1, -1},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xC00000ff, Count: 1}}, 34, 4, 2},
+++
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xC00000ff, Count: 1, Next: &Sequence{Block: 0x0, Count: 1}}}, 55, 6, 7},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xC00000ff, Count: 1, Next: &Sequence{Block: 0x0, Count: 1}}}, 56, -1, -1},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xC00000ff, Count: 1, Next: &Sequence{Block: 0x0, Count: 1}}}, 63, -1, -1},
+++
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xC00000ff, Count: 1, Next: &Sequence{Block: 0x0, Count: 1}}}, 64, 8, 0},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xC00000ff, Count: 1, Next: &Sequence{Block: 0x0, Count: 1}}}, 95, 11, 7},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xC00000ff, Count: 1, Next: &Sequence{Block: 0x0, Count: 1}}}, 96, -1, -1},
+++     }
+++
+++     for n, i := range input {
+++             bytePos, bitPos, _ := CheckIfAvailable(i.head, i.ordinal)
+++             if bytePos != i.bytePos || bitPos != i.bitPos {
+++                     t.Fatalf("Error in (%d) checkIfAvailable(ord:%d). Expected (%d, %d). Got (%d, %d)", n, i.ordinal, i.bytePos, i.bitPos, bytePos, bitPos)
+++             }
+++     }
+++}
+++
+++func TestMergeSequences(t *testing.T) {
+++     input := []struct {
+++             original *Sequence
+++             merged   *Sequence
+++     }{
+++             {&Sequence{Block: 0xFE000000, Count: 8, Next: &Sequence{Block: 0xFE000000, Count: 2}}, &Sequence{Block: 0xFE000000, Count: 10}},
+++             {&Sequence{Block: 0xFFFFFFFF, Count: 8, Next: &Sequence{Block: 0xFFFFFFFF, Count: 1}}, &Sequence{Block: 0xFFFFFFFF, Count: 9}},
+++             {&Sequence{Block: 0xFFFFFFFF, Count: 1, Next: &Sequence{Block: 0xFFFFFFFF, Count: 8}}, &Sequence{Block: 0xFFFFFFFF, Count: 9}},
+++
+++             {&Sequence{Block: 0xFFFFFFF0, Count: 8, Next: &Sequence{Block: 0xFFFFFFF0, Count: 1}}, &Sequence{Block: 0xFFFFFFF0, Count: 9}},
+++             {&Sequence{Block: 0xFFFFFFF0, Count: 1, Next: &Sequence{Block: 0xFFFFFFF0, Count: 8}}, &Sequence{Block: 0xFFFFFFF0, Count: 9}},
+++
+++             {&Sequence{Block: 0xFE, Count: 8, Next: &Sequence{Block: 0xFE, Count: 1, Next: &Sequence{Block: 0xFE, Count: 5}}}, &Sequence{Block: 0xFE, Count: 14}},
+++             {&Sequence{Block: 0xFE, Count: 8, Next: &Sequence{Block: 0xFE, Count: 1, Next: &Sequence{Block: 0xFE, Count: 5, Next: &Sequence{Block: 0xFF, Count: 1}}}},
+++                     &Sequence{Block: 0xFE, Count: 14, Next: &Sequence{Block: 0xFF, Count: 1}}},
+++
+++             // No merge
+++             {&Sequence{Block: 0xFE, Count: 8, Next: &Sequence{Block: 0xF8, Count: 1, Next: &Sequence{Block: 0xFE, Count: 5}}},
+++                     &Sequence{Block: 0xFE, Count: 8, Next: &Sequence{Block: 0xF8, Count: 1, Next: &Sequence{Block: 0xFE, Count: 5}}}},
+++
+++             // No merge from head: // Merge function tries to merge from passed head. If it can't merge with Next, it does not reattempt with Next as head
+++             {&Sequence{Block: 0xFE, Count: 8, Next: &Sequence{Block: 0xFF, Count: 1, Next: &Sequence{Block: 0xFF, Count: 5}}},
+++                     &Sequence{Block: 0xFE, Count: 8, Next: &Sequence{Block: 0xFF, Count: 6}}},
+++     }
+++
+++     for n, i := range input {
+++             mergeSequences(i.original)
+++             for !i.merged.Equal(i.original) {
+++                     t.Fatalf("Error in (%d) mergeSequences().\nExp: %s\nGot: %s,", n, i.merged, i.original)
+++             }
+++     }
+++}
+++
+++func TestPushReservation(t *testing.T) {
+++     input := []struct {
+++             mask    *Sequence
+++             bytePos int
+++             bitPos  int
+++             newMask *Sequence
+++     }{
+++             // Create first Sequence and fill in 8 addresses starting from address 0
+++             {&Sequence{Block: 0x0, Count: 8, Next: nil}, 0, 0, &Sequence{Block: 0x80000000, Count: 1, Next: &Sequence{Block: 0x0, Count: 7, Next: nil}}},
+++             {&Sequence{Block: 0x80000000, Count: 8}, 0, 1, &Sequence{Block: 0xC0000000, Count: 1, Next: &Sequence{Block: 0x80000000, Count: 7, Next: nil}}},
+++             {&Sequence{Block: 0xC0000000, Count: 8}, 0, 2, &Sequence{Block: 0xE0000000, Count: 1, Next: &Sequence{Block: 0xC0000000, Count: 7, Next: nil}}},
+++             {&Sequence{Block: 0xE0000000, Count: 8}, 0, 3, &Sequence{Block: 0xF0000000, Count: 1, Next: &Sequence{Block: 0xE0000000, Count: 7, Next: nil}}},
+++             {&Sequence{Block: 0xF0000000, Count: 8}, 0, 4, &Sequence{Block: 0xF8000000, Count: 1, Next: &Sequence{Block: 0xF0000000, Count: 7, Next: nil}}},
+++             {&Sequence{Block: 0xF8000000, Count: 8}, 0, 5, &Sequence{Block: 0xFC000000, Count: 1, Next: &Sequence{Block: 0xF8000000, Count: 7, Next: nil}}},
+++             {&Sequence{Block: 0xFC000000, Count: 8}, 0, 6, &Sequence{Block: 0xFE000000, Count: 1, Next: &Sequence{Block: 0xFC000000, Count: 7, Next: nil}}},
+++             {&Sequence{Block: 0xFE000000, Count: 8}, 0, 7, &Sequence{Block: 0xFF000000, Count: 1, Next: &Sequence{Block: 0xFE000000, Count: 7, Next: nil}}},
+++
+++             {&Sequence{Block: 0x80000000, Count: 1, Next: &Sequence{Block: 0x0, Count: 7}}, 0, 1, &Sequence{Block: 0xC0000000, Count: 1, Next: &Sequence{Block: 0x0, Count: 7, Next: nil}}},
+++
+++             // Create second Sequence and fill in 8 addresses starting from address 32
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0x00000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6, Next: nil}}}, 4, 0,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0x80000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0x80000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 4, 1,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xC0000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xC0000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 4, 2,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xE0000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xE0000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 4, 3,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xF0000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xF0000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 4, 4,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xF8000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xF8000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 4, 5,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFC000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFC000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 4, 6,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFE000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFE000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 4, 7,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFF000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++             // fill in 8 addresses starting from address 40
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFF000000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 0,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFF800000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFF800000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 1,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFC00000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFC00000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 2,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFE00000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFE00000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 3,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFF00000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFF00000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 4,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFF80000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFF80000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 5,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFFC0000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFFC0000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 6,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFFE0000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFFE0000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}, 5, 7,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xFFFF0000, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 6}}}},
+++
+++             // Insert new Sequence
+++             {&Sequence{Block: 0xffffffff, Count: 2, Next: &Sequence{Block: 0x0, Count: 6}}, 8, 0,
+++                     &Sequence{Block: 0xffffffff, Count: 2, Next: &Sequence{Block: 0x80000000, Count: 1, Next: &Sequence{Block: 0x0, Count: 5}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 2, Next: &Sequence{Block: 0x80000000, Count: 1, Next: &Sequence{Block: 0x0, Count: 5}}}, 8, 1,
+++                     &Sequence{Block: 0xffffffff, Count: 2, Next: &Sequence{Block: 0xC0000000, Count: 1, Next: &Sequence{Block: 0x0, Count: 5}}}},
+++
+++             // Merge affected with Next
+++             {&Sequence{Block: 0xffffffff, Count: 7, Next: &Sequence{Block: 0xfffffffe, Count: 2, Next: &Sequence{Block: 0xffffffff, Count: 1}}}, 31, 7,
+++                     &Sequence{Block: 0xffffffff, Count: 8, Next: &Sequence{Block: 0xfffffffe, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 1}}}},
+++             {&Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xfffffffc, Count: 1, Next: &Sequence{Block: 0xfffffffe, Count: 6}}}, 7, 6,
+++                     &Sequence{Block: 0xffffffff, Count: 1, Next: &Sequence{Block: 0xfffffffe, Count: 7}}},
+++
+++             // Merge affected with Next and Next.Next
+++             {&Sequence{Block: 0xffffffff, Count: 7, Next: &Sequence{Block: 0xfffffffe, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 1}}}, 31, 7,
+++                     &Sequence{Block: 0xffffffff, Count: 9}},
+++             {&Sequence{Block: 0xffffffff, Count: 7, Next: &Sequence{Block: 0xfffffffe, Count: 1}}, 31, 7,
+++                     &Sequence{Block: 0xffffffff, Count: 8}},
+++
+++             // Merge affected with previous and Next
+++             {&Sequence{Block: 0xffffffff, Count: 7, Next: &Sequence{Block: 0xfffffffe, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 1}}}, 31, 7,
+++                     &Sequence{Block: 0xffffffff, Count: 9}},
+++
+++             // Redundant push: No change
+++             {&Sequence{Block: 0xffff0000, Count: 1}, 0, 0, &Sequence{Block: 0xffff0000, Count: 1}},
+++             {&Sequence{Block: 0xffff0000, Count: 7}, 25, 7, &Sequence{Block: 0xffff0000, Count: 7}},
+++             {&Sequence{Block: 0xffffffff, Count: 7, Next: &Sequence{Block: 0xfffffffe, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 1}}}, 7, 7,
+++                     &Sequence{Block: 0xffffffff, Count: 7, Next: &Sequence{Block: 0xfffffffe, Count: 1, Next: &Sequence{Block: 0xffffffff, Count: 1}}}},
+++     }
+++
+++     for n, i := range input {
+++             mask := PushReservation(i.bytePos, i.bitPos, i.mask, false)
+++             if !mask.Equal(i.newMask) {
+++                     t.Fatalf("Error in (%d) pushReservation():\n%s + (%d,%d):\nExp: %s\nGot: %s,", n, i.mask, i.bytePos, i.bitPos, i.newMask, mask)
+++             }
+++     }
+++}
+++
+++func TestSerializeDeserialize(t *testing.T) {
+++     s := &Sequence{
+++             Block: 0xffffffff,
+++             Count: 1,
+++             Next: &Sequence{
+++                     Block: 0xFF000000,
+++                     Count: 1,
+++                     Next: &Sequence{
+++                             Block: 0xffffffff,
+++                             Count: 6,
+++                             Next: &Sequence{
+++                                     Block: 0xffffffff,
+++                                     Count: 1,
+++                                     Next: &Sequence{
+++                                             Block: 0xFF800000,
+++                                             Count: 1,
+++                                             Next: &Sequence{
+++                                                     Block: 0xffffffff,
+++                                                     Count: 6,
+++                                             },
+++                                     },
+++                             },
+++                     },
+++             },
+++     }
+++
+++     data, err := s.ToByteArray()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     r := &Sequence{}
+++     err = r.FromByteArray(data)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if !s.Equal(r) {
+++             t.Fatalf("Sequences are different: \n%v\n%v", s, r)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b5b3f231420fe7d32795d51b7f85cc17cbd303c9
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,138 @@@@
+++package bitseq
+++
+++import (
+++     "encoding/json"
+++     "fmt"
+++
+++     log "github.com/Sirupsen/logrus"
+++     "github.com/docker/libnetwork/datastore"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++// Key provides the Key to be used in KV Store
+++func (h *Handle) Key() []string {
+++     h.Lock()
+++     defer h.Unlock()
+++     return []string{h.app, h.id}
+++}
+++
+++// KeyPrefix returns the immediate parent key that can be used for tree walk
+++func (h *Handle) KeyPrefix() []string {
+++     h.Lock()
+++     defer h.Unlock()
+++     return []string{h.app}
+++}
+++
+++// Value marshals the data to be stored in the KV store
+++func (h *Handle) Value() []byte {
+++     b, err := h.ToByteArray()
+++     if err != nil {
+++             log.Warnf("Failed to serialize Handle: %v", err)
+++             b = []byte{}
+++     }
+++     jv, err := json.Marshal(b)
+++     if err != nil {
+++             log.Warnf("Failed to json encode bitseq handler byte array: %v", err)
+++             return []byte{}
+++     }
+++     return jv
+++}
+++
+++// SetValue unmarshals the data from the KV store
+++func (h *Handle) SetValue(value []byte) error {
+++     var b []byte
+++     if err := json.Unmarshal(value, &b); err != nil {
+++             return err
+++     }
+++
+++     return h.FromByteArray(b)
+++}
+++
+++// Index returns the latest DB Index as seen by this object
+++func (h *Handle) Index() uint64 {
+++     h.Lock()
+++     defer h.Unlock()
+++     return h.dbIndex
+++}
+++
+++// SetIndex method allows the datastore to store the latest DB Index into this object
+++func (h *Handle) SetIndex(index uint64) {
+++     h.Lock()
+++     h.dbIndex = index
+++     h.dbExists = true
+++     h.Unlock()
+++}
+++
+++// Exists method is true if this object has been stored in the DB.
+++func (h *Handle) Exists() bool {
+++     h.Lock()
+++     defer h.Unlock()
+++     return h.dbExists
+++}
+++
+++func (h *Handle) watchForChanges() error {
+++     h.Lock()
+++     store := h.store
+++     h.Unlock()
+++
+++     if store == nil {
+++             return nil
+++     }
+++
+++     kvpChan, err := store.KVStore().Watch(datastore.Key(h.Key()...), nil)
+++     if err != nil {
+++             return err
+++     }
+++     go func() {
+++             for {
+++                     select {
+++                     case kvPair := <-kvpChan:
+++                             // Only process remote update
+++                             if kvPair != nil && (kvPair.LastIndex != h.Index()) {
+++                                     err := h.fromDsValue(kvPair.Value)
+++                                     if err != nil {
+++                                             log.Warnf("Failed to reconstruct bitseq handle from ds watch: %s", err.Error())
+++                                     } else {
+++                                             h.SetIndex(kvPair.LastIndex)
+++                                     }
+++                             }
+++                     }
+++             }
+++     }()
+++     return nil
+++}
+++
+++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
+++     h.Unlock()
+++     if store == nil {
+++             return nil
+++     }
+++     err := store.PutObjectAtomic(h)
+++     if err == datastore.ErrKeyModified {
+++             return types.RetryErrorf("failed to perform atomic write (%v). retry might fix the error", err)
+++     }
+++     return err
+++}
+++
+++func (h *Handle) deleteFromStore() error {
+++     h.Lock()
+++     store := h.store
+++     h.Unlock()
+++     if store == nil {
+++             return nil
+++     }
+++     return store.DeleteObjectAtomic(h)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d02f6a92ed59c5aac87554a3d92a36cd3d74a41f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,12 @@@@
+++machine:
+++    services:
+++        - docker
+++
+++dependencies:
+++    override:
+++        - echo "Nothing to install"
+++
+++test:
+++    override:
+++        - make circle-ci
+++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..c5713b01b34f2f7868b47a58c32901bcc9f12039
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,115 @@@@
+++package client
+++
+++import (
+++     "fmt"
+++     "io"
+++     "io/ioutil"
+++     "net/http"
+++     "reflect"
+++     "strings"
+++
+++     flag "github.com/docker/docker/pkg/mflag"
+++)
+++
+++// CallFunc provides environment specific call utility to invoke backend functions from UI
+++type CallFunc func(string, string, interface{}, map[string][]string) (io.ReadCloser, http.Header, int, error)
+++
+++// NetworkCli is the UI object for network subcmds
+++type NetworkCli struct {
+++     out  io.Writer
+++     err  io.Writer
+++     call CallFunc
+++}
+++
+++// NewNetworkCli is a convenient function to create a NetworkCli object
+++func NewNetworkCli(out, err io.Writer, call CallFunc) *NetworkCli {
+++     return &NetworkCli{
+++             out:  out,
+++             err:  err,
+++             call: call,
+++     }
+++}
+++
+++// getMethod is Borrowed from Docker UI which uses reflection to identify the UI Handler
+++func (cli *NetworkCli) getMethod(args ...string) (func(string, ...string) error, bool) {
+++     camelArgs := make([]string, len(args))
+++     for i, s := range args {
+++             if len(s) == 0 {
+++                     return nil, false
+++             }
+++             camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
+++     }
+++     methodName := "Cmd" + strings.Join(camelArgs, "")
+++     method := reflect.ValueOf(cli).MethodByName(methodName)
+++     if !method.IsValid() {
+++             return nil, false
+++     }
+++     return method.Interface().(func(string, ...string) error), true
+++}
+++
+++// Cmd is borrowed from Docker UI and acts as the entry point for network UI commands.
+++// network UI commands are designed to be invoked from multiple parent chains
+++func (cli *NetworkCli) Cmd(chain string, args ...string) error {
+++     if len(args) > 2 {
+++             method, exists := cli.getMethod(args[:3]...)
+++             if exists {
+++                     return method(chain+" "+args[0]+" "+args[1], args[3:]...)
+++             }
+++     }
+++     if len(args) > 1 {
+++             method, exists := cli.getMethod(args[:2]...)
+++             if exists {
+++                     return method(chain+" "+args[0], args[2:]...)
+++             }
+++     }
+++     if len(args) > 0 {
+++             method, exists := cli.getMethod(args[0])
+++             if !exists {
+++                     return fmt.Errorf("%s: '%s' is not a %s command. See '%s --help'.\n", chain, args[0], chain, chain)
+++             }
+++             return method(chain, args[1:]...)
+++     }
+++     flag.Usage()
+++     return nil
+++}
+++
+++// Subcmd is borrowed from Docker UI and performs the same function of configuring the subCmds
+++func (cli *NetworkCli) Subcmd(chain, name, signature, description string, exitOnError bool) *flag.FlagSet {
+++     var errorHandling flag.ErrorHandling
+++     if exitOnError {
+++             errorHandling = flag.ExitOnError
+++     } else {
+++             errorHandling = flag.ContinueOnError
+++     }
+++     flags := flag.NewFlagSet(name, errorHandling)
+++     flags.Usage = func() {
+++             flags.ShortUsage()
+++             flags.PrintDefaults()
+++     }
+++     flags.ShortUsage = func() {
+++             options := ""
+++             if signature != "" {
+++                     signature = " " + signature
+++             }
+++             if flags.FlagCountUndeprecated() > 0 {
+++                     options = " [OPTIONS]"
+++             }
+++             fmt.Fprintf(cli.out, "\nUsage: %s %s%s%s\n\n%s\n\n", chain, name, options, signature, description)
+++             flags.SetOutput(cli.out)
+++     }
+++     return flags
+++}
+++
+++func readBody(stream io.ReadCloser, hdr http.Header, statusCode int, err error) ([]byte, int, error) {
+++     if stream != nil {
+++             defer stream.Close()
+++     }
+++     if err != nil {
+++             return nil, statusCode, err
+++     }
+++     body, err := ioutil.ReadAll(stream)
+++     if err != nil {
+++             return nil, -1, err
+++     }
+++     return body, statusCode, nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..fb10fd9fd751acc2aca5644bd457441415caf745
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,122 @@@@
+++package client
+++
+++import (
+++     "bytes"
+++     "testing"
+++
+++     _ "github.com/docker/libnetwork/netutils"
+++)
+++
+++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.Fatalf("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())
+++     }
+++}
+++*/
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..06b8b2a6dcac27d15299584bab2a4a3c21c61755
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,217 @@@@
+++package client
+++
+++import (
+++     "bytes"
+++     "encoding/json"
+++     "fmt"
+++     "io"
+++     "net/http"
+++     "os"
+++     "strings"
+++     "testing"
+++
+++     _ "github.com/docker/libnetwork/netutils"
+++)
+++
+++// 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 []byte
+++var mockNwName = "test"
+++var mockNwID = "2a3456789"
+++var mockServiceName = "testSrv"
+++var mockServiceID = "2a3456789"
+++var mockContainerID = "2a3456789"
+++
+++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)
+++
+++     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")
+++                     }
+++             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(mockContainerID)
+++                     }
+++                     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.Fatalf("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.Fatalf("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.Fatalf("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())
+++     }
+++}
+++*/
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a244ad5f699a166ce790a31415dd7f07b23a514e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,231 @@@@
+++package client
+++
+++import (
+++     "bytes"
+++     "encoding/json"
+++     "fmt"
+++     "net/http"
+++     "text/tabwriter"
+++
+++     flag "github.com/docker/docker/pkg/mflag"
+++     "github.com/docker/docker/pkg/stringid"
+++)
+++
+++type command struct {
+++     name        string
+++     description string
+++}
+++
+++var (
+++     networkCommands = []command{
+++             {"create", "Create a network"},
+++             {"rm", "Remove a network"},
+++             {"ls", "List all networks"},
+++             {"info", "Display information of a network"},
+++     }
+++)
+++
+++// CmdNetwork handles the root Network UI
+++func (cli *NetworkCli) CmdNetwork(chain string, args ...string) error {
+++     cmd := cli.Subcmd(chain, "network", "COMMAND [OPTIONS] [arg...]", networkUsage(chain), false)
+++     cmd.Require(flag.Min, 1)
+++     err := cmd.ParseFlags(args, true)
+++     if err == nil {
+++             cmd.Usage()
+++             return fmt.Errorf("invalid command : %v", args)
+++     }
+++     return err
+++}
+++
+++// CmdNetworkCreate handles Network Create UI
+++func (cli *NetworkCli) CmdNetworkCreate(chain string, args ...string) error {
+++     cmd := cli.Subcmd(chain, "create", "NETWORK-NAME", "Creates a new network with a name specified by the user", false)
+++     flDriver := cmd.String([]string{"d", "-driver"}, "", "Driver to manage the Network")
+++     cmd.Require(flag.Exact, 1)
+++     err := cmd.ParseFlags(args, true)
+++     if err != nil {
+++             return err
+++     }
+++
+++     // Construct network create request body
+++     ops := make(map[string]interface{})
+++     nc := networkCreate{Name: cmd.Arg(0), NetworkType: *flDriver, Options: ops}
+++     obj, _, err := readBody(cli.call("POST", "/networks", nc, nil))
+++     if err != nil {
+++             return err
+++     }
+++     var replyID string
+++     err = json.Unmarshal(obj, &replyID)
+++     if err != nil {
+++             return err
+++     }
+++     fmt.Fprintf(cli.out, "%s\n", replyID)
+++     return nil
+++}
+++
+++// CmdNetworkRm handles Network Delete UI
+++func (cli *NetworkCli) CmdNetworkRm(chain string, args ...string) error {
+++     cmd := cli.Subcmd(chain, "rm", "NETWORK", "Deletes a network", false)
+++     cmd.Require(flag.Exact, 1)
+++     err := cmd.ParseFlags(args, true)
+++     if err != nil {
+++             return err
+++     }
+++     id, err := lookupNetworkID(cli, cmd.Arg(0))
+++     if err != nil {
+++             return err
+++     }
+++     _, _, err = readBody(cli.call("DELETE", "/networks/"+id, nil, nil))
+++     if err != nil {
+++             return err
+++     }
+++     return nil
+++}
+++
+++// CmdNetworkLs handles Network List UI
+++func (cli *NetworkCli) CmdNetworkLs(chain string, args ...string) error {
+++     cmd := cli.Subcmd(chain, "ls", "", "Lists all the networks created by the user", false)
+++     quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
+++     noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output")
+++     nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show the latest network created")
+++     last := cmd.Int([]string{"n"}, -1, "Show n last created networks")
+++     err := cmd.ParseFlags(args, true)
+++     if err != nil {
+++             return err
+++     }
+++     obj, _, err := readBody(cli.call("GET", "/networks", nil, nil))
+++     if err != nil {
+++             return err
+++     }
+++     if *last == -1 && *nLatest {
+++             *last = 1
+++     }
+++
+++     var networkResources []networkResource
+++     err = json.Unmarshal(obj, &networkResources)
+++     if err != nil {
+++             return err
+++     }
+++
+++     wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
+++
+++     // unless quiet (-q) is specified, print field titles
+++     if !*quiet {
+++             fmt.Fprintln(wr, "NETWORK ID\tNAME\tTYPE")
+++     }
+++
+++     for _, networkResource := range networkResources {
+++             ID := networkResource.ID
+++             netName := networkResource.Name
+++             if !*noTrunc {
+++                     ID = stringid.TruncateID(ID)
+++             }
+++             if *quiet {
+++                     fmt.Fprintln(wr, ID)
+++                     continue
+++             }
+++             netType := networkResource.Type
+++             fmt.Fprintf(wr, "%s\t%s\t%s\t",
+++                     ID,
+++                     netName,
+++                     netType)
+++             fmt.Fprint(wr, "\n")
+++     }
+++     wr.Flush()
+++     return nil
+++}
+++
+++// CmdNetworkInfo handles Network Info UI
+++func (cli *NetworkCli) CmdNetworkInfo(chain string, args ...string) error {
+++     cmd := cli.Subcmd(chain, "info", "NETWORK", "Displays detailed information on a network", false)
+++     cmd.Require(flag.Exact, 1)
+++     err := cmd.ParseFlags(args, true)
+++     if err != nil {
+++             return err
+++     }
+++
+++     id, err := lookupNetworkID(cli, cmd.Arg(0))
+++     if err != nil {
+++             return err
+++     }
+++
+++     obj, _, err := readBody(cli.call("GET", "/networks/"+id, nil, nil))
+++     if err != nil {
+++             return err
+++     }
+++     networkResource := &networkResource{}
+++     if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
+++             return err
+++     }
+++     fmt.Fprintf(cli.out, "Network Id: %s\n", networkResource.ID)
+++     fmt.Fprintf(cli.out, "Name: %s\n", networkResource.Name)
+++     fmt.Fprintf(cli.out, "Type: %s\n", networkResource.Type)
+++     if networkResource.Services != nil {
+++             for _, serviceResource := range networkResource.Services {
+++                     fmt.Fprintf(cli.out, "  Service Id: %s\n", serviceResource.ID)
+++                     fmt.Fprintf(cli.out, "\tName: %s\n", serviceResource.Name)
+++             }
+++     }
+++
+++     return nil
+++}
+++
+++// Helper function to predict if a string is a name or id or partial-id
+++// This provides a best-effort mechanism to identify a id with the help of GET Filter APIs
+++// Being a UI, its most likely that name will be used by the user, which is used to lookup
+++// the corresponding ID. If ID is not found, this function will assume that the passed string
+++// is an ID by itself.
+++
+++func lookupNetworkID(cli *NetworkCli, nameID string) (string, error) {
+++     obj, statusCode, err := readBody(cli.call("GET", "/networks?name="+nameID, nil, nil))
+++     if err != nil {
+++             return "", err
+++     }
+++
+++     if statusCode != http.StatusOK {
+++             return "", fmt.Errorf("name query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj))
+++     }
+++
+++     var list []*networkResource
+++     err = json.Unmarshal(obj, &list)
+++     if err != nil {
+++             return "", err
+++     }
+++     if len(list) > 0 {
+++             // name query filter will always return a single-element collection
+++             return list[0].ID, nil
+++     }
+++
+++     // Check for Partial-id
+++     obj, statusCode, err = readBody(cli.call("GET", "/networks?partial-id="+nameID, nil, nil))
+++     if err != nil {
+++             return "", err
+++     }
+++
+++     if statusCode != http.StatusOK {
+++             return "", fmt.Errorf("partial-id match query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj))
+++     }
+++
+++     err = json.Unmarshal(obj, &list)
+++     if err != nil {
+++             return "", err
+++     }
+++     if len(list) == 0 {
+++             return "", fmt.Errorf("resource not found %s", nameID)
+++     }
+++     if len(list) > 1 {
+++             return "", fmt.Errorf("multiple Networks matching the partial identifier (%s). Please use full identifier", nameID)
+++     }
+++     return list[0].ID, nil
+++}
+++
+++func networkUsage(chain string) string {
+++     help := "Commands:\n"
+++
+++     for _, cmd := range networkCommands {
+++             help += fmt.Sprintf("  %-25.25s%s\n", cmd.name, cmd.description)
+++     }
+++
+++     help += fmt.Sprintf("\nRun '%s network COMMAND --help' for more information on a command.", chain)
+++     return help
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..35e040f80511e345cb28b39919e39cf1877d693b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,362 @@@@
+++package client
+++
+++import (
+++     "bytes"
+++     "encoding/json"
+++     "fmt"
+++     "net/http"
+++     "strings"
+++     "text/tabwriter"
+++
+++     flag "github.com/docker/docker/pkg/mflag"
+++     "github.com/docker/docker/pkg/stringid"
+++)
+++
+++var (
+++     serviceCommands = []command{
+++             {"publish", "Publish a service"},
+++             {"unpublish", "Remove a service"},
+++             {"attach", "Attach a backend (container) to the service"},
+++             {"detach", "Detach the backend from the service"},
+++             {"ls", "Lists all services"},
+++             {"info", "Display information about a service"},
+++     }
+++)
+++
+++func lookupServiceID(cli *NetworkCli, nwName, svNameID string) (string, error) {
+++     // Sanity Check
+++     obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/networks?name=%s", nwName), nil, nil))
+++     if err != nil {
+++             return "", err
+++     }
+++     var nwList []networkResource
+++     if err = json.Unmarshal(obj, &nwList); err != nil {
+++             return "", err
+++     }
+++     if len(nwList) == 0 {
+++             return "", fmt.Errorf("Network %s does not exist", nwName)
+++     }
+++
+++     if nwName == "" {
+++             obj, _, err := readBody(cli.call("GET", "/networks/"+nwList[0].ID, nil, nil))
+++             if err != nil {
+++                     return "", err
+++             }
+++             networkResource := &networkResource{}
+++             if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
+++                     return "", err
+++             }
+++             nwName = networkResource.Name
+++     }
+++
+++     // Query service by name
+++     obj, statusCode, err := readBody(cli.call("GET", fmt.Sprintf("/services?name=%s", svNameID), nil, nil))
+++     if err != nil {
+++             return "", err
+++     }
+++
+++     if statusCode != http.StatusOK {
+++             return "", fmt.Errorf("name query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
+++     }
+++
+++     var list []*serviceResource
+++     if err = json.Unmarshal(obj, &list); err != nil {
+++             return "", err
+++     }
+++     for _, sr := range list {
+++             if sr.Network == nwName {
+++                     return sr.ID, nil
+++             }
+++     }
+++
+++     // Query service by Partial-id (this covers full id as well)
+++     obj, statusCode, err = readBody(cli.call("GET", fmt.Sprintf("/services?partial-id=%s", svNameID), nil, nil))
+++     if err != nil {
+++             return "", err
+++     }
+++
+++     if statusCode != http.StatusOK {
+++             return "", fmt.Errorf("partial-id match query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
+++     }
+++
+++     if err = json.Unmarshal(obj, &list); err != nil {
+++             return "", err
+++     }
+++     for _, sr := range list {
+++             if sr.Network == nwName {
+++                     return sr.ID, nil
+++             }
+++     }
+++
+++     return "", fmt.Errorf("Service %s not found on network %s", svNameID, nwName)
+++}
+++
+++func lookupContainerID(cli *NetworkCli, cnNameID string) (string, error) {
+++     // Container is a Docker resource, ask docker about it.
+++     // In case of connecton error, we assume we are running in dnet and return whatever was passed to us
+++     obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/containers/%s/json", cnNameID), nil, nil))
+++     if err != nil {
+++             // We are probably running outside of docker
+++             return cnNameID, nil
+++     }
+++
+++     var x map[string]interface{}
+++     err = json.Unmarshal(obj, &x)
+++     if err != nil {
+++             return "", err
+++     }
+++     if iid, ok := x["Id"]; ok {
+++             if id, ok := iid.(string); ok {
+++                     return id, nil
+++             }
+++             return "", fmt.Errorf("Unexpected data type for container ID in json response")
+++     }
+++     return "", fmt.Errorf("Cannot find container ID in json response")
+++}
+++
+++// CmdService handles the service UI
+++func (cli *NetworkCli) CmdService(chain string, args ...string) error {
+++     cmd := cli.Subcmd(chain, "service", "COMMAND [OPTIONS] [arg...]", serviceUsage(chain), false)
+++     cmd.Require(flag.Min, 1)
+++     err := cmd.ParseFlags(args, true)
+++     if err == nil {
+++             cmd.Usage()
+++             return fmt.Errorf("Invalid command : %v", args)
+++     }
+++     return err
+++}
+++
+++// Parse service name for "SERVICE[.NETWORK]" format
+++func parseServiceName(name string) (string, string) {
+++     s := strings.Split(name, ".")
+++     var sName, nName string
+++     if len(s) > 1 {
+++             nName = s[len(s)-1]
+++             sName = strings.Join(s[:len(s)-1], ".")
+++     } else {
+++             sName = s[0]
+++     }
+++     return sName, nName
+++}
+++
+++// CmdServicePublish handles service create UI
+++func (cli *NetworkCli) CmdServicePublish(chain string, args ...string) error {
+++     cmd := cli.Subcmd(chain, "publish", "SERVICE[.NETWORK]", "Publish a new service on a network", false)
+++     cmd.Require(flag.Exact, 1)
+++     err := cmd.ParseFlags(args, true)
+++     if err != nil {
+++             return err
+++     }
+++
+++     sn, nn := parseServiceName(cmd.Arg(0))
+++     sc := serviceCreate{Name: sn, Network: nn}
+++     obj, _, err := readBody(cli.call("POST", "/services", sc, nil))
+++     if err != nil {
+++             return err
+++     }
+++
+++     var replyID string
+++     err = json.Unmarshal(obj, &replyID)
+++     if err != nil {
+++             return err
+++     }
+++
+++     fmt.Fprintf(cli.out, "%s\n", replyID)
+++     return nil
+++}
+++
+++// CmdServiceUnpublish handles service delete UI
+++func (cli *NetworkCli) CmdServiceUnpublish(chain string, args ...string) error {
+++     cmd := cli.Subcmd(chain, "unpublish", "SERVICE[.NETWORK]", "Removes a service", false)
+++     cmd.Require(flag.Exact, 1)
+++     err := cmd.ParseFlags(args, true)
+++     if err != nil {
+++             return err
+++     }
+++
+++     sn, nn := parseServiceName(cmd.Arg(0))
+++     serviceID, err := lookupServiceID(cli, nn, sn)
+++     if err != nil {
+++             return err
+++     }
+++
+++     _, _, err = readBody(cli.call("DELETE", "/services/"+serviceID, nil, nil))
+++
+++     return err
+++}
+++
+++// CmdServiceLs handles service list UI
+++func (cli *NetworkCli) CmdServiceLs(chain string, args ...string) error {
+++     cmd := cli.Subcmd(chain, "ls", "SERVICE", "Lists all the services on a network", false)
+++     flNetwork := cmd.String([]string{"net", "-network"}, "", "Only show the services that are published on the specified network")
+++     quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
+++     noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output")
+++
+++     err := cmd.ParseFlags(args, true)
+++     if err != nil {
+++             return err
+++     }
+++
+++     var obj []byte
+++     if *flNetwork == "" {
+++             obj, _, err = readBody(cli.call("GET", "/services", nil, nil))
+++     } else {
+++             obj, _, err = readBody(cli.call("GET", "/services?network="+*flNetwork, nil, nil))
+++     }
+++     if err != nil {
+++             return err
+++     }
+++
+++     var serviceResources []serviceResource
+++     err = json.Unmarshal(obj, &serviceResources)
+++     if err != nil {
+++             fmt.Println(err)
+++             return err
+++     }
+++
+++     wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
+++     // unless quiet (-q) is specified, print field titles
+++     if !*quiet {
+++             fmt.Fprintln(wr, "SERVICE ID\tNAME\tNETWORK\tCONTAINER")
+++     }
+++
+++     for _, sr := range serviceResources {
+++             ID := sr.ID
+++             bkID, err := getBackendID(cli, ID)
+++             if err != nil {
+++                     return err
+++             }
+++             if !*noTrunc {
+++                     ID = stringid.TruncateID(ID)
+++                     bkID = stringid.TruncateID(bkID)
+++             }
+++             if !*quiet {
+++                     fmt.Fprintf(wr, "%s\t%s\t%s\t%s\n", ID, sr.Name, sr.Network, bkID)
+++             } else {
+++                     fmt.Fprintln(wr, ID)
+++             }
+++     }
+++     wr.Flush()
+++
+++     return nil
+++}
+++
+++func getBackendID(cli *NetworkCli, servID string) (string, error) {
+++     var (
+++             obj []byte
+++             err error
+++             bk  string
+++     )
+++
+++     if obj, _, err = readBody(cli.call("GET", "/services/"+servID+"/backend", nil, nil)); err == nil {
+++             var bkl []backendResource
+++             if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&bkl); err == nil {
+++                     if len(bkl) > 0 {
+++                             bk = bkl[0].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)", servID, err)
+++             }
+++     }
+++
+++     return bk, err
+++}
+++
+++// CmdServiceInfo handles service info UI
+++func (cli *NetworkCli) CmdServiceInfo(chain string, args ...string) error {
+++     cmd := cli.Subcmd(chain, "info", "SERVICE[.NETWORK]", "Displays detailed information about a service", false)
+++     cmd.Require(flag.Min, 1)
+++
+++     err := cmd.ParseFlags(args, true)
+++     if err != nil {
+++             return err
+++     }
+++
+++     sn, nn := parseServiceName(cmd.Arg(0))
+++     serviceID, err := lookupServiceID(cli, nn, sn)
+++     if err != nil {
+++             return err
+++     }
+++
+++     obj, _, err := readBody(cli.call("GET", "/services/"+serviceID, nil, nil))
+++     if err != nil {
+++             return err
+++     }
+++
+++     sr := &serviceResource{}
+++     if err := json.NewDecoder(bytes.NewReader(obj)).Decode(sr); err != nil {
+++             return err
+++     }
+++
+++     fmt.Fprintf(cli.out, "Service Id: %s\n", sr.ID)
+++     fmt.Fprintf(cli.out, "\tName: %s\n", sr.Name)
+++     fmt.Fprintf(cli.out, "\tNetwork: %s\n", sr.Network)
+++
+++     return nil
+++}
+++
+++// CmdServiceAttach handles service attach UI
+++func (cli *NetworkCli) CmdServiceAttach(chain string, args ...string) error {
+++     cmd := cli.Subcmd(chain, "attach", "CONTAINER SERVICE[.NETWORK]", "Sets a container as a service backend", false)
+++     cmd.Require(flag.Min, 2)
+++     err := cmd.ParseFlags(args, true)
+++     if err != nil {
+++             return err
+++     }
+++
+++     containerID, err := lookupContainerID(cli, cmd.Arg(0))
+++     if err != nil {
+++             return err
+++     }
+++
+++     sn, nn := parseServiceName(cmd.Arg(1))
+++     serviceID, err := lookupServiceID(cli, nn, sn)
+++     if err != nil {
+++             return err
+++     }
+++
+++     nc := serviceAttach{ContainerID: containerID}
+++
+++     _, _, 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
+++     }
+++
+++     serviceID, err := lookupServiceID(cli, nn, sn)
+++     if err != nil {
+++             return err
+++     }
+++
+++     _, _, err = readBody(cli.call("DELETE", "/services/"+serviceID+"/backend/"+containerID, 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
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e14460aba72c7f0f862147b7dc7ccd81be59e54f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,73 @@@@
+++package client
+++
+++import "github.com/docker/libnetwork/types"
+++
+++/***********
+++ Resources
+++************/
+++
+++// networkResource is the body of the "get network" http response message
+++type networkResource struct {
+++     Name     string             `json:"name"`
+++     ID       string             `json:"id"`
+++     Type     string             `json:"type"`
+++     Services []*serviceResource `json:"services"`
+++}
+++
+++// serviceResource is the body of the "get service" http response message
+++type serviceResource struct {
+++     Name    string `json:"name"`
+++     ID      string `json:"id"`
+++     Network string `json:"network"`
+++}
+++
+++// backendResource is the body of "get service backend" response message
+++type backendResource struct {
+++     ID string `json:"id"`
+++}
+++
+++/***********
+++  Body types
+++  ************/
+++
+++// networkCreate is the expected body of the "create network" http request message
+++type networkCreate struct {
+++     Name        string                 `json:"name"`
+++     NetworkType string                 `json:"network_type"`
+++     Options     map[string]interface{} `json:"options"`
+++}
+++
+++// serviceCreate represents the body of the "publish service" http request message
+++type serviceCreate struct {
+++     Name         string                `json:"name"`
+++     Network      string                `json:"network_name"`
+++     ExposedPorts []types.TransportPort `json:"exposed_ports"`
+++     PortMapping  []types.PortBinding   `json:"port_mapping"`
+++}
+++
+++// serviceAttach represents the expected body of the "attach/detach backend to/from service" http request messages
+++type serviceAttach 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        []serviceExtraHost    `json:"extra_hosts"`
+++     ParentUpdates     []serviceParentUpdate `json:"parent_updates"`
+++     UseDefaultSandbox bool                  `json:"use_default_sandbox"`
+++}
+++
+++// serviceExtraHost represents the extra host object
+++type serviceExtraHost struct {
+++     Name    string `json:"name"`
+++     Address string `json:"address"`
+++}
+++
+++// EndpointParentUpdate is the object carrying the information about the
+++// endpoint parent that needs to be updated
+++type serviceParentUpdate struct {
+++     EndpointID string `json:"service_id"`
+++     Name       string `json:"name"`
+++     Address    string `json:"address"`
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..c0d497d4c10090acdf4b5376e7e05216aeef4458
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,288 @@@@
+++package main
+++
+++import (
+++     "bytes"
+++     "encoding/json"
+++     "fmt"
+++     "io"
+++     "io/ioutil"
+++     "net/http"
+++     "os"
+++     "strings"
+++
+++     flag "github.com/docker/docker/pkg/mflag"
+++     "github.com/docker/docker/pkg/parsers"
+++     "github.com/docker/docker/pkg/reexec"
+++
+++     "github.com/Sirupsen/logrus"
+++     "github.com/docker/docker/pkg/term"
+++     "github.com/docker/libnetwork"
+++     "github.com/docker/libnetwork/api"
+++     "github.com/docker/libnetwork/client"
+++     "github.com/docker/libnetwork/config"
+++     "github.com/docker/libnetwork/netlabel"
+++     "github.com/docker/libnetwork/options"
+++     "github.com/gorilla/mux"
+++)
+++
+++const (
+++     // DefaultHTTPHost is used if only port is provided to -H flag e.g. docker -d -H tcp://:8080
+++     DefaultHTTPHost = "127.0.0.1"
+++     // DefaultHTTPPort is the default http port used by dnet
+++     DefaultHTTPPort = 2385
+++     // DefaultUnixSocket exported
+++     DefaultUnixSocket = "/var/run/dnet.sock"
+++     cfgFileEnv        = "LIBNETWORK_CFG"
+++     defaultCfgFile    = "/etc/default/libnetwork.toml"
+++)
+++
+++func main() {
+++     if reexec.Init() {
+++             return
+++     }
+++
+++     _, stdout, stderr := term.StdStreams()
+++     logrus.SetOutput(stderr)
+++
+++     err := dnetCommand(stdout, stderr)
+++     if err != nil {
+++             os.Exit(1)
+++     }
+++}
+++
+++func parseConfig(cfgFile string) (*config.Config, error) {
+++     if strings.Trim(cfgFile, " ") == "" {
+++             cfgFile = os.Getenv(cfgFileEnv)
+++             if strings.Trim(cfgFile, " ") == "" {
+++                     cfgFile = defaultCfgFile
+++             }
+++     }
+++     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 strings.TrimSpace(cfg.Datastore.Client.Provider) != "" {
+++             options = append(options, config.OptionKVProvider(cfg.Datastore.Client.Provider))
+++     }
+++     if strings.TrimSpace(cfg.Datastore.Client.Address) != "" {
+++             options = append(options, config.OptionKVProviderURL(cfg.Datastore.Client.Address))
+++     }
+++     return options
+++}
+++
+++func dnetCommand(stdout, stderr io.Writer) error {
+++     flag.Parse()
+++
+++     if *flHelp {
+++             flag.Usage()
+++             return nil
+++     }
+++
+++     if *flLogLevel != "" {
+++             lvl, err := logrus.ParseLevel(*flLogLevel)
+++             if err != nil {
+++                     fmt.Fprintf(stderr, "Unable to parse logging level: %s\n", *flLogLevel)
+++                     return err
+++             }
+++             logrus.SetLevel(lvl)
+++     } else {
+++             logrus.SetLevel(logrus.InfoLevel)
+++     }
+++
+++     if *flDebug {
+++             logrus.SetLevel(logrus.DebugLevel)
+++     }
+++
+++     if *flHost == "" {
+++             defaultHost := os.Getenv("DNET_HOST")
+++             if defaultHost == "" {
+++                     // TODO : Add UDS support
+++                     defaultHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort)
+++             }
+++             *flHost = defaultHost
+++     }
+++
+++     dc, err := newDnetConnection(*flHost)
+++     if err != nil {
+++             if *flDaemon {
+++                     logrus.Error(err)
+++             } else {
+++                     fmt.Fprint(stderr, err)
+++             }
+++             return err
+++     }
+++
+++     if *flDaemon {
+++             err := dc.dnetDaemon()
+++             if err != nil {
+++                     logrus.Errorf("dnet Daemon exited with an error : %v", err)
+++             }
+++             return err
+++     }
+++
+++     cli := client.NewNetworkCli(stdout, stderr, dc.httpCall)
+++     if err := cli.Cmd("dnet", flag.Args()...); err != nil {
+++             fmt.Fprintln(stderr, err)
+++             return err
+++     }
+++     return nil
+++}
+++
+++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]interface{}{
+++                             "BridgeName":            nw,
+++                             "AllowNonDefaultBridge": "true",
+++                     }
+++                     networkOption := libnetwork.NetworkOptionGeneric(genericOption)
+++                     createOptions = append(createOptions, networkOption)
+++             }
+++             _, 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
+++}
+++
+++func (d *dnetConnection) dnetDaemon() error {
+++     cfg, err := parseConfig(*flCfgFile)
+++     var cOptions []config.Option
+++     if err == nil {
+++             cOptions = processConfig(cfg)
+++     }
+++     controller, err := libnetwork.New(cOptions...)
+++     if err != nil {
+++             fmt.Println("Error starting dnetDaemon :", err)
+++             return err
+++     }
+++     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)
+++     return http.ListenAndServe(d.addr, r)
+++}
+++
+++func newDnetConnection(val string) (*dnetConnection, error) {
+++     url, err := parsers.ParseHost(DefaultHTTPHost, DefaultUnixSocket, val)
+++     if err != nil {
+++             return nil, err
+++     }
+++     protoAddrParts := strings.SplitN(url, "://", 2)
+++     if len(protoAddrParts) != 2 {
+++             return nil, fmt.Errorf("bad format, expected tcp://ADDR")
+++     }
+++     if strings.ToLower(protoAddrParts[0]) != "tcp" {
+++             return nil, fmt.Errorf("dnet currently only supports tcp transport")
+++     }
+++
+++     return &dnetConnection{protoAddrParts[0], protoAddrParts[1]}, nil
+++}
+++
+++func (d *dnetConnection) httpCall(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, 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, fmt.Sprintf("%s", 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")
+++     }
+++
+++     if headers != nil {
+++             for k, v := range headers {
+++                     req.Header[k] = v
+++             }
+++     }
+++}
+++
+++func encodeData(data interface{}) (*bytes.Buffer, error) {
+++     params := bytes.NewBuffer(nil)
+++     if data != nil {
+++             if err := json.NewEncoder(params).Encode(data); err != nil {
+++                     return nil, err
+++             }
+++     }
+++     return params, nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b8466f1a707d7daae0cb05a0caf13a13bb405a7a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,132 @@@@
+++package main
+++
+++import (
+++     "fmt"
+++     "io/ioutil"
+++     "os"
+++     "testing"
+++     "time"
+++
+++     "github.com/docker/libnetwork/netutils"
+++)
+++
+++const dnetCommandName = "dnet"
+++
+++var origStdOut = os.Stdout
+++
+++func TestDnetDaemonCustom(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             t.Skip("This test must run inside a container ")
+++     }
+++     customPort := 4567
+++     doneChan := make(chan bool)
+++     go func() {
+++             args := []string{dnetCommandName, "-d", fmt.Sprintf("-H=:%d", customPort)}
+++             executeDnetCommand(t, args, true)
+++             doneChan <- true
+++     }()
+++
+++     select {
+++     case <-doneChan:
+++             t.Fatal("dnet Daemon is not supposed to exit")
+++     case <-time.After(3 * time.Second):
+++             args := []string{dnetCommandName, "-d=false", fmt.Sprintf("-H=:%d", customPort), "-D", "network", "ls"}
+++             executeDnetCommand(t, args, true)
+++     }
+++}
+++
+++func TestDnetDaemonInvalidCustom(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             t.Skip("This test must run inside a container ")
+++     }
+++     customPort := 4668
+++     doneChan := make(chan bool)
+++     go func() {
+++             args := []string{dnetCommandName, "-d=true", fmt.Sprintf("-H=:%d", customPort)}
+++             executeDnetCommand(t, args, true)
+++             doneChan <- true
+++     }()
+++
+++     select {
+++     case <-doneChan:
+++             t.Fatal("dnet Daemon is not supposed to exit")
+++     case <-time.After(3 * time.Second):
+++             args := []string{dnetCommandName, "-d=false", "-H=:6669", "-D", "network", "ls"}
+++             executeDnetCommand(t, args, false)
+++     }
+++}
+++
+++func TestDnetDaemonInvalidParams(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             t.Skip("This test must run inside a container ")
+++     }
+++     args := []string{dnetCommandName, "-d=false", "-H=tcp:/127.0.0.1:8080"}
+++     executeDnetCommand(t, args, false)
+++
+++     args = []string{dnetCommandName, "-d=false", "-H=unix://var/run/dnet.sock"}
+++     executeDnetCommand(t, args, false)
+++
+++     args = []string{dnetCommandName, "-d=false", "-H=", "-l=invalid"}
+++     executeDnetCommand(t, args, false)
+++
+++     args = []string{dnetCommandName, "-d=false", "-H=", "-l=error", "invalid"}
+++     executeDnetCommand(t, args, false)
+++}
+++
+++func TestDnetDefaultsWithFlags(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             t.Skip("This test must run inside a container ")
+++     }
+++     doneChan := make(chan bool)
+++     go func() {
+++             args := []string{dnetCommandName, "-d=true", "-H=", "-l=error"}
+++             executeDnetCommand(t, args, true)
+++             doneChan <- true
+++     }()
+++
+++     select {
+++     case <-doneChan:
+++             t.Fatal("dnet Daemon is not supposed to exit")
+++     case <-time.After(3 * time.Second):
+++             args := []string{dnetCommandName, "-d=false", "network", "create", "-d=null", "test"}
+++             executeDnetCommand(t, args, true)
+++
+++             args = []string{dnetCommandName, "-d=false", "-D", "network", "ls"}
+++             executeDnetCommand(t, args, true)
+++     }
+++}
+++
+++func TestDnetMain(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             t.Skip("This test must run inside a container ")
+++     }
+++     customPort := 4568
+++     doneChan := make(chan bool)
+++     go func() {
+++             args := []string{dnetCommandName, "-d=true", "-h=false", fmt.Sprintf("-H=:%d", customPort)}
+++             os.Args = args
+++             main()
+++             doneChan <- true
+++     }()
+++     select {
+++     case <-doneChan:
+++             t.Fatal("dnet Daemon is not supposed to exit")
+++     case <-time.After(2 * time.Second):
+++     }
+++}
+++
+++func executeDnetCommand(t *testing.T, args []string, shouldSucced bool) {
+++     _, w, _ := os.Pipe()
+++     os.Stdout = w
+++
+++     os.Args = args
+++     err := dnetCommand(ioutil.Discard, ioutil.Discard)
+++     if shouldSucced && err != nil {
+++             os.Stdout = origStdOut
+++             t.Fatalf("cli [%v] must succeed, but failed with an error :  %v", args, err)
+++     } else if !shouldSucced && err == nil {
+++             os.Stdout = origStdOut
+++             t.Fatalf("cli [%v] must fail, but succeeded with an error :  %v", args, err)
+++     }
+++     os.Stdout = origStdOut
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..27dfbd19ed64c5877045eb8a13c1ca4f0f752566
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,51 @@@@
+++package main
+++
+++import (
+++     "fmt"
+++     "os"
+++
+++     flag "github.com/docker/docker/pkg/mflag"
+++)
+++
+++type command struct {
+++     name        string
+++     description string
+++}
+++
+++type byName []command
+++
+++var (
+++     flDaemon   = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode")
+++     flHost     = flag.String([]string{"H", "-host"}, "", "Daemon socket to connect to")
+++     flLogLevel = flag.String([]string{"l", "-log-level"}, "info", "Set the logging level")
+++     flDebug    = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode")
+++     flCfgFile  = flag.String([]string{"c", "-cfg-file"}, "/etc/default/libnetwork.toml", "Configuration file")
+++     flHelp     = flag.Bool([]string{"h", "-help"}, false, "Print usage")
+++
+++     dnetCommands = []command{
+++             {"network", "Network management commands"},
+++             {"service", "Service management commands"},
+++     }
+++)
+++
+++func init() {
+++     flag.Usage = func() {
+++             fmt.Fprint(os.Stdout, "Usage: dnet [OPTIONS] COMMAND [arg...]\n\nA self-sufficient runtime for container networking.\n\nOptions:\n")
+++
+++             flag.CommandLine.SetOutput(os.Stdout)
+++             flag.PrintDefaults()
+++
+++             help := "\nCommands:\n"
+++
+++             for _, cmd := range dnetCommands {
+++                     help += fmt.Sprintf("    %-10.10s%s\n", cmd.name, cmd.description)
+++             }
+++
+++             help += "\nRun 'dnet COMMAND --help' for more information on a command."
+++             fmt.Fprintf(os.Stdout, "%s\n", help)
+++     }
+++}
+++
+++func printUsage() {
+++     fmt.Println("Usage: dnet <OPTIONS> COMMAND [arg...]")
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4e22516d131caee530c29f3fe8f1c576ca7c675f
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,12 @@@@
+++title = "LibNetwork Configuration file"
+++
+++[daemon]
+++  debug = false
+++[cluster]
+++  discovery = "token://22aa23948f4f6b31230687689636959e"
+++  Address = "1.1.1.1"
+++[datastore]
+++  embedded = false
+++[datastore.client]
+++  provider = "consul"
+++  Address = "localhost:8500"
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a75b55067c849577b07b03dd9f78677a7ea15a81
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,149 @@@@
+++package main
+++
+++import (
+++     "fmt"
+++     "net"
+++     "os"
+++     "os/signal"
+++
+++     "github.com/docker/docker/pkg/reexec"
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/drivers/overlay"
+++     "github.com/docker/libnetwork/netlabel"
+++     "github.com/docker/libnetwork/types"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++type router struct {
+++     d driverapi.Driver
+++}
+++
+++type endpoint struct {
+++     addr net.IPNet
+++     mac  net.HardwareAddr
+++     name string
+++     id   int
+++}
+++
+++func (r *router) RegisterDriver(name string, driver driverapi.Driver, c driverapi.Capability) error {
+++     r.d = driver
+++     return nil
+++}
+++
+++func (ep *endpoint) Interfaces() []driverapi.InterfaceInfo {
+++     return nil
+++}
+++
+++func (ep *endpoint) AddInterface(ID int, mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error {
+++     ep.id = ID
+++     ep.addr = ipv4
+++     ep.mac = mac
+++     return nil
+++}
+++
+++func (ep *endpoint) InterfaceNames() []driverapi.InterfaceNameInfo {
+++     return []driverapi.InterfaceNameInfo{ep}
+++
+++}
+++
+++func (ep *endpoint) SetNames(srcName, dstPrefix string) error {
+++     ep.name = srcName
+++     return nil
+++}
+++
+++func (ep *endpoint) ID() int {
+++     return ep.id
+++}
+++
+++func (ep *endpoint) SetGateway(net.IP) error {
+++     return nil
+++}
+++
+++func (ep *endpoint) SetGatewayIPv6(net.IP) error {
+++     return nil
+++}
+++
+++func (ep *endpoint) AddStaticRoute(destination *net.IPNet, routeType int,
+++     nextHop net.IP, interfaceID int) error {
+++     return nil
+++}
+++
+++func (ep *endpoint) SetHostsPath(string) error {
+++     return nil
+++}
+++
+++func (ep *endpoint) SetResolvConfPath(string) error {
+++     return nil
+++}
+++
+++func main() {
+++     if reexec.Init() {
+++             return
+++     }
+++
+++     r := &router{}
+++     if err := overlay.Init(r); err != nil {
+++             fmt.Printf("Failed to initialize overlay driver: %v\n", err)
+++             os.Exit(1)
+++     }
+++
+++     opt := make(map[string]interface{})
+++     if len(os.Args) > 1 {
+++             opt[netlabel.OverlayBindInterface] = os.Args[1]
+++     }
+++     if len(os.Args) > 2 {
+++             opt[netlabel.OverlayNeighborIP] = os.Args[2]
+++     }
+++     if len(os.Args) > 3 {
+++             opt[netlabel.KVProvider] = os.Args[3]
+++     }
+++     if len(os.Args) > 4 {
+++             opt[netlabel.KVProviderURL] = os.Args[4]
+++     }
+++
+++     r.d.Config(opt)
+++
+++     if err := r.d.CreateNetwork(types.UUID("testnetwork"),
+++             map[string]interface{}{}); err != nil {
+++             fmt.Printf("Failed to create network in the driver: %v\n", err)
+++             os.Exit(1)
+++     }
+++
+++     ep := &endpoint{}
+++     if err := r.d.CreateEndpoint(types.UUID("testnetwork"), types.UUID("testep"),
+++             ep, map[string]interface{}{}); err != nil {
+++             fmt.Printf("Failed to create endpoint in the driver: %v\n", err)
+++             os.Exit(1)
+++     }
+++
+++     if err := r.d.Join(types.UUID("testnetwork"), types.UUID("testep"),
+++             "", ep, map[string]interface{}{}); err != nil {
+++             fmt.Printf("Failed to join an endpoint in the driver: %v\n", err)
+++             os.Exit(1)
+++     }
+++
+++     link, err := netlink.LinkByName(ep.name)
+++     if err != nil {
+++             fmt.Printf("Failed to find the container interface with name %s: %v\n",
+++                     ep.name, err)
+++             os.Exit(1)
+++     }
+++
+++     ipAddr := &netlink.Addr{IPNet: &ep.addr, Label: ""}
+++     if err := netlink.AddrAdd(link, ipAddr); err != nil {
+++             fmt.Printf("Failed to add address to the interface: %v\n", err)
+++             os.Exit(1)
+++     }
+++
+++     sigCh := make(chan os.Signal, 1)
+++     signal.Notify(sigCh, os.Interrupt, os.Kill)
+++
+++     for {
+++             select {
+++             case <-sigCh:
+++                     r.d.Leave(types.UUID("testnetwork"), types.UUID("testep"))
+++                     overlay.Fini(r.d)
+++                     os.Exit(0)
+++             }
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..669b517f439f9802230bbdad901dbe983f517daf
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,65 @@@@
+++package main
+++
+++import (
+++     "fmt"
+++
+++     "github.com/docker/libnetwork"
+++     "github.com/docker/libnetwork/netlabel"
+++     "github.com/docker/libnetwork/options"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++func main() {
+++     // Create a new controller instance
+++     controller, err := libnetwork.New()
+++     if err != nil {
+++             return
+++     }
+++
+++     // Select and configure the network driver
+++     networkType := "bridge"
+++
+++     driverOptions := options.Generic{}
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = driverOptions
+++     err = controller.ConfigureNetworkDriver(networkType, genericOption)
+++     if err != nil {
+++             return
+++     }
+++
+++     // Create a network for containers to join.
+++     // NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can make of
+++     network, err := controller.NewNetwork(networkType, "network1")
+++     if err != nil {
+++             return
+++     }
+++
+++     // For each new container: allocate IP and interfaces. The returned network
+++     // settings will be used for container infos (inspect and such), as well as
+++     // iptables rules for port publishing. This info is contained or accessible
+++     // from the returned endpoint.
+++     ep, err := network.CreateEndpoint("Endpoint1")
+++     if err != nil {
+++             return
+++     }
+++
+++     // A container can join the endpoint by providing the container ID to the join
+++     // api.
+++     // Join accepts Variadic arguments which will be made use of by libnetwork and Drivers
+++     err = ep.Join("container1",
+++             libnetwork.JoinOptionHostname("test"),
+++             libnetwork.JoinOptionDomainname("docker.io"))
+++     if err != nil {
+++             return
+++     }
+++
+++     // libnetwork client can check the endpoint's operational data via the Info() API
+++     epInfo, err := ep.DriverInfo()
+++     mapData, ok := epInfo[netlabel.PortMap]
+++     if ok {
+++             portMapping, ok := mapData.([]types.PortBinding)
+++             if ok {
+++                     fmt.Printf("Current port mapping for endpoint %s: %v", ep.Name(), portMapping)
+++             }
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4e22516d131caee530c29f3fe8f1c576ca7c675f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,12 @@@@
+++title = "LibNetwork Configuration file"
+++
+++[daemon]
+++  debug = false
+++[cluster]
+++  discovery = "token://22aa23948f4f6b31230687689636959e"
+++  Address = "1.1.1.1"
+++[datastore]
+++  embedded = false
+++[datastore.client]
+++  provider = "consul"
+++  Address = "localhost:8500"
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..957a0cdab5e5dac23aaa8b4798778b65f68d192c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,49 @@@@
+++package main
+++
+++import (
+++     "fmt"
+++     "net"
+++     "time"
+++
+++     log "github.com/Sirupsen/logrus"
+++
+++     "github.com/docker/libnetwork"
+++     "github.com/docker/libnetwork/options"
+++)
+++
+++func main() {
+++     log.SetLevel(log.DebugLevel)
+++     controller, err := libnetwork.New()
+++     if err != nil {
+++             log.Fatal(err)
+++     }
+++
+++     netType := "null"
+++     ip, net, _ := net.ParseCIDR("192.168.100.1/24")
+++     net.IP = ip
+++     options := options.Generic{"AddressIPv4": net}
+++
+++     err = controller.ConfigureNetworkDriver(netType, options)
+++     for i := 0; i < 10; i++ {
+++             netw, err := controller.NewNetwork(netType, fmt.Sprintf("Gordon-%d", i))
+++             if err != nil {
+++                     if _, ok := err.(libnetwork.NetworkNameError); !ok {
+++                             log.Fatal(err)
+++                     }
+++             } else {
+++                     fmt.Println("Network Created Successfully :", netw)
+++             }
+++             netw, _ = controller.NetworkByName(fmt.Sprintf("Gordon-%d", i))
+++             _, err = netw.CreateEndpoint(fmt.Sprintf("Gordon-Ep-%d", i), nil)
+++             if err != nil {
+++                     log.Fatalf("Error creating endpoint 1 %v", err)
+++             }
+++
+++             _, err = netw.CreateEndpoint(fmt.Sprintf("Gordon-Ep2-%d", i), nil)
+++             if err != nil {
+++                     log.Fatalf("Error creating endpoint 2 %v", err)
+++             }
+++
+++             time.Sleep(2 * time.Second)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..bb93d981b1ba517dc2dfcef0d3c67cdc7b98496b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,116 @@@@
+++package config
+++
+++import (
+++     "strings"
+++
+++     "github.com/BurntSushi/toml"
+++     log "github.com/Sirupsen/logrus"
+++     "github.com/docker/libnetwork/netlabel"
+++)
+++
+++// Config encapsulates configurations of various Libnetwork components
+++type Config struct {
+++     Daemon    DaemonCfg
+++     Cluster   ClusterCfg
+++     Datastore DatastoreCfg
+++}
+++
+++// DaemonCfg represents libnetwork core configuration
+++type DaemonCfg struct {
+++     Debug          bool
+++     DefaultNetwork string
+++     DefaultDriver  string
+++     Labels         []string
+++}
+++
+++// ClusterCfg represents cluster configuration
+++type ClusterCfg struct {
+++     Discovery string
+++     Address   string
+++     Heartbeat uint64
+++}
+++
+++// DatastoreCfg represents Datastore configuration.
+++type DatastoreCfg struct {
+++     Embedded bool
+++     Client   DatastoreClientCfg
+++}
+++
+++// DatastoreClientCfg represents Datastore Client-only mode configuration
+++type DatastoreClientCfg struct {
+++     Provider string
+++     Address  string
+++}
+++
+++// ParseConfig parses the libnetwork configuration file
+++func ParseConfig(tomlCfgFile string) (*Config, error) {
+++     var cfg Config
+++     if _, err := toml.DecodeFile(tomlCfgFile, &cfg); err != nil {
+++             return nil, err
+++     }
+++     return &cfg, nil
+++}
+++
+++// Option is a option setter function type used to pass varios configurations
+++// to the controller
+++type Option func(c *Config)
+++
+++// OptionDefaultNetwork function returns an option setter for a default network
+++func OptionDefaultNetwork(dn string) Option {
+++     return func(c *Config) {
+++             log.Infof("Option DefaultNetwork: %s", dn)
+++             c.Daemon.DefaultNetwork = strings.TrimSpace(dn)
+++     }
+++}
+++
+++// OptionDefaultDriver function returns an option setter for default driver
+++func OptionDefaultDriver(dd string) Option {
+++     return func(c *Config) {
+++             log.Infof("Option DefaultDriver: %s", dd)
+++             c.Daemon.DefaultDriver = strings.TrimSpace(dd)
+++     }
+++}
+++
+++// OptionLabels function returns an option setter for labels
+++func OptionLabels(labels []string) Option {
+++     return func(c *Config) {
+++             for _, label := range labels {
+++                     if strings.HasPrefix(label, netlabel.Prefix) {
+++                             c.Daemon.Labels = append(c.Daemon.Labels, label)
+++                     }
+++             }
+++     }
+++}
+++
+++// OptionKVProvider function returns an option setter for kvstore provider
+++func OptionKVProvider(provider string) Option {
+++     return func(c *Config) {
+++             log.Infof("Option OptionKVProvider: %s", provider)
+++             c.Datastore.Client.Provider = strings.TrimSpace(provider)
+++     }
+++}
+++
+++// OptionKVProviderURL function returns an option setter for kvstore url
+++func OptionKVProviderURL(url string) Option {
+++     return func(c *Config) {
+++             log.Infof("Option OptionKVProviderURL: %s", url)
+++             c.Datastore.Client.Address = strings.TrimSpace(url)
+++     }
+++}
+++
+++// ProcessOptions processes options and stores it in config
+++func (c *Config) ProcessOptions(options ...Option) {
+++     for _, opt := range options {
+++             if opt != nil {
+++                     opt(c)
+++             }
+++     }
+++}
+++
+++// IsValidName validates configuration objects supported by libnetwork
+++func IsValidName(name string) bool {
+++     if name == "" || strings.Contains(name, ".") {
+++             return false
+++     }
+++     return true
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..cc8a911c948ffe06ad7d5635642aa0b6bcded756
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,55 @@@@
+++package config
+++
+++import (
+++     "strings"
+++     "testing"
+++
+++     "github.com/docker/libnetwork/netlabel"
+++     _ "github.com/docker/libnetwork/netutils"
+++)
+++
+++func TestInvalidConfig(t *testing.T) {
+++     _, err := ParseConfig("invalid.toml")
+++     if err == nil {
+++             t.Fatal("Invalid Configuration file must fail")
+++     }
+++}
+++
+++func TestConfig(t *testing.T) {
+++     _, err := ParseConfig("libnetwork.toml")
+++     if err != nil {
+++             t.Fatal("Error parsing a valid configuration file :", err)
+++     }
+++}
+++
+++func TestOptionsLabels(t *testing.T) {
+++     c := &Config{}
+++     l := []string{
+++             "com.docker.network.key1=value1",
+++             "com.docker.storage.key1=value1",
+++             "com.docker.network.driver.key1=value1",
+++             "com.docker.network.driver.key2=value2",
+++     }
+++     f := OptionLabels(l)
+++     f(c)
+++     if len(c.Daemon.Labels) != 3 {
+++             t.Fatalf("Expecting 3 labels, seen %d", len(c.Daemon.Labels))
+++     }
+++     for _, l := range c.Daemon.Labels {
+++             if !strings.HasPrefix(l, netlabel.Prefix) {
+++                     t.Fatalf("config must accept only libnetwork labels. Not : %s", l)
+++             }
+++     }
+++}
+++
+++func TestValidName(t *testing.T) {
+++     if !IsValidName("test") {
+++             t.Fatal("Name validation fails for a name that must be accepted")
+++     }
+++     if IsValidName("") {
+++             t.Fatal("Name validation succeeds for a case when it is expected to fail")
+++     }
+++     if IsValidName("name.with.dots") {
+++             t.Fatal("Name validation succeeds for a case when it is expected to fail")
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..93a2ff4756fcbffe6c5701bbd09345cccb41d576
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,12 @@@@
+++title = "LibNetwork Configuration file"
+++
+++[daemon]
+++  debug = false
+++[cluster]
+++  discovery = "token://swarm-discovery-token"
+++  Address = "Cluster-wide reachable Host IP"
+++[datastore]
+++  embedded = false
+++[datastore.client]
+++  provider = "consul"
+++  Address = "localhost:8500"
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..02a9f7eb2a8a3188484c057d203645f5c1d33b78
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,399 @@@@
+++/*
+++Package libnetwork provides the basic functionality and extension points to
+++create network namespaces and allocate interfaces for containers to use.
+++
+++     // Create a new controller instance
+++     controller, _err := libnetwork.New(nil)
+++
+++     // Select and configure the network driver
+++     networkType := "bridge"
+++
+++     driverOptions := options.Generic{}
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = driverOptions
+++     err := controller.ConfigureNetworkDriver(networkType, genericOption)
+++     if err != nil {
+++             return
+++     }
+++
+++     // Create a network for containers to join.
+++     // NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can make of
+++     network, err := controller.NewNetwork(networkType, "network1")
+++     if err != nil {
+++             return
+++     }
+++
+++     // For each new container: allocate IP and interfaces. The returned network
+++     // settings will be used for container infos (inspect and such), as well as
+++     // iptables rules for port publishing. This info is contained or accessible
+++     // from the returned endpoint.
+++     ep, err := network.CreateEndpoint("Endpoint1")
+++     if err != nil {
+++             return
+++     }
+++
+++     // A container can join the endpoint by providing the container ID to the join
+++     // api.
+++     // Join accepts Variadic arguments which will be made use of by libnetwork and Drivers
+++     err = ep.Join("container1",
+++             libnetwork.JoinOptionHostname("test"),
+++             libnetwork.JoinOptionDomainname("docker.io"))
+++     if err != nil {
+++             return
+++     }
+++*/
+++package libnetwork
+++
+++import (
+++     "fmt"
+++     "net"
+++     "strings"
+++     "sync"
+++
+++     log "github.com/Sirupsen/logrus"
+++     "github.com/docker/docker/pkg/plugins"
+++     "github.com/docker/docker/pkg/stringid"
+++     "github.com/docker/libnetwork/config"
+++     "github.com/docker/libnetwork/datastore"
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/hostdiscovery"
+++     "github.com/docker/libnetwork/netlabel"
+++     "github.com/docker/libnetwork/sandbox"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++// NetworkController provides the interface for controller instance which manages
+++// networks.
+++type NetworkController interface {
+++     // ConfigureNetworkDriver applies the passed options to the driver instance for the specified network type
+++     ConfigureNetworkDriver(networkType string, options map[string]interface{}) error
+++
+++     // Config method returns the bootup configuration for the controller
+++     Config() config.Config
+++
+++     // Create a new network. The options parameter carries network specific options.
+++     // Labels support will be added in the near future.
+++     NewNetwork(networkType, name string, options ...NetworkOption) (Network, error)
+++
+++     // Networks returns the list of Network(s) managed by this controller.
+++     Networks() []Network
+++
+++     // WalkNetworks uses the provided function to walk the Network(s) managed by this controller.
+++     WalkNetworks(walker NetworkWalker)
+++
+++     // NetworkByName returns the Network which has the passed name. If not found, the error ErrNoSuchNetwork is returned.
+++     NetworkByName(name string) (Network, error)
+++
+++     // NetworkByID returns the Network which has the passed id. If not found, the error ErrNoSuchNetwork is returned.
+++     NetworkByID(id string) (Network, error)
+++
+++     // LeaveAll accepts a container id and attempts to leave all endpoints that the container has joined
+++     LeaveAll(id string) error
+++
+++     // GC triggers immediate garbage collection of resources which are garbage collected.
+++     GC()
+++}
+++
+++// NetworkWalker is a client provided function which will be used to walk the Networks.
+++// When the function returns true, the walk will stop.
+++type NetworkWalker func(nw Network) bool
+++
+++type driverData struct {
+++     driver     driverapi.Driver
+++     capability driverapi.Capability
+++}
+++
+++type driverTable map[string]*driverData
+++type networkTable map[types.UUID]*network
+++type endpointTable map[types.UUID]*endpoint
+++type sandboxTable map[string]*sandboxData
+++
+++type controller struct {
+++     networks  networkTable
+++     drivers   driverTable
+++     sandboxes sandboxTable
+++     cfg       *config.Config
+++     store     datastore.DataStore
+++     sync.Mutex
+++}
+++
+++// New creates a new instance of network controller.
+++func New(cfgOptions ...config.Option) (NetworkController, error) {
+++     var cfg *config.Config
+++     if len(cfgOptions) > 0 {
+++             cfg = &config.Config{}
+++             cfg.ProcessOptions(cfgOptions...)
+++     }
+++     c := &controller{
+++             cfg:       cfg,
+++             networks:  networkTable{},
+++             sandboxes: sandboxTable{},
+++             drivers:   driverTable{}}
+++     if err := initDrivers(c); err != nil {
+++             return nil, err
+++     }
+++
+++     if cfg != nil {
+++             if err := c.initDataStore(); err != nil {
+++                     // Failing to initalize datastore is a bad situation to be in.
+++                     // But it cannot fail creating the Controller
+++                     log.Debugf("Failed to Initialize Datastore due to %v. Operating in non-clustered mode", err)
+++             }
+++             if err := c.initDiscovery(); err != nil {
+++                     // Failing to initalize discovery is a bad situation to be in.
+++                     // But it cannot fail creating the Controller
+++                     log.Debugf("Failed to Initialize Discovery : %v", err)
+++             }
+++     }
+++
+++     return c, nil
+++}
+++
+++func (c *controller) validateHostDiscoveryConfig() bool {
+++     if c.cfg == nil || c.cfg.Cluster.Discovery == "" || c.cfg.Cluster.Address == "" {
+++             return false
+++     }
+++     return true
+++}
+++
+++func (c *controller) initDiscovery() error {
+++     if c.cfg == nil {
+++             return fmt.Errorf("discovery initialization requires a valid configuration")
+++     }
+++
+++     hostDiscovery := hostdiscovery.NewHostDiscovery()
+++     return hostDiscovery.StartDiscovery(&c.cfg.Cluster, c.hostJoinCallback, c.hostLeaveCallback)
+++}
+++
+++func (c *controller) hostJoinCallback(hosts []net.IP) {
+++}
+++
+++func (c *controller) hostLeaveCallback(hosts []net.IP) {
+++}
+++
+++func (c *controller) Config() config.Config {
+++     c.Lock()
+++     defer c.Unlock()
+++     if c.cfg == nil {
+++             return config.Config{}
+++     }
+++     return *c.cfg
+++}
+++
+++func (c *controller) ConfigureNetworkDriver(networkType string, options map[string]interface{}) error {
+++     c.Lock()
+++     dd, ok := c.drivers[networkType]
+++     c.Unlock()
+++     if !ok {
+++             return NetworkTypeError(networkType)
+++     }
+++     return dd.driver.Config(options)
+++}
+++
+++func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver, capability driverapi.Capability) error {
+++     c.Lock()
+++     if !config.IsValidName(networkType) {
+++             c.Unlock()
+++             return ErrInvalidName(networkType)
+++     }
+++     if _, ok := c.drivers[networkType]; ok {
+++             c.Unlock()
+++             return driverapi.ErrActiveRegistration(networkType)
+++     }
+++     c.drivers[networkType] = &driverData{driver, capability}
+++
+++     if c.cfg == nil {
+++             c.Unlock()
+++             return nil
+++     }
+++
+++     opt := make(map[string]interface{})
+++     for _, label := range c.cfg.Daemon.Labels {
+++             if strings.HasPrefix(label, netlabel.DriverPrefix+"."+networkType) {
+++                     opt[netlabel.Key(label)] = netlabel.Value(label)
+++             }
+++     }
+++
+++     if capability.Scope == driverapi.GlobalScope && c.validateDatastoreConfig() {
+++             opt[netlabel.KVProvider] = c.cfg.Datastore.Client.Provider
+++             opt[netlabel.KVProviderURL] = c.cfg.Datastore.Client.Address
+++     }
+++
+++     c.Unlock()
+++
+++     if len(opt) != 0 {
+++             if err := driver.Config(opt); err != nil {
+++                     return err
+++             }
+++     }
+++
+++     return nil
+++}
+++
+++// NewNetwork creates a new network of the specified network type. The options
+++// are network specific and modeled in a generic way.
+++func (c *controller) NewNetwork(networkType, name string, options ...NetworkOption) (Network, error) {
+++     if !config.IsValidName(name) {
+++             return nil, ErrInvalidName(name)
+++     }
+++     // Check if a network already exists with the specified network name
+++     c.Lock()
+++     for _, n := range c.networks {
+++             if n.name == name {
+++                     c.Unlock()
+++                     return nil, NetworkNameError(name)
+++             }
+++     }
+++     c.Unlock()
+++
+++     // Construct the network object
+++     network := &network{
+++             name:        name,
+++             networkType: networkType,
+++             id:          types.UUID(stringid.GenerateRandomID()),
+++             ctrlr:       c,
+++             endpoints:   endpointTable{},
+++     }
+++
+++     network.processOptions(options...)
+++
+++     if err := c.addNetwork(network); err != nil {
+++             return nil, err
+++     }
+++
+++     if err := c.updateNetworkToStore(network); err != nil {
+++             log.Warnf("couldnt create network %s: %v", network.name, err)
+++             if e := network.Delete(); e != nil {
+++                     log.Warnf("couldnt cleanup network %s: %v", network.name, err)
+++             }
+++             return nil, err
+++     }
+++
+++     return network, nil
+++}
+++
+++func (c *controller) addNetwork(n *network) error {
+++
+++     c.Lock()
+++     // Check if a driver for the specified network type is available
+++     dd, ok := c.drivers[n.networkType]
+++     c.Unlock()
+++
+++     if !ok {
+++             var err error
+++             dd, err = c.loadDriver(n.networkType)
+++             if err != nil {
+++                     return err
+++             }
+++     }
+++
+++     n.Lock()
+++     n.svcRecords = svcMap{}
+++     n.driver = dd.driver
+++     d := n.driver
+++     n.Unlock()
+++
+++     // Create the network
+++     if err := d.CreateNetwork(n.id, n.generic); err != nil {
+++             return err
+++     }
+++     if err := n.watchEndpoints(); err != nil {
+++             return err
+++     }
+++     c.Lock()
+++     c.networks[n.id] = n
+++     c.Unlock()
+++
+++     return nil
+++}
+++
+++func (c *controller) Networks() []Network {
+++     c.Lock()
+++     defer c.Unlock()
+++
+++     list := make([]Network, 0, len(c.networks))
+++     for _, n := range c.networks {
+++             list = append(list, n)
+++     }
+++
+++     return list
+++}
+++
+++func (c *controller) WalkNetworks(walker NetworkWalker) {
+++     for _, n := range c.Networks() {
+++             if walker(n) {
+++                     return
+++             }
+++     }
+++}
+++
+++func (c *controller) NetworkByName(name string) (Network, error) {
+++     if name == "" {
+++             return nil, ErrInvalidName(name)
+++     }
+++     var n Network
+++
+++     s := func(current Network) bool {
+++             if current.Name() == name {
+++                     n = current
+++                     return true
+++             }
+++             return false
+++     }
+++
+++     c.WalkNetworks(s)
+++
+++     if n == nil {
+++             return nil, ErrNoSuchNetwork(name)
+++     }
+++
+++     return n, nil
+++}
+++
+++func (c *controller) NetworkByID(id string) (Network, error) {
+++     if id == "" {
+++             return nil, ErrInvalidID(id)
+++     }
+++     c.Lock()
+++     defer c.Unlock()
+++     if n, ok := c.networks[types.UUID(id)]; ok {
+++             return n, nil
+++     }
+++     return nil, ErrNoSuchNetwork(id)
+++}
+++
+++func (c *controller) loadDriver(networkType string) (*driverData, error) {
+++     // Plugins pkg performs lazy loading of plugins that acts as remote drivers.
+++     // As per the design, this Get call will result in remote driver discovery if there is a corresponding plugin available.
+++     _, err := plugins.Get(networkType, driverapi.NetworkPluginEndpointType)
+++     if err != nil {
+++             if err == plugins.ErrNotFound {
+++                     return nil, types.NotFoundErrorf(err.Error())
+++             }
+++             return nil, err
+++     }
+++     c.Lock()
+++     defer c.Unlock()
+++     dd, ok := c.drivers[networkType]
+++     if !ok {
+++             return nil, ErrInvalidNetworkDriver(networkType)
+++     }
+++     return dd, nil
+++}
+++
+++func (c *controller) isDriverGlobalScoped(networkType string) (bool, error) {
+++     c.Lock()
+++     dd, ok := c.drivers[networkType]
+++     c.Unlock()
+++     if !ok {
+++             return false, types.NotFoundErrorf("driver not found for %s", networkType)
+++     }
+++     if dd.capability.Scope == driverapi.GlobalScope {
+++             return true, nil
+++     }
+++     return false, nil
+++}
+++
+++func (c *controller) GC() {
+++     sandbox.GC()
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8a195aa902d164333dc264988c73752fd134b64c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,194 @@@@
+++package datastore
+++
+++import (
+++     "reflect"
+++     "strings"
+++
+++     "github.com/docker/libkv"
+++     "github.com/docker/libkv/store"
+++     "github.com/docker/libnetwork/config"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++//DataStore exported
+++type DataStore interface {
+++     // GetObject gets data from datastore and unmarshals to the specified object
+++     GetObject(key string, o KV) error
+++     // PutObject adds a new Record based on an object into the datastore
+++     PutObject(kvObject KV) error
+++     // PutObjectAtomic provides an atomic add and update operation for a Record
+++     PutObjectAtomic(kvObject KV) error
+++     // DeleteObject deletes a record
+++     DeleteObject(kvObject KV) error
+++     // DeleteObjectAtomic performs an atomic delete operation
+++     DeleteObjectAtomic(kvObject KV) error
+++     // DeleteTree deletes a record
+++     DeleteTree(kvObject KV) error
+++     // KVStore returns access to the KV Store
+++     KVStore() store.Store
+++}
+++
+++// ErrKeyModified is raised for an atomic update when the update is working on a stale state
+++var (
+++     ErrKeyModified = store.ErrKeyModified
+++     ErrKeyNotFound = store.ErrKeyNotFound
+++)
+++
+++type datastore struct {
+++     store store.Store
+++}
+++
+++//KV Key Value interface used by objects to be part of the DataStore
+++type KV interface {
+++     // Key method lets an object to provide the Key to be used in KV Store
+++     Key() []string
+++     // KeyPrefix method lets an object to return immediate parent key that can be used for tree walk
+++     KeyPrefix() []string
+++     // Value method lets an object to marshal its content to be stored in the KV store
+++     Value() []byte
+++     // SetValue is used by the datastore to set the object's value when loaded from the data store.
+++     SetValue([]byte) error
+++     // Index method returns the latest DB Index as seen by the object
+++     Index() uint64
+++     // SetIndex method allows the datastore to store the latest DB Index into the object
+++     SetIndex(uint64)
+++     // True if the object exists in the datastore, false if it hasn't been stored yet.
+++     // When SetIndex() is called, the object has been stored.
+++     Exists() bool
+++}
+++
+++const (
+++     // NetworkKeyPrefix is the prefix for network key in the kv store
+++     NetworkKeyPrefix = "network"
+++     // EndpointKeyPrefix is the prefix for endpoint key in the kv store
+++     EndpointKeyPrefix = "endpoint"
+++)
+++
+++var rootChain = []string{"docker", "libnetwork"}
+++
+++//Key provides convenient method to create a Key
+++func Key(key ...string) string {
+++     keychain := append(rootChain, key...)
+++     str := strings.Join(keychain, "/")
+++     return str + "/"
+++}
+++
+++//ParseKey provides convenient method to unpack the key to complement the Key function
+++func ParseKey(key string) ([]string, error) {
+++     chain := strings.Split(strings.Trim(key, "/"), "/")
+++
+++     // The key must atleast be equal to the rootChain in order to be considered as valid
+++     if len(chain) <= len(rootChain) || !reflect.DeepEqual(chain[0:len(rootChain)], rootChain) {
+++             return nil, types.BadRequestErrorf("invalid Key : %s", key)
+++     }
+++     return chain[len(rootChain):], nil
+++}
+++
+++// newClient used to connect to KV Store
+++func newClient(kv string, addrs string) (DataStore, error) {
+++     store, err := libkv.NewStore(store.Backend(kv), []string{addrs}, &store.Config{})
+++     if err != nil {
+++             return nil, err
+++     }
+++     ds := &datastore{store: store}
+++     return ds, nil
+++}
+++
+++// NewDataStore creates a new instance of LibKV data store
+++func NewDataStore(cfg *config.DatastoreCfg) (DataStore, error) {
+++     if cfg == nil {
+++             return nil, types.BadRequestErrorf("invalid configuration passed to datastore")
+++     }
+++     // TODO : cfg.Embedded case
+++     return newClient(cfg.Client.Provider, cfg.Client.Address)
+++}
+++
+++// NewCustomDataStore can be used by clients to plugin cusom datatore that adhers to store.Store
+++func NewCustomDataStore(customStore store.Store) DataStore {
+++     return &datastore{store: customStore}
+++}
+++
+++func (ds *datastore) KVStore() store.Store {
+++     return ds.store
+++}
+++
+++// PutObjectAtomic adds a new Record based on an object into the datastore
+++func (ds *datastore) PutObjectAtomic(kvObject KV) error {
+++     if kvObject == nil {
+++             return types.BadRequestErrorf("invalid KV Object : nil")
+++     }
+++     kvObjValue := kvObject.Value()
+++
+++     if kvObjValue == nil {
+++             return types.BadRequestErrorf("invalid KV Object with a nil Value for key %s", Key(kvObject.Key()...))
+++     }
+++
+++     var previous *store.KVPair
+++     if kvObject.Exists() {
+++             previous = &store.KVPair{Key: Key(kvObject.Key()...), LastIndex: kvObject.Index()}
+++     } else {
+++             previous = nil
+++     }
+++     _, pair, err := ds.store.AtomicPut(Key(kvObject.Key()...), kvObjValue, previous, nil)
+++     if err != nil {
+++             return err
+++     }
+++
+++     kvObject.SetIndex(pair.LastIndex)
+++     return nil
+++}
+++
+++// PutObject adds a new Record based on an object into the datastore
+++func (ds *datastore) PutObject(kvObject KV) error {
+++     if kvObject == nil {
+++             return types.BadRequestErrorf("invalid KV Object : nil")
+++     }
+++     return ds.putObjectWithKey(kvObject, kvObject.Key()...)
+++}
+++
+++func (ds *datastore) putObjectWithKey(kvObject KV, key ...string) error {
+++     kvObjValue := kvObject.Value()
+++
+++     if kvObjValue == nil {
+++             return types.BadRequestErrorf("invalid KV Object with a nil Value for key %s", Key(kvObject.Key()...))
+++     }
+++     return ds.store.Put(Key(key...), kvObjValue, nil)
+++}
+++
+++// GetObject returns a record matching the key
+++func (ds *datastore) GetObject(key string, o KV) error {
+++     kvPair, err := ds.store.Get(key)
+++     if err != nil {
+++             return err
+++     }
+++     err = o.SetValue(kvPair.Value)
+++     if err != nil {
+++             return err
+++     }
+++
+++     // Make sure the object has a correct view of the DB index in case we need to modify it
+++     // and update the DB.
+++     o.SetIndex(kvPair.LastIndex)
+++     return nil
+++}
+++
+++// DeleteObject unconditionally deletes a record from the store
+++func (ds *datastore) DeleteObject(kvObject KV) error {
+++     return ds.store.Delete(Key(kvObject.Key()...))
+++}
+++
+++// DeleteObjectAtomic performs atomic delete on a record
+++func (ds *datastore) DeleteObjectAtomic(kvObject KV) error {
+++     if kvObject == nil {
+++             return types.BadRequestErrorf("invalid KV Object : nil")
+++     }
+++
+++     previous := &store.KVPair{Key: Key(kvObject.Key()...), LastIndex: kvObject.Index()}
+++     _, err := ds.store.AtomicDelete(Key(kvObject.Key()...), previous)
+++     return err
+++}
+++
+++// DeleteTree unconditionally deletes a record from the store
+++func (ds *datastore) DeleteTree(kvObject KV) error {
+++     return ds.store.DeleteTree(Key(kvObject.KeyPrefix()...))
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..397842db10895f2d19cee6cba196ef565ebf2f81
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,241 @@@@
+++package datastore
+++
+++import (
+++     "encoding/json"
+++     "reflect"
+++     "testing"
+++
+++     "github.com/docker/libnetwork/config"
+++     _ "github.com/docker/libnetwork/netutils"
+++     "github.com/docker/libnetwork/options"
+++     "github.com/stretchr/testify/assert"
+++)
+++
+++var dummyKey = "dummy"
+++
+++// NewCustomDataStore can be used by other Tests in order to use custom datastore
+++func NewTestDataStore() DataStore {
+++     return &datastore{store: NewMockStore()}
+++}
+++
+++func TestKey(t *testing.T) {
+++     eKey := []string{"hello", "world"}
+++     sKey := Key(eKey...)
+++     if sKey != "docker/libnetwork/hello/world/" {
+++             t.Fatalf("unexpected key : %s", sKey)
+++     }
+++}
+++
+++func TestParseKey(t *testing.T) {
+++     keySlice, err := ParseKey("/docker/libnetwork/hello/world/")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     eKey := []string{"hello", "world"}
+++     if len(keySlice) < 2 || !reflect.DeepEqual(eKey, keySlice) {
+++             t.Fatalf("unexpected unkey : %s", keySlice)
+++     }
+++}
+++
+++func TestInvalidDataStore(t *testing.T) {
+++     config := &config.DatastoreCfg{}
+++     config.Embedded = false
+++     config.Client.Provider = "invalid"
+++     config.Client.Address = "localhost:8500"
+++     _, err := NewDataStore(config)
+++     if err == nil {
+++             t.Fatal("Invalid Datastore connection configuration must result in a failure")
+++     }
+++}
+++
+++func TestKVObjectFlatKey(t *testing.T) {
+++     store := NewTestDataStore()
+++     expected := dummyKVObject("1000", true)
+++     err := store.PutObject(expected)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     keychain := []string{dummyKey, "1000"}
+++     data, err := store.KVStore().Get(Key(keychain...))
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     var n dummyObject
+++     json.Unmarshal(data.Value, &n)
+++     if n.Name != expected.Name {
+++             t.Fatalf("Dummy object doesn't match the expected object")
+++     }
+++}
+++
+++func TestAtomicKVObjectFlatKey(t *testing.T) {
+++     store := NewTestDataStore()
+++     expected := dummyKVObject("1111", true)
+++     assert.False(t, expected.Exists())
+++     err := store.PutObjectAtomic(expected)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     assert.True(t, expected.Exists())
+++
+++     // PutObjectAtomic automatically sets the Index again. Hence the following must pass.
+++
+++     err = store.PutObjectAtomic(expected)
+++     if err != nil {
+++             t.Fatal("Atomic update should succeed.")
+++     }
+++
+++     // Get the latest index and try PutObjectAtomic again for the same Key
+++     // This must succeed as well
+++     data, err := store.KVStore().Get(Key(expected.Key()...))
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     n := dummyObject{}
+++     json.Unmarshal(data.Value, &n)
+++     n.ID = "1111"
+++     n.SetIndex(data.LastIndex)
+++     n.ReturnValue = true
+++     err = store.PutObjectAtomic(&n)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     // Get the Object using GetObject, then set again.
+++     newObj := dummyObject{}
+++     err = store.GetObject(Key(expected.Key()...), &newObj)
+++     assert.True(t, newObj.Exists())
+++     err = store.PutObjectAtomic(&n)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++}
+++
+++// dummy data used to test the datastore
+++type dummyObject struct {
+++     Name        string                `kv:"leaf"`
+++     NetworkType string                `kv:"leaf"`
+++     EnableIPv6  bool                  `kv:"leaf"`
+++     Rec         *recStruct            `kv:"recursive"`
+++     Dict        map[string]*recStruct `kv:"iterative"`
+++     Generic     options.Generic       `kv:"iterative"`
+++     ID          string
+++     DBIndex     uint64
+++     DBExists    bool
+++     ReturnValue bool
+++}
+++
+++func (n *dummyObject) Key() []string {
+++     return []string{dummyKey, n.ID}
+++}
+++
+++func (n *dummyObject) KeyPrefix() []string {
+++     return []string{dummyKey}
+++}
+++
+++func (n *dummyObject) Value() []byte {
+++     if !n.ReturnValue {
+++             return nil
+++     }
+++
+++     b, err := json.Marshal(n)
+++     if err != nil {
+++             return nil
+++     }
+++     return b
+++}
+++
+++func (n *dummyObject) SetValue(value []byte) error {
+++     return json.Unmarshal(value, n)
+++}
+++
+++func (n *dummyObject) Index() uint64 {
+++     return n.DBIndex
+++}
+++
+++func (n *dummyObject) SetIndex(index uint64) {
+++     n.DBIndex = index
+++     n.DBExists = true
+++}
+++
+++func (n *dummyObject) Exists() bool {
+++     return n.DBExists
+++}
+++
+++func (n *dummyObject) MarshalJSON() ([]byte, error) {
+++     netMap := make(map[string]interface{})
+++     netMap["name"] = n.Name
+++     netMap["networkType"] = n.NetworkType
+++     netMap["enableIPv6"] = n.EnableIPv6
+++     netMap["generic"] = n.Generic
+++     return json.Marshal(netMap)
+++}
+++
+++func (n *dummyObject) UnmarshalJSON(b []byte) (err error) {
+++     var netMap map[string]interface{}
+++     if err := json.Unmarshal(b, &netMap); err != nil {
+++             return err
+++     }
+++     n.Name = netMap["name"].(string)
+++     n.NetworkType = netMap["networkType"].(string)
+++     n.EnableIPv6 = netMap["enableIPv6"].(bool)
+++     n.Generic = netMap["generic"].(map[string]interface{})
+++     return nil
+++}
+++
+++// dummy structure to test "recursive" cases
+++type recStruct struct {
+++     Name     string            `kv:"leaf"`
+++     Field1   int               `kv:"leaf"`
+++     Dict     map[string]string `kv:"iterative"`
+++     DBIndex  uint64
+++     DBExists bool
+++}
+++
+++func (r *recStruct) Key() []string {
+++     return []string{"recStruct"}
+++}
+++func (r *recStruct) Value() []byte {
+++     b, err := json.Marshal(r)
+++     if err != nil {
+++             return nil
+++     }
+++     return b
+++}
+++
+++func (r *recStruct) SetValue(value []byte) error {
+++     return json.Unmarshal(value, r)
+++}
+++
+++func (r *recStruct) Index() uint64 {
+++     return r.DBIndex
+++}
+++
+++func (r *recStruct) SetIndex(index uint64) {
+++     r.DBIndex = index
+++     r.DBExists = true
+++}
+++
+++func (r *recStruct) Exists() bool {
+++     return r.DBExists
+++}
+++
+++func dummyKVObject(id string, retValue bool) *dummyObject {
+++     cDict := make(map[string]string)
+++     cDict["foo"] = "bar"
+++     cDict["hello"] = "world"
+++     n := dummyObject{
+++             Name:        "testNw",
+++             NetworkType: "bridge",
+++             EnableIPv6:  true,
+++             Rec:         &recStruct{"gen", 5, cDict, 0, false},
+++             ID:          id,
+++             DBIndex:     0,
+++             ReturnValue: retValue,
+++             DBExists:    false}
+++     generic := make(map[string]interface{})
+++     generic["label1"] = &recStruct{"value1", 1, cDict, 0, false}
+++     generic["label2"] = "subnet=10.1.1.0/16"
+++     n.Generic = generic
+++     return &n
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..0817339b6a487b0ab908c65eceb687803e15edb5
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,129 @@@@
+++package datastore
+++
+++import (
+++     "errors"
+++
+++     "github.com/docker/libkv/store"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++var (
+++     // ErrNotImplmented exported
+++     ErrNotImplmented = errors.New("Functionality not implemented")
+++)
+++
+++// MockData exported
+++type MockData struct {
+++     Data  []byte
+++     Index uint64
+++}
+++
+++// MockStore exported
+++type MockStore struct {
+++     db map[string]*MockData
+++}
+++
+++// NewMockStore creates a Map backed Datastore that is useful for mocking
+++func NewMockStore() *MockStore {
+++     db := make(map[string]*MockData)
+++     return &MockStore{db}
+++}
+++
+++// Get the value at "key", returns the last modified index
+++// to use in conjunction to CAS calls
+++func (s *MockStore) Get(key string) (*store.KVPair, error) {
+++     mData := s.db[key]
+++     if mData == nil {
+++             return nil, nil
+++     }
+++     return &store.KVPair{Value: mData.Data, LastIndex: mData.Index}, nil
+++
+++}
+++
+++// Put a value at "key"
+++func (s *MockStore) Put(key string, value []byte, options *store.WriteOptions) error {
+++     mData := s.db[key]
+++     if mData == nil {
+++             mData = &MockData{value, 0}
+++     }
+++     mData.Index = mData.Index + 1
+++     s.db[key] = mData
+++     return nil
+++}
+++
+++// Delete a value at "key"
+++func (s *MockStore) Delete(key string) error {
+++     delete(s.db, key)
+++     return nil
+++}
+++
+++// Exists checks that the key exists inside the store
+++func (s *MockStore) Exists(key string) (bool, error) {
+++     _, ok := s.db[key]
+++     return ok, nil
+++}
+++
+++// List gets a range of values at "directory"
+++func (s *MockStore) List(prefix string) ([]*store.KVPair, error) {
+++     return nil, ErrNotImplmented
+++}
+++
+++// DeleteTree deletes a range of values at "directory"
+++func (s *MockStore) DeleteTree(prefix string) error {
+++     delete(s.db, prefix)
+++     return nil
+++}
+++
+++// Watch a single key for modifications
+++func (s *MockStore) Watch(key string, stopCh <-chan struct{}) (<-chan *store.KVPair, error) {
+++     return nil, ErrNotImplmented
+++}
+++
+++// WatchTree triggers a watch on a range of values at "directory"
+++func (s *MockStore) WatchTree(prefix string, stopCh <-chan struct{}) (<-chan []*store.KVPair, error) {
+++     return nil, ErrNotImplmented
+++}
+++
+++// NewLock exposed
+++func (s *MockStore) NewLock(key string, options *store.LockOptions) (store.Locker, error) {
+++     return nil, ErrNotImplmented
+++}
+++
+++// AtomicPut put a value at "key" if the key has not been
+++// modified in the meantime, throws an error if this is the case
+++func (s *MockStore) AtomicPut(key string, newValue []byte, previous *store.KVPair, options *store.WriteOptions) (bool, *store.KVPair, error) {
+++     mData := s.db[key]
+++
+++     if previous == nil {
+++             if mData != nil {
+++                     return false, nil, types.BadRequestErrorf("atomic put failed because key exists")
+++             } // Else OK.
+++     } else {
+++             if mData == nil {
+++                     return false, nil, types.BadRequestErrorf("atomic put failed because key exists")
+++             }
+++             if mData != nil && mData.Index != previous.LastIndex {
+++                     return false, nil, types.BadRequestErrorf("atomic put failed due to mismatched Index")
+++             } // Else OK.
+++     }
+++     err := s.Put(key, newValue, nil)
+++     if err != nil {
+++             return false, nil, err
+++     }
+++     return true, &store.KVPair{Key: key, Value: newValue, LastIndex: s.db[key].Index}, nil
+++}
+++
+++// AtomicDelete deletes a value at "key" if the key has not
+++// been modified in the meantime, throws an error if this is the case
+++func (s *MockStore) AtomicDelete(key string, previous *store.KVPair) (bool, error) {
+++     mData := s.db[key]
+++     if mData != nil && mData.Index != previous.LastIndex {
+++             return false, types.BadRequestErrorf("atomic delete failed due to mismatched Index")
+++     }
+++     return true, s.Delete(key)
+++}
+++
+++// Close closes the client connection
+++func (s *MockStore) Close() {
+++     return
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8f1f8e1c04eb48c34dbca6a26895cf1bccd4d9e4
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,57 @@@@
+++# -*- 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=\\"--default-network=overlay:multihost --kv-store=consul:192.168.33.10:8500 --label=com.docker.network.driver.overlay.bind_interface=eth1 --label=com.docker.network.driver.overlay.neighbor_ip=192.168.33.11\\" >> /etc/default/docker
+++service docker restart
+++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 = "chef/ubuntu-14.10"
+++      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
+++    end
+++  end
+++
+++end
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..900136121a79d5e7831f25c96727dad7d9b646b8
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,13 @@@@
+++Bridge Driver
+++=============
+++
+++The bridge driver is an implementation that uses Linux Bridging and iptables to provide connectivity for containers
+++It creates a single bridge, called `docker0` by default, and attaches a `veth pair` between the bridge and every endpoint.
+++
+++## Configuration
+++
+++The bridge driver supports configuration through the Docker Daemon flags. 
+++
+++## Usage
+++
+++This driver is supported for the default "bridge" network only and it cannot be used for any other networks.
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..c44b9b2e0007ad1f92d3e14ac0e354c792d3cc7d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,158 @@@@
+++Design
+++======
+++
+++The vision and goals of libnetwork are highlighted in [roadmap](../ROADMAP.md).
+++This document describes how libnetwork has been designed in order to achieve this.
+++Requirements for individual releases can be found on the [Project Page](https://github.com/docker/libnetwork/wiki).
+++
+++Many of the design decisions are inspired by the learnings from the Docker networking design as of Docker v1.6.
+++Please refer to this [Docker v1.6 Design](legacy.md) document for more information on networking design as of Docker v1.6.
+++
+++## Goal
+++
+++libnetwork project will follow Docker and Linux philosophy of developing small, highly modular and composable tools that works well independently.
+++Libnetwork aims to satisfy that composable need for Networking in Containers.
+++
+++## The Container Network Model
+++
+++Libnetwork implements Container Network Model (CNM) which formalizes the steps required to provide networking for containers while providing an abstraction that can be used to support multiple network drivers. The CNM is built on 3 main components.
+++
+++**Sandbox**
+++
+++A Sandbox contains the configuration of a container's network stack.
+++This includes management of the container's interfaces, routing table and DNS settings. 
+++An implementation of a Sandbox could be a Linux Network Namespace, a FreeBSD Jail or other similar concept.
+++A Sandbox may contain *many* endpoints from *multiple* networks.
+++
+++**Endpoint**
+++
+++An Endpoint joins a Sandbox to a Network.
+++An implementation of an Endpoint could be a `veth` pair, an Open vSwitch internal port or similar.
+++An Endpoint can belong to *only one* network but may only belong to *one* Sandbox.
+++
+++**Network**
+++
+++A Network is a group of Endpoints that are able to communicate with each-other directly.
+++An implementation of a Network could be a Linux bridge, a VLAN, etc.
+++Networks consist of *many* endpoints.
+++
+++## CNM Objects
+++
+++**NetworkController**
+++`NetworkController` object provides the entry-point into libnetwork that exposes simple APIs for the users (such as Docker Engine) to allocate and manage Networks. libnetwork supports multiple active drivers (both inbuilt and remote). `NetworkController` allows user to bind a particular driver to a given network.
+++
+++**Driver**
+++`Driver` is not an user visible object, but drivers provides the actual implementation that makes network work. `NetworkController` however provides an API to configure any specific driver with driver-specific options/labels that is transparent to libnetwork, but can be handled by the drivers directly. Drivers can be both inbuilt (such as Bridge, Host, None & overlay) and remote (from plugin providers) to satisfy various usecases & deployment scenarios. At this point, the Driver owns a network and is responsible for managing the network (including IPAM, etc.). This can be improved in the future by having multiple drivers participating in handling various network management functionalities.
+++
+++**Network**
+++`Network` object is an implementation of the `CNM : Network` as defined above. `NetworkController` provides APIs to create and manage `Network` object. Whenever a `Network` is created or updated, the corresponding `Driver` will be notified of the event. LibNetwork treats `Network` object at an abstract level to provide connectivity between a group of end-points that belong to the same network and isolate from the rest. The Driver performs the actual work of providing the required connectivity and isolation. The connectivity can be within the same host or across multiple-hosts. Hence `Network` has a global scope within a cluster.
+++
+++**Endpoint**
+++`Endpoint` represents a Service Endpoint. It provides the connectivity for services exposed by a container in a network with other services provided by other containers in the network. `Network` object provides APIs to create and manage endpoint. An endpoint can be attached to only one network. `Endpoint` creation calls are made to the corresponding `Driver` which is responsible for allocating resources for the corresponding `Sandbox`. Since Endpoint represents a Service and not necessarily a particular container, `Endpoint` has a global scope within a cluster as well.
+++
+++**Sandbox**
+++`Sandbox` object represents container's network configuration such as ip-address, mac-address, routes, DNS entries. A `Sandbox` object is created when the user requests to create an endpoint on a network. The `Driver` that handles the `Network` is responsible to allocate the required network resources (such as ip-address) and pass the info called `SandboxInfo` back to libnetwork. libnetwork will make use of OS specific constructs (example: netns for Linux) to populate the network configuration into the containers that is represented by the `Sandbox`. A `Sandbox` can have multiple endpoints attached to different networks. Since `Sandbox` is associated with a particular container in a given host, it has a local scope that represents the Host that the Container belong to.
+++
+++**CNM Attributes**
+++
+++***Options***
+++`Options` provides a generic and flexible mechanism to pass `Driver` specific configuration option from the user to the `Driver` directly. `Options` are just key-value pairs of data with `key` represented by a string and `value` represented by a generic object (such as golang `interface{}`). Libnetwork will operate on the `Options` ONLY if the  `key` matches any of the well-known `Label` defined in the `net-labels` package. `Options` also encompasses `Labels` as explained below. `Options` are generally NOT end-user visible (in UI), while `Labels` are.
+++
+++***Labels***
+++`Labels` are very similar to `Options` & in fact they are just a subset of `Options`. `Labels` are typically end-user visible and are represented in the UI explicitly using the `--labels` option. They are passed from the UI to the `Driver` so that `Driver` can make use of it and perform any `Driver` specific operation (such as a subnet to allocate IP-Addresses from in a Network).
+++
+++## CNM Lifecycle
+++
+++Consumers of the CNM, like Docker for example, interact through the CNM Objects and its APIs to network the containers that they manage.
+++
+++0. `Drivers` registers with `NetworkController`. Build-in drivers registers inside of LibNetwork, while remote Drivers registers with LibNetwork via Plugin mechanism. (*plugin-mechanism is WIP*). Each `driver` handles a particular `networkType`.
+++
+++1. `NetworkController` object is created using `libnetwork.New()` API to manage the allocation of Networks and optionally configure a `Driver` with driver specific `Options`.
+++
+++2. `Network` is created using the controller's `NewNetwork()` API by providing a `name` and `networkType`. `networkType` parameter helps to choose a corresponding `Driver` and binds the created `Network` to that `Driver`. From this point, any operation on `Network` will be handled by that `Driver`.
+++
+++3. `controller.NewNetwork()` API also takes in optional `options` parameter which carries Driver-specific options and `Labels`, which the Drivers can make use for its purpose.
+++
+++4. `network.CreateEndpoint()` can be called to create a new Endpoint in a given network. This API also accepts optional `options` parameter which drivers can make use of. These 'options' carry both well-known labels and driver-specific labels. Drivers will in turn be called with `driver.CreateEndpoint` and it can choose to reserve IPv4/IPv6 addresses when an `Endpoint` is created in a `Network`. The `Driver` will assign these addresses using `InterfaceInfo` interface defined in the `driverapi`. The IP/IPv6 are needed to complete the endpoint as service definition along with the ports the endpoint exposes since essentially a service endpoint is nothing but a network address and the port number that the application container is listening on.
+++
+++5. `endpoint.Join()` can be used to attach a container to an `Endpoint`. The Join operation will create a `Sandbox` if it doesn't exist already for that container. The Drivers can make use of the Sandbox Key to identify multiple endpoints attached to a same container. This API also accepts optional `options` parameter which drivers can make use of.
+++  * Though it is not a direct design issue of LibNetwork, it is highly encouraged to have users like `Docker` to call the endpoint.Join() during Container's `Start()` lifecycle that is invoked *before* the container is made operational. As part of Docker integration, this will be taken care of.
+++  * One of a FAQ on endpoint join() API is that, why do we need an API to create an Endpoint and another to join the endpoint.
+++    - The answer is based on the fact that Endpoint represents a Service which may or may not be backed by a Container. When an Endpoint is created, it will have its resources reserved so that any container can get attached to the endpoint later and get a consistent networking behaviour.
+++
+++6. `endpoint.Leave()` can be invoked when a container is stopped. The `Driver` can cleanup the states that it allocated during the `Join()` call. LibNetwork will delete the `Sandbox` when the last referencing endpoint leaves the network. But LibNetwork keeps hold of the IP addresses as long as the endpoint is still present and will be reused when the container(or any container) joins again. This ensures that the container's resources are reused when they are Stopped and Started again.
+++
+++7. `endpoint.Delete()` is used to delete an endpoint from a network. This results in deleting an endpoint and cleaning up the cached `sandbox.Info`.
+++
+++8. `network.Delete()` is used to delete a network. LibNetwork will not allow the delete to proceed if there are any existing endpoints attached to the Network. 
+++
+++
+++## Implementation Details
+++
+++### Networks & Endpoints
+++
+++LibNetwork's Network and Endpoint APIs are primarily for managing the corresponding Objects and book-keeping them to provide a level of abstraction as required by the CNM. It delegates the actual implementation to the drivers which  realize the functionality as promised in the CNM. For more information on these details, please see [the drivers section](#Drivers)
+++
+++### Sandbox
+++
+++Libnetwork provides a framework to implement of a Sandbox in multiple operating systems. Currently we have implemented Sandbox for Linux using `namespace_linux.go` and `configure_linux.go` in `sandbox` package 
+++This creates a Network Namespace for each sandbox which is uniquely identified by a path on the host filesystem.
+++Netlink calls are used to move interfaces from the global namespace to the Sandbox namespace.
+++Netlink is also used to manage the routing table in the namespace.
+++
+++## Drivers
+++
+++## API
+++
+++Drivers are essentially an extension of libnetwork and provides the actual implementation for all of the LibNetwork APIs defined above. Hence there is an 1-1 correspondence for all the `Network` and `Endpoint` APIs, which includes :
+++* `driver.Config`
+++* `driver.CreateNetwork`
+++* `driver.DeleteNetwork`
+++* `driver.CreateEndpoint`
+++* `driver.DeleteEndpoint`
+++* `driver.Join`
+++* `driver.Leave` 
+++
+++These Driver facing APIs makes use of unique identifiers (`networkid`,`endpointid`,...) instead of names (as seen in user-facing APIs).
+++
+++The APIs are still work in progress and there can be changes to these based on the driver requirements especially when it comes to Multi-host networking.
+++
+++### Driver semantics
+++
+++ * `Driver.CreateEndpoint`
+++
+++This method is passed an interface `EndpointInfo`, with methods `Interfaces` and `AddInterface`.
+++
+++If the slice returned by `Interfaces` is non-empty, the driver is expected to make use of the interface information therein (e.g., treating the address or addresses as statically supplied), and must return an error if it cannot. If the slice is empty, the driver should allocate zero or more _fresh_ interfaces, and use `AddInterface` to record them; or return an error if it cannot.
+++
+++It is forbidden to use `AddInterface` if `Interfaces` is non-empty.
+++
+++## Implementations
+++
+++Libnetwork includes the following driver packages:
+++
+++- null
+++- bridge
+++- overlay
+++- remote
+++
+++### Null
+++
+++The null driver is a `noop` implementation of the driver API, used only in cases where no networking is desired. This is to provide backward compatibility to the Docker's `--net=none` option.
+++
+++### Bridge
+++
+++The `bridge` driver provides a Linux-specific bridging implementation based on the Linux Bridge.
+++For more details, please [see the Bridge Driver documentation](bridge.md).
+++
+++### Overlay
+++
+++The `overlay` driver implements networking that can span multiple hosts using overlay network encapsulations such as VXLAN.
+++For more details on its design, please see the [Overlay Driver Design](overlay.md).
+++
+++### Remote
+++
+++The `remote` package does not provide a driver, but provides a means of supporting drivers over a remote transport.
+++This allows a driver to be written in a language of your choice.
+++For further details, please see the [Remote Driver Design](remote.md).
+++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..7a19dcdff9e57f57c9672f2a37ff7e894cf17176
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,15 @@@@
+++
+++This document provides a TLD&R version of https://docs.docker.com/v1.6/articles/networking/.
+++If more interested in detailed operational design, please refer to this link.
+++
+++## Docker Networking design as of Docker v1.6
+++
+++Prior to libnetwork, Docker Networking was handled in both Docker Engine and libcontainer.
+++Docker Engine makes use of the Bridge Driver to provide single-host networking solution with the help of linux bridge and IPTables.
+++Docker Engine provides simple configurations such as `--link`, `--expose`,... to enable container connectivity within the same host by abstracting away networking configuration completely from the Containers.
+++For external connectivity, it relied upon NAT & Port-mapping 
+++
+++Docker Engine was responsible for providing the configuration for the container's networking stack.
+++
+++Libcontainer would then use this information to create the necessary networking devices and move them in to a network namespace.
+++This namespace would then be used when the container is started.
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..5be0b724100b929da79190486f6edb547bb9a142
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,153 @@@@
+++# Overlay Driver
+++
+++### Design
+++TODO
+++
+++### Multi-Host Overlay Driver Quick Start
+++
+++This example is to provision two Docker Hosts with the **experimental** Libnetwork overlay network driver.
+++
+++### Pre-Requisites
+++
+++- Kernel >= 3.16
+++- Experimental Docker client
+++
+++### Install Docker Experimental
+++
+++Follow Docker experimental installation instructions at: [https://github.com/docker/docker/tree/master/experimental](https://github.com/docker/docker/tree/master/experimental)
+++
+++To ensure you are running the experimental Docker branch, check the version and look for the experimental tag:
+++
+++```
+++$ docker -v
+++Docker version 1.8.0-dev, build f39b9a0, experimental
+++```
+++
+++### Install and Bootstrap K/V Store
+++
+++
+++Multi-host networking uses a pluggable Key-Value store backend to distribute states using `libkv`.
+++`libkv` supports multiple pluggable backends such as `consul`, `etcd` & `zookeeper` (more to come).
+++
+++In this example we will use `consul`
+++
+++Install:
+++
+++```
+++$ curl -OL https://dl.bintray.com/mitchellh/consul/0.5.2_linux_amd64.zip
+++$ unzip 0.5.2_linux_amd64.zip
+++$ mv consul /usr/local/bin/
+++```
+++
+++**host-1** Start Consul as a server in bootstrap mode:
+++
+++``` 
+++$ consul agent -server -bootstrap -data-dir /tmp/consul -bind=<host-1-ip-address>
+++```
+++
+++**host-2** Start the Consul agent:
+++
+++``` 
+++$ consul agent -data-dir /tmp/consul -bind <host-2-ip-address>
+++$ consul join <host-1-ip-address>
+++```
+++
+++
+++### Start the Docker Daemon with the Network Driver Daemon Flags
+++
+++**host-1** Docker daemon:
+++
+++```
+++$ docker -d --kv-store=consul:localhost:8500 --label=com.docker.network.driver.overlay.bind_interface=eth0
+++```
+++
+++**host-2** Start the Docker Daemon with the neighbor ID configuration:
+++
+++```
+++$ docker -d --kv-store=consul:localhost:8500 --label=com.docker.network.driver.overlay.bind_interface=eth0 --label=com.docker.network.driver.overlay.neighbor_ip=<host-1-ip-address>
+++```
+++
+++### QuickStart Containers Attached to a Network
+++
+++**host-1** Start a container that publishes a service svc1 in the network dev that is managed by overlay driver.
+++
+++```
+++$ docker run -i -t --publish-service=svc1.dev.overlay debian
+++root@21578ff721a9:/# ip add show eth0
+++34: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+++    link/ether 02:42:ec:41:35:bf brd ff:ff:ff:ff:ff:ff
+++    inet 172.21.0.16/16 scope global eth0
+++       valid_lft forever preferred_lft forever
+++    inet6 fe80::42:ecff:fe41:35bf/64 scope link
+++       valid_lft forever preferred_lft forever
+++```
+++
+++**host-2** Start a container that publishes a service svc2 in the network dev that is managed by overlay driver.
+++
+++```
+++$ docker run -i -t --publish-service=svc2.dev.overlay debian
+++root@d217828eb876:/# ping svc1
+++PING svc1 (172.21.0.16): 56 data bytes
+++64 bytes from 172.21.0.16: icmp_seq=0 ttl=64 time=0.706 ms
+++64 bytes from 172.21.0.16: icmp_seq=1 ttl=64 time=0.687 ms
+++64 bytes from 172.21.0.16: icmp_seq=2 ttl=64 time=0.841 ms
+++```
+++### Detailed Setup
+++
+++You can also setup networks and services and then attach a running container to them.
+++
+++**host-1**:
+++
+++```
+++docker network create -d overlay prod 
+++docker network ls
+++docker network info prod
+++docker service publish db1.prod
+++cid=$(docker run -itd -p 8000:8000 ubuntu)
+++docker service attach $cid db1.prod
+++```
+++
+++**host-2**:
+++
+++```
+++docker network ls
+++docker network info prod
+++docker service publish db2.prod
+++cid=$(docker run -itd -p 8000:8000 ubuntu)
+++docker service attach $cid db2.prod
+++```
+++
+++Once a container is started, a container on `host-1` and `host-2` both containers should be able to ping one another via IP, service name, \<service name>.\<network name>
+++
+++
+++View information about the networks and services using `ls` and `info` subcommands like so:
+++
+++```
+++$ docker service ls
+++SERVICE ID          NAME                  NETWORK             CONTAINER
+++0771deb5f84b        db2                   prod                0e54a527f22c
+++aea23b224acf        db1                   prod                4b0a309ca311
+++
+++$ docker network info prod
+++Network Id: 5ac68be2518959b48ad102e9ec3d8f42fb2ec72056aa9592eb5abd0252203012
+++     Name: prod
+++     Type: overlay
+++
+++$ docker service info db1.prod
+++Service Id: aea23b224acfd2da9b893870e0d632499188a1a4b3881515ba042928a9d3f465
+++     Name: db1
+++     Network: prod
+++```
+++
+++To detach and unpublish a service:
+++
+++```
+++$ docker service detach $cid <service>.<network>
+++$ docker service unpublish <service>.<network>
+++
+++# Example:
+++$ docker service detach $cid  db2.prod
+++$ docker service unpublish db2.prod
+++```
+++
+++To reiterate, this is experimental, and will be under active development.
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d9f2ab8b42603c33c8f056fb190ee2bfb5f43982
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,203 @@@@
+++Remote Drivers
+++==============
+++
+++The `drivers.remote` package provides the integration point for dynamically-registered drivers. Unlike the other driver packages, it does not provide a single implementation of a driver; rather, it provides a proxy for remote driver processes, which are registered and communicate with LibNetwork via the Docker plugin package.
+++
+++For the semantics of driver methods, which correspond to the protocol below, please see the [overall design](design.md).
+++
+++## LibNetwork integration with the Docker `plugins` package
+++
+++When LibNetwork initialises the `drivers.remote` package with the `Init()` function, it passes a `DriverCallback` as a parameter, which implements `RegisterDriver()`. The remote driver package uses this interface to register remote drivers with LibNetwork's `NetworkController`, by supplying it in a `plugins.Handle` callback.
+++
+++The callback is invoked when a driver is loaded with the `plugins.Get` API call. How that comes about is out of scope here (but it might be, for instance, when that driver is mentioned by the user).
+++
+++This design ensures that the details of driver registration mechanism are owned by the remote driver package, and it doesn't expose any of the driver layer to the North of LibNetwork.
+++
+++## Implementation
+++
+++The remote driver implementation uses a `plugins.Client` to communicate with the remote driver process. The `driverapi.Driver` methods are implemented as RPCs over the plugin client.
+++
+++The payloads of these RPCs are mostly direct translations into JSON of the arguments given to the method. There are some exceptions to account for the use of the interfaces `EndpointInfo` and `JoinInfo`, and data types that do not serialise to JSON well (e.g., `net.IPNet`). The protocol is detailed below under "Protocol".
+++
+++## Usage
+++
+++A remote driver proxy follows all the rules of any other in-built driver and has exactly the same `Driver` interface exposed. LibNetwork will also support driver-specific `options` and user-supplied `labels` which may influence the behaviour of a remote driver process.
+++
+++## Protocol
+++
+++The remote driver protocol is a set of RPCs, issued as HTTP POSTs with JSON payloads. The proxy issues requests, and the remote driver process is expected to respond usually with a JSON payload of its own, although in some cases these are empty maps.
+++
+++### Errors
+++
+++If the remote process cannot decode, or otherwise detects a syntactic problem with the HTTP request or payload, it must respond with an HTTP error status (4xx or 5xx).
+++
+++If the remote process can decode the request, but cannot complete the operation, it must send a response in the form
+++
+++    {
+++        "Err": string
+++    }
+++
+++The string value supplied may appear in logs, so should not include confidential information.
+++
+++### Handshake
+++
+++When loaded, a remote driver process receives an HTTP POST on the URL `/Plugin.Activate` with no payload. It must respond with a manifest of the form
+++
+++    {
+++        "Implements": ["NetworkDriver"]
+++    }
+++
+++Other entries in the list value are allowed; `"NetworkDriver"` indicates that the plugin should be registered with LibNetwork as a driver.
+++
+++### Create network
+++
+++When the proxy is asked to create a network, the remote process shall receive a POST to the URL `/NetworkDriver.CreateNetwork` of the form
+++
+++    {
+++        "NetworkID": string,
+++        "Options": {
+++            ...
+++        }
+++    }
+++
+++The `NetworkID` value is generated by LibNetwork. The `Options` value is the arbitrary map given to the proxy by LibNetwork.
+++
+++The response indicating success is empty:
+++
+++    `{}`
+++
+++### Delete network
+++
+++When a network owned by the remote driver is deleted, the remote process shall receive a POST to the URL `/NetworkDriver.DeleteNetwork` of the form
+++
+++    {
+++        "NetworkID": string
+++    }
+++
+++The success response is empty:
+++
+++    {}
+++
+++### Create endpoint
+++
+++When the proxy is asked to create an endpoint, the remote process shall receive a POST to the URL `/NetworkDriver.CreateEndpoint` of the form
+++
+++    {
+++        "NetworkID": string,
+++        "EndpointID": string,
+++        "Options": {
+++            ...
+++        },
+++        "Interfaces": [{
+++            "ID": int,
+++            "Address": string,
+++            "AddressIPv6": string,
+++            "MacAddress": string
+++        }, ...]
+++    }
+++
+++The `NetworkID` is the generated identifier for the network to which the endpoint belongs; the `EndpointID` is a generated identifier for the endpoint.
+++
+++`Options` is an arbitrary map as supplied to the proxy.
+++
+++The `Interfaces` value is a list with values of the form given. The fields in the `Interfaces` entries may be empty; and the `Interfaces` list itself may be empty. If supplied, `Address` is an IPv4 address and subnet in CIDR notation; e.g., `"192.168.34.12/16"`. If supplied, `AddressIPv6` is an IPv6 address and subnet in CIDR notation. `MacAddress` is a MAC address as a string; e.g., `"6e:75:32:60:44:c9"`.
+++
+++A success response is of the form
+++
+++    {
+++        "Interfaces": [{
+++            "ID": int,
+++            "Address": string,
+++            "AddressIPv6": string,
+++            "MacAddress": string
+++        }, ...]
+++    }
+++
+++with values in the `Interfaces` entries as above. For each entry, an `ID` and `MacAddress` and either or both of `Address` and `AddressIPv6` must be given. The `ID` is arbitrary but must differ among entries. It is used to identify, within the scope of the endpoint, an individual interface during a `Join` call.
+++
+++If the remote process was supplied entries in `Interfaces`, it must respond with an empty `Interfaces` list. LibNetwork will treat it as an error if it supplies a non-empty list and receives a non-empty list back, and roll back the operation.
+++
+++### Endpoint operational info
+++
+++The proxy may be asked for "operational info" on an endpoint. When this happens, the remote process shall receive a POST to `/NetworkDriver.EndpointOperInfo` of the form
+++
+++    {
+++        "NetworkID": string,
+++        "EndpointID": string
+++    }
+++
+++where `NetworkID` and `EndpointID` have meanings as above. It must send a response of the form
+++
+++    {
+++        "Value": { ... }
+++    }
+++
+++where the value of the `Value` field is an arbitrary (possibly empty) map.
+++
+++### Delete endpoint
+++
+++When an endpoint is deleted, the remote process shall receive a POST to the URL `/NetworkDriver.DeleteEndpoint` with a body of the form
+++
+++    {
+++        "NetworkID": string,
+++        "EndpointID": string
+++    }
+++
+++where `NetworkID` and `EndpointID` have meanings as above. A success response is empty:
+++
+++    {}
+++
+++### Join
+++
+++When a sandbox is given an endpoint, the remote process shall receive a POST to the URL `NetworkDriver.Join` of the form
+++
+++    {
+++        "NetworkID": string,
+++        "EndpointID": string,
+++        "SandboxKey": string,
+++        "Options": { ... }
+++    }
+++
+++The `NetworkID` and `EndpointID` have meanings as above. The `SandboxKey` identifies the sandbox. `Options` is an arbitrary map as supplied to the proxy.
+++
+++The response must have the form
+++
+++    {
+++        "InterfaceNames": [{
+++            SrcName: string,
+++            DstPrefix: string
+++        }, ...],
+++        "Gateway": string,
+++        "GatewayIPv6": string,
+++        "StaticRoutes": [{
+++            "Destination": string,
+++            "RouteType": int,
+++            "NextHop": string,
+++            "InterfaceID": int
+++        }, ...]
+++        "HostsPath": string,
+++        "ResolvConfPath": string
+++    }
+++
+++`Gateway` is optional and if supplied is an IP address as a string; e.g., `"192.168.0.1"`. `GatewayIPv6` is optional and if supplied is an IPv6 address as a string; e.g., `"fe80::7809:baff:fec6:7744"`. `HostsPath` is optional, as is `ResolvConfPath`.
+++
+++The entries in `InterfaceNames` represent veths that should be moved by LibNetwork into the sandbox; the `SrcName` is the name of the veth that the remote process created, and the `DstPrefix` is a prefix for the name the veth should have after it has been moved into the sandbox (LibNetwork will append an index to make sure the actual name does not collide with others).
+++
+++The position of the entries in the list must correspond to the interface IDs given in the response to `/NetworkDriver.CreateEndpoint` as described above. For example, if there were two `Interfaces` in the create endpoint response, with IDs `0` and `1`, then the `InterfaceNames` list would have the interface names respectively in positions `0` and `1`of the list. (For this reason it is recommended that interfaces are given sequential IDs starting with `0`.)
+++
+++The entries in `"StaticRoutes"` represent routes that should be added to an interface once it has been moved into the sandbox. Since there may be zero or more routes for an interface, unlike the interface names they can be supplied in any order, and are marked with the `InterfaceID` of the corresponding interface.
+++
+++Routes are either given a `RouteType` of `0` and a value for `NextHop`; or, a `RouteType` of `1` and no value for `NextHop`, meaning a connected route.
+++
+++### Leave
+++
+++If the proxy is asked to remove an endpoint from a sandbox, the remote process shall receive a POST to the URL `/NetworkDriver.Leave` of the form
+++
+++    {
+++        "NetworkID": string,
+++        "EndpointID": string
+++    }
+++
+++where `NetworkID` and `EndpointID` have meanings as above. The success response is empty:
+++
+++    {}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..0d2d3daa0967e6e9232554ee7313192390da0632
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,185 @@@@
+++# Vagrant Setup to Test the Overlay Driver
+++
+++This documentation highlights how to use Vagrant to start a three nodes setup to test Docker network.
+++
+++## Pre-requisites
+++
+++This was tested on:
+++
+++- Vagrant 1.7.2
+++- VirtualBox 4.3.26
+++
+++## Machine Setup
+++
+++The Vagrantfile provided will start three virtual machines. One will act as a consul server, and the other two will act as Docker host.
+++The experimental version of Docker is installed.
+++
+++- `consul-server` is the Consul server node, based on Ubuntu 14.04, this has IP 192.168.33.10
+++- `net-1` is the first Docker host based on Ubuntu 14.10, this has IP 192.168.33.11
+++- `net-2` is the second Docker host based on Ubuntu 14.10, this has IP 192.168.33.12
+++
+++## Getting Started
+++
+++Clone this repo, change to the `docs` directory and let Vagrant do the work.
+++
+++    $ vagrant up
+++    $ vagrant status
+++    Current machine states:
+++
+++    consul-server             running (virtualbox)
+++    net-1                     running (virtualbox)
+++    net-2                     running (virtualbox)
+++
+++You are now ready to SSH to the Docker hosts and start containers.
+++
+++    $ vagrant ssh net-1
+++    vagrant@net-1:~$ docker version
+++    Client version: 1.8.0-dev
+++    ...<snip>...
+++
+++Check that Docker network is functional by listing the default networks:
+++
+++    vagrant@net-1:~$ docker network ls
+++    NETWORK ID          NAME                TYPE
+++    4275f8b3a821        none                null                
+++    80eba28ed4a7        host                host                
+++    64322973b4aa        bridge              bridge              
+++
+++No services has been published so far, so the `docker service ls` will return an empty list:
+++
+++    $ docker service ls
+++    SERVICE ID          NAME                NETWORK             CONTAINER
+++
+++Start a container and check the content of `/etc/hosts`.
+++
+++    $ docker run -it --rm ubuntu:14.04 bash
+++    root@df479e660658:/# cat /etc/hosts
+++    172.21.0.3       df479e660658
+++    127.0.0.1        localhost
+++    ::1      localhost ip6-localhost ip6-loopback
+++    fe00::0  ip6-localnet
+++    ff00::0  ip6-mcastprefix
+++    ff02::1  ip6-allnodes
+++    ff02::2  ip6-allrouters
+++    172.21.0.3       distracted_bohr
+++    172.21.0.3       distracted_bohr.multihost
+++
+++In a separate terminal on `net-1` list the networks again. You will see that the _multihost_ overlay now appears.
+++The overlay network _multihost_ is your default network. This was setup by the Docker daemon during the Vagrant provisioning. Check `/etc/default/docker` to see the options that were set.
+++
+++    vagrant@net-1:~$ docker network ls
+++    NETWORK ID          NAME                TYPE
+++    4275f8b3a821        none                null
+++    80eba28ed4a7        host                host
+++    64322973b4aa        bridge              bridge
+++    b5c9f05f1f8f        multihost           overlay
+++
+++Now in a separate terminal, SSH to `net-2`, check the network and services. The networks will be the same, and the default network will also be _multihost_ of type overlay. But the service will show the container started on `net-1`:
+++
+++    $ vagrant ssh net-2
+++    vagrant@net-2:~$ docker service ls
+++    SERVICE ID          NAME                NETWORK             CONTAINER
+++    b00f2bfd81ac        distracted_bohr     multihost           df479e660658
+++
+++Start a container on `net-2` and check the `/etc/hosts`.
+++
+++    vagrant@net-2:~$ docker run -ti --rm ubuntu:14.04 bash
+++    root@2ac726b4ce60:/# cat /etc/hosts
+++    172.21.0.4       2ac726b4ce60
+++    127.0.0.1        localhost
+++    ::1      localhost ip6-localhost ip6-loopback
+++    fe00::0  ip6-localnet
+++    ff00::0  ip6-mcastprefix
+++    ff02::1  ip6-allnodes
+++    ff02::2  ip6-allrouters
+++    172.21.0.3       distracted_bohr
+++    172.21.0.3       distracted_bohr.multihost
+++    172.21.0.4       modest_curie
+++    172.21.0.4       modest_curie.multihost
+++
+++You will see not only the container that you just started on `net-2` but also the container that you started earlier on `net-1`.
+++And of course you will be able to ping each container.
+++
+++## Creating a Non Default Overlay Network
+++
+++In the previous test we started containers with regular options `-ti --rm` and these containers got placed automatically in the default network which was set to be the _multihost_ network of type overlay.
+++
+++But you could create your own overlay network and start containers in it. Let's create a new overlay network.
+++On one of your Docker hosts, `net-1` or `net-2` do:
+++
+++    $ docker network create -d overlay foobar
+++    8805e22ad6e29cd7abb95597c91420fdcac54f33fcdd6fbca6dd4ec9710dd6a4
+++    $ docker network ls
+++    NETWORK ID          NAME                TYPE
+++    a77e16a1e394        host                host                
+++    684a4bb4c471        bridge              bridge              
+++    8805e22ad6e2        foobar              overlay             
+++    b5c9f05f1f8f        multihost           overlay             
+++    67d5a33a2e54        none                null   
+++
+++Automatically, the second host will also see this network. To start a container on this new network, simply use the `--publish-service` option of `docker run` like so:
+++
+++    $ docker run -it --rm --publish-service=bar.foobar.overlay ubuntu:14.04 bash
+++
+++Note, that you could directly start a container with a new overlay using the `--publish-service` option and it will create the network automatically.
+++
+++Check the docker services now:
+++
+++    $ docker service ls
+++    SERVICE ID          NAME                NETWORK             CONTAINER
+++    b1ffdbfb1ac6        bar                 foobar              6635a3822135
+++
+++Repeat the getting started steps, by starting another container in this new overlay on the other host, check the `/etc/hosts` file and try to ping each container.
+++
+++## A look at the interfaces
+++
+++This new Docker multihost networking is made possible via VXLAN tunnels and the use of network namespaces.
+++Check the [design](design.md) documentation for all the details. But to explore these concepts a bit, nothing beats an example.
+++
+++With a running container in one overlay, check the network namespace:
+++
+++    $ docker inspect -f '{{ .NetworkSettings.SandboxKey}}' 6635a3822135
+++    /var/run/docker/netns/6635a3822135
+++
+++This is a none default location for network namespaces which might confuse things a bit. So let's become root, head over to this directory that contains the network namespaces of the containers and check the interfaces:
+++
+++    $ sudo su
+++    root@net-2:/home/vagrant# cd /var/run/docker/
+++    root@net-2:/var/run/docker# ls netns
+++    6635a3822135
+++    8805e22ad6e2
+++
+++To be able to check the interfaces in those network namespace using `ip` command, just create a symlink for `netns` that points to `/var/run/docker/netns`:
+++
+++    root@net-2:/var/run# ln -s /var/run/docker/netns netns
+++    root@net-2:/var/run# ip netns show
+++    6635a3822135
+++    8805e22ad6e2
+++
+++The two namespace ID return are the ones of the running container on that host and the one of the actual overlay network the container is in.
+++Let's check the interfaces in the container:
+++
+++    root@net-2:/var/run/docker# ip netns exec 6635a3822135 ip addr show eth0
+++    15: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
+++        link/ether 02:42:b3:91:22:c3 brd ff:ff:ff:ff:ff:ff
+++        inet 172.21.0.5/16 scope global eth0
+++           valid_lft forever preferred_lft forever
+++        inet6 fe80::42:b3ff:fe91:22c3/64 scope link 
+++           valid_lft forever preferred_lft forever
+++
+++Indeed we get back the network interface of our running container, same MAC address, same IP.
+++If we check the links of the overlay namespace we see our vxlan interface and the VLAN ID being used.
+++
+++    root@net-2:/var/run/docker# ip netns exec 8805e22ad6e2 ip -d link show
+++    ...<snip>...
+++    14: vxlan1: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UNKNOWN mode DEFAULT group default 
+++        link/ether 7a:af:20:ee:e3:81 brd ff:ff:ff:ff:ff:ff promiscuity 1 
+++        vxlan id 256 srcport 32768 61000 dstport 8472 proxy l2miss l3miss ageing 300 
+++        bridge_slave 
+++    16: veth2: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master br0 state UP mode DEFAULT group default qlen 1000
+++        link/ether 46:b1:e2:5c:48:a8 brd ff:ff:ff:ff:ff:ff promiscuity 1 
+++        veth 
+++        bridge_slave  
+++
+++If you sniff packets on these interfaces you will see the traffic between your containers.
+++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e53947d82ed3276b08bc478df68140aa32876f62
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,137 @@@@
+++package driverapi
+++
+++import (
+++     "net"
+++
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++// NetworkPluginEndpointType represents the Endpoint Type used by Plugin system
+++const NetworkPluginEndpointType = "NetworkDriver"
+++
+++// Driver is an interface that every plugin driver needs to implement.
+++type Driver interface {
+++     // Push driver specific config to the driver
+++     Config(options map[string]interface{}) error
+++
+++     // CreateNetwork invokes the driver method to create a network passing
+++     // the network id and network specific config. The config mechanism will
+++     // eventually be replaced with labels which are yet to be introduced.
+++     CreateNetwork(nid types.UUID, options map[string]interface{}) error
+++
+++     // DeleteNetwork invokes the driver method to delete network passing
+++     // the network id.
+++     DeleteNetwork(nid types.UUID) error
+++
+++     // CreateEndpoint invokes the driver method to create an endpoint
+++     // passing the network id, endpoint id endpoint information and driver
+++     // specific config. The endpoint information can be either consumed by
+++     // the driver or populated by the driver. The config mechanism will
+++     // eventually be replaced with labels which are yet to be introduced.
+++     CreateEndpoint(nid, eid types.UUID, epInfo EndpointInfo, options map[string]interface{}) error
+++
+++     // DeleteEndpoint invokes the driver method to delete an endpoint
+++     // passing the network id and endpoint id.
+++     DeleteEndpoint(nid, eid types.UUID) error
+++
+++     // EndpointOperInfo retrieves from the driver the operational data related to the specified endpoint
+++     EndpointOperInfo(nid, eid types.UUID) (map[string]interface{}, error)
+++
+++     // Join method is invoked when a Sandbox is attached to an endpoint.
+++     Join(nid, eid types.UUID, sboxKey string, jinfo JoinInfo, options map[string]interface{}) error
+++
+++     // Leave method is invoked when a Sandbox detaches from an endpoint.
+++     Leave(nid, eid types.UUID) error
+++
+++     // Type returns the the type of this driver, the network type this driver manages
+++     Type() string
+++}
+++
+++// EndpointInfo provides a go interface to fetch or populate endpoint assigned network resources.
+++type EndpointInfo interface {
+++     // Interfaces returns a list of interfaces bound to the endpoint.
+++     // If the list is not empty the driver is only expected to consume the interfaces.
+++     // It is an error to try to add interfaces to a non-empty list.
+++     // If the list is empty the driver is expected to populate with 0 or more interfaces.
+++     Interfaces() []InterfaceInfo
+++
+++     // AddInterface is used by the driver to add an interface to the interface list.
+++     // This method will return an error if the driver attempts to add interfaces
+++     // if the Interfaces() method returned a non-empty list.
+++     // ID field need only have significance within the endpoint so it can be a simple
+++     // monotonically increasing number
+++     AddInterface(ID int, mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error
+++}
+++
+++// InterfaceInfo provides a go interface for drivers to retrive
+++// network information to interface resources.
+++type InterfaceInfo interface {
+++     // MacAddress returns the MAC address.
+++     MacAddress() net.HardwareAddr
+++
+++     // Address returns the IPv4 address.
+++     Address() net.IPNet
+++
+++     // AddressIPv6 returns the IPv6 address.
+++     AddressIPv6() net.IPNet
+++
+++     // ID returns the numerical id of the interface and has significance only within
+++     // the endpoint.
+++     ID() int
+++}
+++
+++// InterfaceNameInfo provides a go interface for the drivers to assign names
+++// to interfaces.
+++type InterfaceNameInfo interface {
+++     // SetNames method assigns the srcName and dstPrefix for the interface.
+++     SetNames(srcName, dstPrefix string) error
+++
+++     // ID returns the numerical id that was assigned to the interface by the driver
+++     // CreateEndpoint.
+++     ID() int
+++}
+++
+++// JoinInfo represents a set of resources that the driver has the ability to provide during
+++// join time.
+++type JoinInfo interface {
+++     // InterfaceNames returns a list of InterfaceNameInfo go interface to facilitate
+++     // setting the names for the interfaces.
+++     InterfaceNames() []InterfaceNameInfo
+++
+++     // SetGateway sets the default IPv4 gateway when a container joins the endpoint.
+++     SetGateway(net.IP) error
+++
+++     // SetGatewayIPv6 sets the default IPv6 gateway when a container joins the endpoint.
+++     SetGatewayIPv6(net.IP) error
+++
+++     // AddStaticRoute adds a routes to the sandbox.
+++     // It may be used in addtion to or instead of a default gateway (as above).
+++     AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP, interfaceID int) error
+++
+++     // SetHostsPath sets the overriding /etc/hosts path to use for the container.
+++     SetHostsPath(string) error
+++
+++     // SetResolvConfPath sets the overriding /etc/resolv.conf path to use for the container.
+++     SetResolvConfPath(string) error
+++}
+++
+++// DriverCallback provides a Callback interface for Drivers into LibNetwork
+++type DriverCallback interface {
+++     // RegisterDriver provides a way for Remote drivers to dynamically register new NetworkType and associate with a driver instance
+++     RegisterDriver(name string, driver Driver, capability Capability) error
+++}
+++
+++// Scope indicates the drivers scope capability
+++type Scope int
+++
+++const (
+++     // LocalScope represents the driver capable of providing networking services for containers in a single host
+++     LocalScope Scope = iota
+++     // GlobalScope represents the driver capable of providing networking services for containers across hosts
+++     GlobalScope
+++)
+++
+++// Capability represents the high level capabilities of the drivers which libnetwork can make use of
+++type Capability struct {
+++     Scope Scope
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..041ef415067babd15e5bbfd3b50993ee22eb8a8c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,56 @@@@
+++package driverapi
+++
+++import (
+++     "fmt"
+++)
+++
+++// ErrNoNetwork is returned if no network with the specified id exists
+++type ErrNoNetwork string
+++
+++func (enn ErrNoNetwork) Error() string {
+++     return fmt.Sprintf("No network (%s) exists", string(enn))
+++}
+++
+++// NotFound denotes the type of this error
+++func (enn ErrNoNetwork) NotFound() {}
+++
+++// ErrEndpointExists is returned if more than one endpoint is added to the network
+++type ErrEndpointExists string
+++
+++func (ee ErrEndpointExists) Error() string {
+++     return fmt.Sprintf("Endpoint (%s) already exists (Only one endpoint allowed)", string(ee))
+++}
+++
+++// Forbidden denotes the type of this error
+++func (ee ErrEndpointExists) Forbidden() {}
+++
+++// ErrNotImplemented is returned when a Driver has not implemented an API yet
+++type ErrNotImplemented struct{}
+++
+++func (eni *ErrNotImplemented) Error() string {
+++     return "The API is not implemented yet"
+++}
+++
+++// NotImplemented denotes the type of this error
+++func (eni *ErrNotImplemented) NotImplemented() {}
+++
+++// ErrNoEndpoint is returned if no endpoint with the specified id exists
+++type ErrNoEndpoint string
+++
+++func (ene ErrNoEndpoint) Error() string {
+++     return fmt.Sprintf("No endpoint (%s) exists", string(ene))
+++}
+++
+++// NotFound denotes the type of this error
+++func (ene ErrNoEndpoint) NotFound() {}
+++
+++// ErrActiveRegistration represents an error when a driver is registered to a networkType that is previously registered
+++type ErrActiveRegistration string
+++
+++// Error interface for ErrActiveRegistration
+++func (ar ErrActiveRegistration) Error() string {
+++     return fmt.Sprintf("Driver already registered for type %q", string(ar))
+++}
+++
+++// Forbidden denotes the type of this error
+++func (ar ErrActiveRegistration) Forbidden() {}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..3ff8f70925d74ab14316172702d52c9d466247cc
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,1427 @@@@
+++package bridge
+++
+++import (
+++     "errors"
+++     "fmt"
+++     "io/ioutil"
+++     "net"
+++     "os/exec"
+++     "path/filepath"
+++     "strconv"
+++     "sync"
+++     "syscall"
+++
+++     "github.com/Sirupsen/logrus"
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/ipallocator"
+++     "github.com/docker/libnetwork/iptables"
+++     "github.com/docker/libnetwork/netlabel"
+++     "github.com/docker/libnetwork/netutils"
+++     "github.com/docker/libnetwork/options"
+++     "github.com/docker/libnetwork/portmapper"
+++     "github.com/docker/libnetwork/sandbox"
+++     "github.com/docker/libnetwork/types"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++const (
+++     networkType             = "bridge"
+++     vethPrefix              = "veth"
+++     vethLen                 = 7
+++     containerVethPrefix     = "eth"
+++     maxAllocatePortAttempts = 10
+++     ifaceID                 = 1
+++)
+++
+++var (
+++     ipAllocator *ipallocator.IPAllocator
+++)
+++
+++// configuration info for the "bridge" driver.
+++type configuration struct {
+++     EnableIPForwarding bool
+++}
+++
+++// networkConfiguration for network specific configuration
+++type networkConfiguration struct {
+++     BridgeName            string
+++     AddressIPv4           *net.IPNet
+++     FixedCIDR             *net.IPNet
+++     FixedCIDRv6           *net.IPNet
+++     EnableIPv6            bool
+++     EnableIPTables        bool
+++     EnableIPMasquerade    bool
+++     EnableICC             bool
+++     Mtu                   int
+++     DefaultGatewayIPv4    net.IP
+++     DefaultGatewayIPv6    net.IP
+++     DefaultBindingIP      net.IP
+++     AllowNonDefaultBridge bool
+++     EnableUserlandProxy   bool
+++}
+++
+++// endpointConfiguration represents the user specified configuration for the sandbox endpoint
+++type endpointConfiguration struct {
+++     MacAddress   net.HardwareAddr
+++     PortBindings []types.PortBinding
+++     ExposedPorts []types.TransportPort
+++}
+++
+++// containerConfiguration represents the user specified configuration for a container
+++type containerConfiguration struct {
+++     ParentEndpoints []string
+++     ChildEndpoints  []string
+++}
+++
+++type bridgeEndpoint struct {
+++     id              types.UUID
+++     srcName         string
+++     addr            *net.IPNet
+++     addrv6          *net.IPNet
+++     macAddress      net.HardwareAddr
+++     config          *endpointConfiguration // User specified parameters
+++     containerConfig *containerConfiguration
+++     portMapping     []types.PortBinding // Operation port bindings
+++}
+++
+++type bridgeNetwork struct {
+++     id         types.UUID
+++     bridge     *bridgeInterface // The bridge's L3 interface
+++     config     *networkConfiguration
+++     endpoints  map[types.UUID]*bridgeEndpoint // key: endpoint id
+++     portMapper *portmapper.PortMapper
+++     sync.Mutex
+++}
+++
+++type driver struct {
+++     config   *configuration
+++     network  *bridgeNetwork
+++     networks map[types.UUID]*bridgeNetwork
+++     sync.Mutex
+++}
+++
+++func init() {
+++     ipAllocator = ipallocator.New()
+++}
+++
+++// New constructs a new bridge driver
+++func newDriver() driverapi.Driver {
+++     return &driver{networks: map[types.UUID]*bridgeNetwork{}}
+++}
+++
+++// Init registers a new instance of bridge driver
+++func Init(dc driverapi.DriverCallback) error {
+++     // try to modprobe bridge first
+++     // see gh#12177
+++     if out, err := exec.Command("modprobe", "-va", "bridge", "nf_nat", "br_netfilter").CombinedOutput(); err != nil {
+++             logrus.Warnf("Running modprobe bridge nf_nat br_netfilter failed with message: %s, error: %v", out, err)
+++     }
+++     if err := iptables.FirewalldInit(); err != nil {
+++             logrus.Debugf("Fail to initialize firewalld: %v, using raw iptables instead", err)
+++     }
+++     if err := iptables.RemoveExistingChain(DockerChain, iptables.Nat); err != nil {
+++             logrus.Warnf("Failed to remove existing iptables entries in %s : %v", DockerChain, err)
+++     }
+++
+++     c := driverapi.Capability{
+++             Scope: driverapi.LocalScope,
+++     }
+++     return dc.RegisterDriver(networkType, newDriver(), c)
+++}
+++
+++// Validate performs a static validation on the network configuration parameters.
+++// Whatever can be assessed a priori before attempting any programming.
+++func (c *networkConfiguration) Validate() error {
+++     if c.Mtu < 0 {
+++             return ErrInvalidMtu(c.Mtu)
+++     }
+++
+++     // If bridge v4 subnet is specified
+++     if c.AddressIPv4 != nil {
+++             // If Container restricted subnet is specified, it must be a subset of bridge subnet
+++             if c.FixedCIDR != nil {
+++                     // Check Network address
+++                     if !c.AddressIPv4.Contains(c.FixedCIDR.IP) {
+++                             return &ErrInvalidContainerSubnet{}
+++                     }
+++                     // Check it is effectively a subset
+++                     brNetLen, _ := c.AddressIPv4.Mask.Size()
+++                     cnNetLen, _ := c.FixedCIDR.Mask.Size()
+++                     if brNetLen > cnNetLen {
+++                             return &ErrInvalidContainerSubnet{}
+++                     }
+++             }
+++             // If default gw is specified, it must be part of bridge subnet
+++             if c.DefaultGatewayIPv4 != nil {
+++                     if !c.AddressIPv4.Contains(c.DefaultGatewayIPv4) {
+++                             return &ErrInvalidGateway{}
+++                     }
+++             }
+++     }
+++
+++     // If default v6 gw is specified, FixedCIDRv6 must be specified and gw must belong to FixedCIDRv6 subnet
+++     if c.EnableIPv6 && c.DefaultGatewayIPv6 != nil {
+++             if c.FixedCIDRv6 == nil || !c.FixedCIDRv6.Contains(c.DefaultGatewayIPv6) {
+++                     return &ErrInvalidGateway{}
+++             }
+++     }
+++
+++     return nil
+++}
+++
+++// Conflicts check if two NetworkConfiguration objects overlap
+++func (c *networkConfiguration) Conflicts(o *networkConfiguration) bool {
+++     if o == nil {
+++             return false
+++     }
+++
+++     // Also empty, becasue only one network with empty name is allowed
+++     if c.BridgeName == o.BridgeName {
+++             return true
+++     }
+++
+++     // They must be in different subnets
+++     if (c.AddressIPv4 != nil && o.AddressIPv4 != nil) &&
+++             (c.AddressIPv4.Contains(o.AddressIPv4.IP) || o.AddressIPv4.Contains(c.AddressIPv4.IP)) {
+++             return true
+++     }
+++
+++     return false
+++}
+++
+++// fromMap retrieve the configuration data from the map form.
+++func (c *networkConfiguration) fromMap(data map[string]interface{}) error {
+++     var err error
+++
+++     if i, ok := data["BridgeName"]; ok && i != nil {
+++             if c.BridgeName, ok = i.(string); !ok {
+++                     return types.BadRequestErrorf("invalid type for BridgeName value")
+++             }
+++     }
+++
+++     if i, ok := data["Mtu"]; ok && i != nil {
+++             if s, ok := i.(string); ok {
+++                     if c.Mtu, err = strconv.Atoi(s); err != nil {
+++                             return types.BadRequestErrorf("failed to parse Mtu value: %s", err.Error())
+++                     }
+++             } else {
+++                     return types.BadRequestErrorf("invalid type for Mtu value")
+++             }
+++     }
+++
+++     if i, ok := data["EnableIPv6"]; ok && i != nil {
+++             if s, ok := i.(string); ok {
+++                     if c.EnableIPv6, err = strconv.ParseBool(s); err != nil {
+++                             return types.BadRequestErrorf("failed to parse EnableIPv6 value: %s", err.Error())
+++                     }
+++             } else {
+++                     return types.BadRequestErrorf("invalid type for EnableIPv6 value")
+++             }
+++     }
+++
+++     if i, ok := data["EnableIPTables"]; ok && i != nil {
+++             if s, ok := i.(string); ok {
+++                     if c.EnableIPTables, err = strconv.ParseBool(s); err != nil {
+++                             return types.BadRequestErrorf("failed to parse EnableIPTables value: %s", err.Error())
+++                     }
+++             } else {
+++                     return types.BadRequestErrorf("invalid type for EnableIPTables value")
+++             }
+++     }
+++
+++     if i, ok := data["EnableIPMasquerade"]; ok && i != nil {
+++             if s, ok := i.(string); ok {
+++                     if c.EnableIPMasquerade, err = strconv.ParseBool(s); err != nil {
+++                             return types.BadRequestErrorf("failed to parse EnableIPMasquerade value: %s", err.Error())
+++                     }
+++             } else {
+++                     return types.BadRequestErrorf("invalid type for EnableIPMasquerade value")
+++             }
+++     }
+++
+++     if i, ok := data["EnableICC"]; ok && i != nil {
+++             if s, ok := i.(string); ok {
+++                     if c.EnableICC, err = strconv.ParseBool(s); err != nil {
+++                             return types.BadRequestErrorf("failed to parse EnableICC value: %s", err.Error())
+++                     }
+++             } else {
+++                     return types.BadRequestErrorf("invalid type for EnableICC value")
+++             }
+++     }
+++
+++     if i, ok := data["AllowNonDefaultBridge"]; ok && i != nil {
+++             if s, ok := i.(string); ok {
+++                     if c.AllowNonDefaultBridge, err = strconv.ParseBool(s); err != nil {
+++                             return types.BadRequestErrorf("failed to parse AllowNonDefaultBridge value: %s", err.Error())
+++                     }
+++             } else {
+++                     return types.BadRequestErrorf("invalid type for AllowNonDefaultBridge value")
+++             }
+++     }
+++
+++     if i, ok := data["AddressIPv4"]; ok && i != nil {
+++             if s, ok := i.(string); ok {
+++                     if ip, nw, e := net.ParseCIDR(s); e == nil {
+++                             nw.IP = ip
+++                             c.AddressIPv4 = nw
+++                     } else {
+++                             return types.BadRequestErrorf("failed to parse AddressIPv4 value")
+++                     }
+++             } else {
+++                     return types.BadRequestErrorf("invalid type for AddressIPv4 value")
+++             }
+++     }
+++
+++     if i, ok := data["FixedCIDR"]; ok && i != nil {
+++             if s, ok := i.(string); ok {
+++                     if ip, nw, e := net.ParseCIDR(s); e == nil {
+++                             nw.IP = ip
+++                             c.FixedCIDR = nw
+++                     } else {
+++                             return types.BadRequestErrorf("failed to parse FixedCIDR value")
+++                     }
+++             } else {
+++                     return types.BadRequestErrorf("invalid type for FixedCIDR value")
+++             }
+++     }
+++
+++     if i, ok := data["FixedCIDRv6"]; ok && i != nil {
+++             if s, ok := i.(string); ok {
+++                     if ip, nw, e := net.ParseCIDR(s); e == nil {
+++                             nw.IP = ip
+++                             c.FixedCIDRv6 = nw
+++                     } else {
+++                             return types.BadRequestErrorf("failed to parse FixedCIDRv6 value")
+++                     }
+++             } else {
+++                     return types.BadRequestErrorf("invalid type for FixedCIDRv6 value")
+++             }
+++     }
+++
+++     if i, ok := data["DefaultGatewayIPv4"]; ok && i != nil {
+++             if s, ok := i.(string); ok {
+++                     if c.DefaultGatewayIPv4 = net.ParseIP(s); c.DefaultGatewayIPv4 == nil {
+++                             return types.BadRequestErrorf("failed to parse DefaultGatewayIPv4 value")
+++                     }
+++             } else {
+++                     return types.BadRequestErrorf("invalid type for DefaultGatewayIPv4 value")
+++             }
+++     }
+++
+++     if i, ok := data["DefaultGatewayIPv6"]; ok && i != nil {
+++             if s, ok := i.(string); ok {
+++                     if c.DefaultGatewayIPv6 = net.ParseIP(s); c.DefaultGatewayIPv6 == nil {
+++                             return types.BadRequestErrorf("failed to parse DefaultGatewayIPv6 value")
+++                     }
+++             } else {
+++                     return types.BadRequestErrorf("invalid type for DefaultGatewayIPv6 value")
+++             }
+++     }
+++
+++     if i, ok := data["DefaultBindingIP"]; ok && i != nil {
+++             if s, ok := i.(string); ok {
+++                     if c.DefaultBindingIP = net.ParseIP(s); c.DefaultBindingIP == nil {
+++                             return types.BadRequestErrorf("failed to parse DefaultBindingIP value")
+++                     }
+++             } else {
+++                     return types.BadRequestErrorf("invalid type for DefaultBindingIP value")
+++             }
+++     }
+++     return nil
+++}
+++
+++func (n *bridgeNetwork) getEndpoint(eid types.UUID) (*bridgeEndpoint, error) {
+++     n.Lock()
+++     defer n.Unlock()
+++
+++     if eid == "" {
+++             return nil, InvalidEndpointIDError(eid)
+++     }
+++
+++     if ep, ok := n.endpoints[eid]; ok {
+++             return ep, nil
+++     }
+++
+++     return nil, nil
+++}
+++
+++// Install/Removes the iptables rules needed to isolate this network
+++// from each of the other networks
+++func (n *bridgeNetwork) isolateNetwork(others []*bridgeNetwork, enable bool) error {
+++     n.Lock()
+++     thisV4 := n.bridge.bridgeIPv4
+++     thisV6 := getV6Network(n.config, n.bridge)
+++     n.Unlock()
+++
+++     // Install the rules to isolate this networks against each of the other networks
+++     for _, o := range others {
+++             o.Lock()
+++             otherV4 := o.bridge.bridgeIPv4
+++             otherV6 := getV6Network(o.config, o.bridge)
+++             o.Unlock()
+++
+++             if !types.CompareIPNet(thisV4, otherV4) {
+++                     // It's ok to pass a.b.c.d/x, iptables will ignore the host subnet bits
+++                     if err := setINC(thisV4.String(), otherV4.String(), enable); err != nil {
+++                             return err
+++                     }
+++             }
+++
+++             if thisV6 != nil && otherV6 != nil && !types.CompareIPNet(thisV6, otherV6) {
+++                     if err := setINC(thisV6.String(), otherV6.String(), enable); err != nil {
+++                             return err
+++                     }
+++             }
+++     }
+++
+++     return nil
+++}
+++
+++// Checks whether this network's configuration for the network with this id conflicts with any of the passed networks
+++func (c *networkConfiguration) conflictsWithNetworks(id types.UUID, others []*bridgeNetwork) error {
+++     for _, nw := range others {
+++
+++             nw.Lock()
+++             nwID := nw.id
+++             nwConfig := nw.config
+++             nwBridge := nw.bridge
+++             nw.Unlock()
+++
+++             if nwID == id {
+++                     continue
+++             }
+++             // Verify the name (which may have been set by newInterface()) does not conflict with
+++             // existing bridge interfaces. Ironically the system chosen name gets stored in the config...
+++             // Basically we are checking if the two original configs were both empty.
+++             if nwConfig.BridgeName == c.BridgeName {
+++                     return types.ForbiddenErrorf("conflicts with network %s (%s) by bridge name", nwID, nwConfig.BridgeName)
+++             }
+++             // If this network config specifies the AddressIPv4, we need
+++             // to make sure it does not conflict with any previously allocated
+++             // bridges. This could not be completely caught by the config conflict
+++             // check, because networks which config does not specify the AddressIPv4
+++             // get their address and subnet selected by the driver (see electBridgeIPv4())
+++             if c.AddressIPv4 != nil {
+++                     if nwBridge.bridgeIPv4.Contains(c.AddressIPv4.IP) ||
+++                             c.AddressIPv4.Contains(nwBridge.bridgeIPv4.IP) {
+++                             return types.ForbiddenErrorf("conflicts with network %s (%s) by ip network", nwID, nwConfig.BridgeName)
+++                     }
+++             }
+++     }
+++
+++     return nil
+++}
+++
+++func (d *driver) Config(option map[string]interface{}) error {
+++     var config *configuration
+++
+++     d.Lock()
+++     defer d.Unlock()
+++
+++     if d.config != nil {
+++             return &ErrConfigExists{}
+++     }
+++
+++     genericData, ok := option[netlabel.GenericData]
+++     if ok && genericData != nil {
+++             switch opt := genericData.(type) {
+++             case options.Generic:
+++                     opaqueConfig, err := options.GenerateFromModel(opt, &configuration{})
+++                     if err != nil {
+++                             return err
+++                     }
+++                     config = opaqueConfig.(*configuration)
+++             case *configuration:
+++                     config = opt
+++             default:
+++                     return &ErrInvalidDriverConfig{}
+++             }
+++
+++             d.config = config
+++     } else {
+++             config = &configuration{}
+++     }
+++
+++     if config.EnableIPForwarding {
+++             return setupIPForwarding(config)
+++     }
+++
+++     return nil
+++}
+++
+++func (d *driver) getNetwork(id types.UUID) (*bridgeNetwork, error) {
+++     d.Lock()
+++     defer d.Unlock()
+++
+++     if id == "" {
+++             return nil, types.BadRequestErrorf("invalid network id: %s", id)
+++     }
+++
+++     if nw, ok := d.networks[id]; ok {
+++             return nw, nil
+++     }
+++
+++     return nil, types.NotFoundErrorf("network not found: %s", id)
+++}
+++
+++func parseNetworkGenericOptions(data interface{}) (*networkConfiguration, error) {
+++     var (
+++             err    error
+++             config *networkConfiguration
+++     )
+++
+++     switch opt := data.(type) {
+++     case *networkConfiguration:
+++             config = opt
+++     case map[string]interface{}:
+++             config = &networkConfiguration{
+++                     EnableICC:          true,
+++                     EnableIPTables:     true,
+++                     EnableIPMasquerade: true,
+++             }
+++             err = config.fromMap(opt)
+++     case options.Generic:
+++             var opaqueConfig interface{}
+++             if opaqueConfig, err = options.GenerateFromModel(opt, config); err == nil {
+++                     config = opaqueConfig.(*networkConfiguration)
+++             }
+++     default:
+++             err = types.BadRequestErrorf("do not recognize network configuration format: %T", opt)
+++     }
+++
+++     return config, err
+++}
+++
+++func parseNetworkOptions(option options.Generic) (*networkConfiguration, error) {
+++     var err error
+++     config := &networkConfiguration{}
+++
+++     // Parse generic label first, config will be re-assigned
+++     if genData, ok := option[netlabel.GenericData]; ok && genData != nil {
+++             if config, err = parseNetworkGenericOptions(genData); err != nil {
+++                     return nil, err
+++             }
+++     }
+++
+++     // Process well-known labels next
+++     if _, ok := option[netlabel.EnableIPv6]; ok {
+++             config.EnableIPv6 = option[netlabel.EnableIPv6].(bool)
+++     }
+++
+++     // Finally validate the configuration
+++     if err = config.Validate(); err != nil {
+++             return nil, err
+++     }
+++
+++     return config, nil
+++}
+++
+++// Returns the non link-local IPv6 subnet for the containers attached to this bridge if found, nil otherwise
+++func getV6Network(config *networkConfiguration, i *bridgeInterface) *net.IPNet {
+++     if config.FixedCIDRv6 != nil {
+++             return config.FixedCIDRv6
+++     }
+++
+++     if i.bridgeIPv6 != nil && i.bridgeIPv6.IP != nil && !i.bridgeIPv6.IP.IsLinkLocalUnicast() {
+++             return i.bridgeIPv6
+++     }
+++
+++     return nil
+++}
+++
+++// Return a slice of networks over which caller can iterate safely
+++func (d *driver) getNetworks() []*bridgeNetwork {
+++     d.Lock()
+++     defer d.Unlock()
+++
+++     ls := make([]*bridgeNetwork, 0, len(d.networks))
+++     for _, nw := range d.networks {
+++             ls = append(ls, nw)
+++     }
+++     return ls
+++}
+++
+++// Create a new network using bridge plugin
+++func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) error {
+++     var err error
+++
+++     defer sandbox.InitOSContext()()
+++
+++     // Sanity checks
+++     d.Lock()
+++     if _, ok := d.networks[id]; ok {
+++             d.Unlock()
+++             return types.ForbiddenErrorf("network %s exists", id)
+++     }
+++     d.Unlock()
+++
+++     // Parse and validate the config. It should not conflict with existing networks' config
+++     config, err := parseNetworkOptions(option)
+++     if err != nil {
+++             return err
+++     }
+++     networkList := d.getNetworks()
+++     for _, nw := range networkList {
+++             nw.Lock()
+++             nwConfig := nw.config
+++             nw.Unlock()
+++             if nwConfig.Conflicts(config) {
+++                     return types.ForbiddenErrorf("conflicts with network %s (%s)", nw.id, nw.config.BridgeName)
+++             }
+++     }
+++
+++     // Create and set network handler in driver
+++     network := &bridgeNetwork{
+++             id:         id,
+++             endpoints:  make(map[types.UUID]*bridgeEndpoint),
+++             config:     config,
+++             portMapper: portmapper.New(),
+++     }
+++
+++     d.Lock()
+++     d.networks[id] = network
+++     d.Unlock()
+++
+++     // On failure make sure to reset driver network handler to nil
+++     defer func() {
+++             if err != nil {
+++                     d.Lock()
+++                     delete(d.networks, id)
+++                     d.Unlock()
+++             }
+++     }()
+++
+++     // Create or retrieve the bridge L3 interface
+++     bridgeIface := newInterface(config)
+++     network.bridge = bridgeIface
+++
+++     // Verify the network configuration does not conflict with previously installed
+++     // networks. This step is needed now because driver might have now set the bridge
+++     // name on this config struct. And because we need to check for possible address
+++     // conflicts, so we need to check against operationa lnetworks.
+++     if err = config.conflictsWithNetworks(id, networkList); err != nil {
+++             return err
+++     }
+++
+++     setupNetworkIsolationRules := func(config *networkConfiguration, i *bridgeInterface) error {
+++             if err := network.isolateNetwork(networkList, true); err != nil {
+++                     if err := network.isolateNetwork(networkList, false); err != nil {
+++                             logrus.Warnf("Failed on removing the inter-network iptables rules on cleanup: %v", err)
+++                     }
+++                     return err
+++             }
+++             return nil
+++     }
+++
+++     // Prepare the bridge setup configuration
+++     bridgeSetup := newBridgeSetup(config, bridgeIface)
+++
+++     // If the bridge interface doesn't exist, we need to start the setup steps
+++     // by creating a new device and assigning it an IPv4 address.
+++     bridgeAlreadyExists := bridgeIface.exists()
+++     if !bridgeAlreadyExists {
+++             bridgeSetup.queueStep(setupDevice)
+++     }
+++
+++     // Even if a bridge exists try to setup IPv4.
+++     bridgeSetup.queueStep(setupBridgeIPv4)
+++
+++     enableIPv6Forwarding := false
+++     if d.config != nil && d.config.EnableIPForwarding && config.FixedCIDRv6 != nil {
+++             enableIPv6Forwarding = true
+++     }
+++
+++     // Conditionally queue setup steps depending on configuration values.
+++     for _, step := range []struct {
+++             Condition bool
+++             Fn        setupStep
+++     }{
+++             // Enable IPv6 on the bridge if required. We do this even for a
+++             // previously  existing bridge, as it may be here from a previous
+++             // installation where IPv6 wasn't supported yet and needs to be
+++             // assigned an IPv6 link-local address.
+++             {config.EnableIPv6, setupBridgeIPv6},
+++
+++             // We ensure that the bridge has the expectedIPv4 and IPv6 addresses in
+++             // the case of a previously existing device.
+++             {bridgeAlreadyExists, setupVerifyAndReconcile},
+++
+++             // Setup the bridge to allocate containers IPv4 addresses in the
+++             // specified subnet.
+++             {config.FixedCIDR != nil, setupFixedCIDRv4},
+++
+++             // Setup the bridge to allocate containers global IPv6 addresses in the
+++             // specified subnet.
+++             {config.FixedCIDRv6 != nil, setupFixedCIDRv6},
+++
+++             // Enable IPv6 Forwarding
+++             {enableIPv6Forwarding, setupIPv6Forwarding},
+++
+++             // Setup Loopback Adresses Routing
+++             {!config.EnableUserlandProxy, setupLoopbackAdressesRouting},
+++
+++             // Setup IPTables.
+++             {config.EnableIPTables, network.setupIPTables},
+++
+++             //We want to track firewalld configuration so that
+++             //if it is started/reloaded, the rules can be applied correctly
+++             {config.EnableIPTables, network.setupFirewalld},
+++
+++             // Setup DefaultGatewayIPv4
+++             {config.DefaultGatewayIPv4 != nil, setupGatewayIPv4},
+++
+++             // Setup DefaultGatewayIPv6
+++             {config.DefaultGatewayIPv6 != nil, setupGatewayIPv6},
+++
+++             // Add inter-network communication rules.
+++             {config.EnableIPTables, setupNetworkIsolationRules},
+++
+++             //Configure bridge networking filtering if ICC is off and IP tables are enabled
+++             {!config.EnableICC && config.EnableIPTables, setupBridgeNetFiltering},
+++     } {
+++             if step.Condition {
+++                     bridgeSetup.queueStep(step.Fn)
+++             }
+++     }
+++
+++     // Block bridge IP from being allocated.
+++     bridgeSetup.queueStep(allocateBridgeIP)
+++     // Apply the prepared list of steps, and abort at the first error.
+++     bridgeSetup.queueStep(setupDeviceUp)
+++     if err = bridgeSetup.apply(); err != nil {
+++             return err
+++     }
+++
+++     return nil
+++}
+++
+++func (d *driver) DeleteNetwork(nid types.UUID) error {
+++     var err error
+++
+++     defer sandbox.InitOSContext()()
+++
+++     // Get network handler and remove it from driver
+++     d.Lock()
+++     n, ok := d.networks[nid]
+++     d.Unlock()
+++
+++     if !ok {
+++             return types.InternalMaskableErrorf("network %s does not exist", nid)
+++     }
+++
+++     n.Lock()
+++     config := n.config
+++     n.Unlock()
+++
+++     if config.BridgeName == DefaultBridgeName {
+++             return types.ForbiddenErrorf("default network of type \"%s\" cannot be deleted", networkType)
+++     }
+++
+++     d.Lock()
+++     delete(d.networks, nid)
+++     d.Unlock()
+++
+++     // On failure set network handler back in driver, but
+++     // only if is not already taken over by some other thread
+++     defer func() {
+++             if err != nil {
+++                     d.Lock()
+++                     if _, ok := d.networks[nid]; !ok {
+++                             d.networks[nid] = n
+++                     }
+++                     d.Unlock()
+++             }
+++     }()
+++
+++     // Sanity check
+++     if n == nil {
+++             err = driverapi.ErrNoNetwork(nid)
+++             return err
+++     }
+++
+++     // Cannot remove network if endpoints are still present
+++     if len(n.endpoints) != 0 {
+++             err = ActiveEndpointsError(n.id)
+++             return err
+++     }
+++
+++     // In case of failures after this point, restore the network isolation rules
+++     nwList := d.getNetworks()
+++     defer func() {
+++             if err != nil {
+++                     if err := n.isolateNetwork(nwList, true); err != nil {
+++                             logrus.Warnf("Failed on restoring the inter-network iptables rules on cleanup: %v", err)
+++                     }
+++             }
+++     }()
+++
+++     // Remove inter-network communication rules.
+++     err = n.isolateNetwork(nwList, false)
+++     if err != nil {
+++             return err
+++     }
+++
+++     // Programming
+++     err = netlink.LinkDel(n.bridge.Link)
+++
+++     return err
+++}
+++
+++func addToBridge(ifaceName, bridgeName string) error {
+++     link, err := netlink.LinkByName(ifaceName)
+++     if err != nil {
+++             return fmt.Errorf("could not find interface %s: %v", ifaceName, err)
+++     }
+++     if err = netlink.LinkSetMaster(link,
+++             &netlink.Bridge{LinkAttrs: netlink.LinkAttrs{Name: bridgeName}}); err != nil {
+++             logrus.Debugf("Failed to add %s to bridge via netlink.Trying ioctl: %v", ifaceName, err)
+++             iface, err := net.InterfaceByName(ifaceName)
+++             if err != nil {
+++                     return fmt.Errorf("could not find network interface %s: %v", ifaceName, err)
+++             }
+++
+++             master, err := net.InterfaceByName(bridgeName)
+++             if err != nil {
+++                     return fmt.Errorf("could not find bridge %s: %v", bridgeName, err)
+++             }
+++
+++             return ioctlAddToBridge(iface, master)
+++     }
+++     return nil
+++}
+++
+++func setHairpinMode(link netlink.Link, enable bool) error {
+++     err := netlink.LinkSetHairpin(link, enable)
+++     if err != nil && err != syscall.EINVAL {
+++             // If error is not EINVAL something else went wrong, bail out right away
+++             return fmt.Errorf("unable to set hairpin mode on %s via netlink: %v",
+++                     link.Attrs().Name, err)
+++     }
+++
+++     // Hairpin mode successfully set up
+++     if err == nil {
+++             return nil
+++     }
+++
+++     // The netlink method failed with EINVAL which is probably because of an older
+++     // kernel. Try one more time via the sysfs method.
+++     path := filepath.Join("/sys/class/net", link.Attrs().Name, "brport/hairpin_mode")
+++
+++     var val []byte
+++     if enable {
+++             val = []byte{'1', '\n'}
+++     } else {
+++             val = []byte{'0', '\n'}
+++     }
+++
+++     if err := ioutil.WriteFile(path, val, 0644); err != nil {
+++             return fmt.Errorf("unable to set hairpin mode on %s via sysfs: %v", link.Attrs().Name, err)
+++     }
+++
+++     return nil
+++}
+++
+++func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error {
+++     var (
+++             ipv6Addr *net.IPNet
+++             err      error
+++     )
+++
+++     defer sandbox.InitOSContext()()
+++
+++     if epInfo == nil {
+++             return errors.New("invalid endpoint info passed")
+++     }
+++
+++     if len(epInfo.Interfaces()) != 0 {
+++             return errors.New("non empty interface list passed to bridge(local) driver")
+++     }
+++
+++     // Get the network handler and make sure it exists
+++     d.Lock()
+++     n, ok := d.networks[nid]
+++     d.Unlock()
+++
+++     if !ok {
+++             return types.NotFoundErrorf("network %s does not exist", nid)
+++     }
+++     if n == nil {
+++             return driverapi.ErrNoNetwork(nid)
+++     }
+++
+++     // Sanity check
+++     n.Lock()
+++     if n.id != nid {
+++             n.Unlock()
+++             return InvalidNetworkIDError(nid)
+++     }
+++     n.Unlock()
+++
+++     // Check if endpoint id is good and retrieve correspondent endpoint
+++     ep, err := n.getEndpoint(eid)
+++     if err != nil {
+++             return err
+++     }
+++
+++     // Endpoint with that id exists either on desired or other sandbox
+++     if ep != nil {
+++             return driverapi.ErrEndpointExists(eid)
+++     }
+++
+++     // Try to convert the options to endpoint configuration
+++     epConfig, err := parseEndpointOptions(epOptions)
+++     if err != nil {
+++             return err
+++     }
+++
+++     // Create and add the endpoint
+++     n.Lock()
+++     endpoint := &bridgeEndpoint{id: eid, config: epConfig}
+++     n.endpoints[eid] = endpoint
+++     n.Unlock()
+++
+++     // On failure make sure to remove the endpoint
+++     defer func() {
+++             if err != nil {
+++                     n.Lock()
+++                     delete(n.endpoints, eid)
+++                     n.Unlock()
+++             }
+++     }()
+++
+++     // Generate a name for what will be the host side pipe interface
+++     hostIfName, err := netutils.GenerateIfaceName(vethPrefix, vethLen)
+++     if err != nil {
+++             return err
+++     }
+++
+++     // Generate a name for what will be the sandbox side pipe interface
+++     containerIfName, err := netutils.GenerateIfaceName(vethPrefix, vethLen)
+++     if err != nil {
+++             return err
+++     }
+++
+++     // Generate and add the interface pipe host <-> sandbox
+++     veth := &netlink.Veth{
+++             LinkAttrs: netlink.LinkAttrs{Name: hostIfName, TxQLen: 0},
+++             PeerName:  containerIfName}
+++     if err = netlink.LinkAdd(veth); err != nil {
+++             return err
+++     }
+++
+++     // Get the host side pipe interface handler
+++     host, err := netlink.LinkByName(hostIfName)
+++     if err != nil {
+++             return err
+++     }
+++     defer func() {
+++             if err != nil {
+++                     netlink.LinkDel(host)
+++             }
+++     }()
+++
+++     // Get the sandbox side pipe interface handler
+++     sbox, err := netlink.LinkByName(containerIfName)
+++     if err != nil {
+++             return err
+++     }
+++     defer func() {
+++             if err != nil {
+++                     netlink.LinkDel(sbox)
+++             }
+++     }()
+++
+++     n.Lock()
+++     config := n.config
+++     n.Unlock()
+++
+++     // Add bridge inherited attributes to pipe interfaces
+++     if config.Mtu != 0 {
+++             err = netlink.LinkSetMTU(host, config.Mtu)
+++             if err != nil {
+++                     return err
+++             }
+++             err = netlink.LinkSetMTU(sbox, config.Mtu)
+++             if err != nil {
+++                     return err
+++             }
+++     }
+++
+++     // Attach host side pipe interface into the bridge
+++     if err = addToBridge(hostIfName, config.BridgeName); err != nil {
+++             return fmt.Errorf("adding interface %s to bridge %s failed: %v", hostIfName, config.BridgeName, err)
+++     }
+++
+++     if !config.EnableUserlandProxy {
+++             err = setHairpinMode(host, true)
+++             if err != nil {
+++                     return err
+++             }
+++     }
+++
+++     // v4 address for the sandbox side pipe interface
+++     ip4, err := ipAllocator.RequestIP(n.bridge.bridgeIPv4, nil)
+++     if err != nil {
+++             return err
+++     }
+++     ipv4Addr := &net.IPNet{IP: ip4, Mask: n.bridge.bridgeIPv4.Mask}
+++
+++     // Down the interface before configuring mac address.
+++     if err = netlink.LinkSetDown(sbox); err != nil {
+++             return fmt.Errorf("could not set link down for container interface %s: %v", containerIfName, err)
+++     }
+++
+++     // Set the sbox's MAC. If specified, use the one configured by user, otherwise generate one based on IP.
+++     mac := electMacAddress(epConfig, ip4)
+++     err = netlink.LinkSetHardwareAddr(sbox, mac)
+++     if err != nil {
+++             return fmt.Errorf("could not set mac address for container interface %s: %v", containerIfName, err)
+++     }
+++     endpoint.macAddress = mac
+++
+++     // Up the host interface after finishing all netlink configuration
+++     if err = netlink.LinkSetUp(host); err != nil {
+++             return fmt.Errorf("could not set link up for host interface %s: %v", hostIfName, err)
+++     }
+++
+++     // v6 address for the sandbox side pipe interface
+++     ipv6Addr = &net.IPNet{}
+++     if config.EnableIPv6 {
+++             var ip6 net.IP
+++
+++             network := n.bridge.bridgeIPv6
+++             if config.FixedCIDRv6 != nil {
+++                     network = config.FixedCIDRv6
+++             }
+++
+++             ones, _ := network.Mask.Size()
+++             if ones <= 80 {
+++                     ip6 = make(net.IP, len(network.IP))
+++                     copy(ip6, network.IP)
+++                     for i, h := range mac {
+++                             ip6[i+10] = h
+++                     }
+++             }
+++
+++             ip6, err := ipAllocator.RequestIP(network, ip6)
+++             if err != nil {
+++                     return err
+++             }
+++
+++             ipv6Addr = &net.IPNet{IP: ip6, Mask: network.Mask}
+++     }
+++
+++     // Create the sandbox side pipe interface
+++     endpoint.srcName = containerIfName
+++     endpoint.addr = ipv4Addr
+++
+++     if config.EnableIPv6 {
+++             endpoint.addrv6 = ipv6Addr
+++     }
+++
+++     err = epInfo.AddInterface(ifaceID, endpoint.macAddress, *ipv4Addr, *ipv6Addr)
+++     if err != nil {
+++             return err
+++     }
+++
+++     // Program any required port mapping and store them in the endpoint
+++     endpoint.portMapping, err = n.allocatePorts(epConfig, endpoint, config.DefaultBindingIP, config.EnableUserlandProxy)
+++     if err != nil {
+++             return err
+++     }
+++
+++     return nil
+++}
+++
+++func (d *driver) DeleteEndpoint(nid, eid types.UUID) error {
+++     var err error
+++
+++     defer sandbox.InitOSContext()()
+++
+++     // Get the network handler and make sure it exists
+++     d.Lock()
+++     n, ok := d.networks[nid]
+++     d.Unlock()
+++
+++     if !ok {
+++             return types.NotFoundErrorf("network %s does not exist", nid)
+++     }
+++     if n == nil {
+++             return driverapi.ErrNoNetwork(nid)
+++     }
+++
+++     // Sanity Check
+++     n.Lock()
+++     if n.id != nid {
+++             n.Unlock()
+++             return InvalidNetworkIDError(nid)
+++     }
+++     n.Unlock()
+++
+++     // Check endpoint id and if an endpoint is actually there
+++     ep, err := n.getEndpoint(eid)
+++     if err != nil {
+++             return err
+++     }
+++     if ep == nil {
+++             return EndpointNotFoundError(eid)
+++     }
+++
+++     // Remove it
+++     n.Lock()
+++     delete(n.endpoints, eid)
+++     n.Unlock()
+++
+++     // On failure make sure to set back ep in n.endpoints, but only
+++     // if it hasn't been taken over already by some other thread.
+++     defer func() {
+++             if err != nil {
+++                     n.Lock()
+++                     if _, ok := n.endpoints[eid]; !ok {
+++                             n.endpoints[eid] = ep
+++                     }
+++                     n.Unlock()
+++             }
+++     }()
+++
+++     // Remove port mappings. Do not stop endpoint delete on unmap failure
+++     n.releasePorts(ep)
+++
+++     // Release the v4 address allocated to this endpoint's sandbox interface
+++     err = ipAllocator.ReleaseIP(n.bridge.bridgeIPv4, ep.addr.IP)
+++     if err != nil {
+++             return err
+++     }
+++
+++     n.Lock()
+++     config := n.config
+++     n.Unlock()
+++
+++     // Release the v6 address allocated to this endpoint's sandbox interface
+++     if config.EnableIPv6 {
+++             network := n.bridge.bridgeIPv6
+++             if config.FixedCIDRv6 != nil {
+++                     network = config.FixedCIDRv6
+++             }
+++             err := ipAllocator.ReleaseIP(network, ep.addrv6.IP)
+++             if err != nil {
+++                     return err
+++             }
+++     }
+++
+++     // Try removal of link. Discard error: link pair might have
+++     // already been deleted by sandbox delete.
+++     link, err := netlink.LinkByName(ep.srcName)
+++     if err == nil {
+++             netlink.LinkDel(link)
+++     }
+++
+++     return nil
+++}
+++
+++func (d *driver) EndpointOperInfo(nid, eid types.UUID) (map[string]interface{}, error) {
+++     // Get the network handler and make sure it exists
+++     d.Lock()
+++     n, ok := d.networks[nid]
+++     d.Unlock()
+++     if !ok {
+++             return nil, types.NotFoundErrorf("network %s does not exist", nid)
+++     }
+++     if n == nil {
+++             return nil, driverapi.ErrNoNetwork(nid)
+++     }
+++
+++     // Sanity check
+++     n.Lock()
+++     if n.id != nid {
+++             n.Unlock()
+++             return nil, InvalidNetworkIDError(nid)
+++     }
+++     n.Unlock()
+++
+++     // Check if endpoint id is good and retrieve correspondent endpoint
+++     ep, err := n.getEndpoint(eid)
+++     if err != nil {
+++             return nil, err
+++     }
+++     if ep == nil {
+++             return nil, driverapi.ErrNoEndpoint(eid)
+++     }
+++
+++     m := make(map[string]interface{})
+++
+++     if ep.config.ExposedPorts != nil {
+++             // Return a copy of the config data
+++             epc := make([]types.TransportPort, 0, len(ep.config.ExposedPorts))
+++             for _, tp := range ep.config.ExposedPorts {
+++                     epc = append(epc, tp.GetCopy())
+++             }
+++             m[netlabel.ExposedPorts] = epc
+++     }
+++
+++     if ep.portMapping != nil {
+++             // Return a copy of the operational data
+++             pmc := make([]types.PortBinding, 0, len(ep.portMapping))
+++             for _, pm := range ep.portMapping {
+++                     pmc = append(pmc, pm.GetCopy())
+++             }
+++             m[netlabel.PortMap] = pmc
+++     }
+++
+++     if len(ep.macAddress) != 0 {
+++             m[netlabel.MacAddress] = ep.macAddress
+++     }
+++
+++     return m, nil
+++}
+++
+++// Join method is invoked when a Sandbox is attached to an endpoint.
+++func (d *driver) Join(nid, eid types.UUID, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+++     defer sandbox.InitOSContext()()
+++
+++     network, err := d.getNetwork(nid)
+++     if err != nil {
+++             return err
+++     }
+++
+++     endpoint, err := network.getEndpoint(eid)
+++     if err != nil {
+++             return err
+++     }
+++
+++     if endpoint == nil {
+++             return EndpointNotFoundError(eid)
+++     }
+++
+++     for _, iNames := range jinfo.InterfaceNames() {
+++             // Make sure to set names on the correct interface ID.
+++             if iNames.ID() == ifaceID {
+++                     err = iNames.SetNames(endpoint.srcName, containerVethPrefix)
+++                     if err != nil {
+++                             return err
+++                     }
+++             }
+++     }
+++
+++     err = jinfo.SetGateway(network.bridge.gatewayIPv4)
+++     if err != nil {
+++             return err
+++     }
+++
+++     err = jinfo.SetGatewayIPv6(network.bridge.gatewayIPv6)
+++     if err != nil {
+++             return err
+++     }
+++
+++     if !network.config.EnableICC {
+++             return d.link(network, endpoint, options, true)
+++     }
+++
+++     return nil
+++}
+++
+++// Leave method is invoked when a Sandbox detaches from an endpoint.
+++func (d *driver) Leave(nid, eid types.UUID) error {
+++     defer sandbox.InitOSContext()()
+++
+++     network, err := d.getNetwork(nid)
+++     if err != nil {
+++             return err
+++     }
+++
+++     endpoint, err := network.getEndpoint(eid)
+++     if err != nil {
+++             return err
+++     }
+++
+++     if endpoint == nil {
+++             return EndpointNotFoundError(eid)
+++     }
+++
+++     if !network.config.EnableICC {
+++             return d.link(network, endpoint, nil, false)
+++     }
+++
+++     return nil
+++}
+++
+++func (d *driver) link(network *bridgeNetwork, endpoint *bridgeEndpoint, options map[string]interface{}, enable bool) error {
+++     var (
+++             cc  *containerConfiguration
+++             err error
+++     )
+++
+++     if enable {
+++             cc, err = parseContainerOptions(options)
+++             if err != nil {
+++                     return err
+++             }
+++     } else {
+++             cc = endpoint.containerConfig
+++     }
+++
+++     if cc == nil {
+++             return nil
+++     }
+++
+++     if endpoint.config != nil && endpoint.config.ExposedPorts != nil {
+++             for _, p := range cc.ParentEndpoints {
+++                     var parentEndpoint *bridgeEndpoint
+++                     parentEndpoint, err = network.getEndpoint(types.UUID(p))
+++                     if err != nil {
+++                             return err
+++                     }
+++                     if parentEndpoint == nil {
+++                             err = InvalidEndpointIDError(p)
+++                             return err
+++                     }
+++
+++                     l := newLink(parentEndpoint.addr.IP.String(),
+++                             endpoint.addr.IP.String(),
+++                             endpoint.config.ExposedPorts, network.config.BridgeName)
+++                     if enable {
+++                             err = l.Enable()
+++                             if err != nil {
+++                                     return err
+++                             }
+++                             defer func() {
+++                                     if err != nil {
+++                                             l.Disable()
+++                                     }
+++                             }()
+++                     } else {
+++                             l.Disable()
+++                     }
+++             }
+++     }
+++
+++     for _, c := range cc.ChildEndpoints {
+++             var childEndpoint *bridgeEndpoint
+++             childEndpoint, err = network.getEndpoint(types.UUID(c))
+++             if err != nil {
+++                     return err
+++             }
+++             if childEndpoint == nil {
+++                     err = InvalidEndpointIDError(c)
+++                     return err
+++             }
+++             if childEndpoint.config == nil || childEndpoint.config.ExposedPorts == nil {
+++                     continue
+++             }
+++
+++             l := newLink(endpoint.addr.IP.String(),
+++                     childEndpoint.addr.IP.String(),
+++                     childEndpoint.config.ExposedPorts, network.config.BridgeName)
+++             if enable {
+++                     err = l.Enable()
+++                     if err != nil {
+++                             return err
+++                     }
+++                     defer func() {
+++                             if err != nil {
+++                                     l.Disable()
+++                             }
+++                     }()
+++             } else {
+++                     l.Disable()
+++             }
+++     }
+++
+++     if enable {
+++             endpoint.containerConfig = cc
+++     }
+++
+++     return nil
+++}
+++
+++func (d *driver) Type() string {
+++     return networkType
+++}
+++
+++func parseEndpointOptions(epOptions map[string]interface{}) (*endpointConfiguration, error) {
+++     if epOptions == nil {
+++             return nil, nil
+++     }
+++
+++     ec := &endpointConfiguration{}
+++
+++     if opt, ok := epOptions[netlabel.MacAddress]; ok {
+++             if mac, ok := opt.(net.HardwareAddr); ok {
+++                     ec.MacAddress = mac
+++             } else {
+++                     return nil, &ErrInvalidEndpointConfig{}
+++             }
+++     }
+++
+++     if opt, ok := epOptions[netlabel.PortMap]; ok {
+++             if bs, ok := opt.([]types.PortBinding); ok {
+++                     ec.PortBindings = bs
+++             } else {
+++                     return nil, &ErrInvalidEndpointConfig{}
+++             }
+++     }
+++
+++     if opt, ok := epOptions[netlabel.ExposedPorts]; ok {
+++             if ports, ok := opt.([]types.TransportPort); ok {
+++                     ec.ExposedPorts = ports
+++             } else {
+++                     return nil, &ErrInvalidEndpointConfig{}
+++             }
+++     }
+++
+++     return ec, nil
+++}
+++
+++func parseContainerOptions(cOptions map[string]interface{}) (*containerConfiguration, error) {
+++     if cOptions == nil {
+++             return nil, nil
+++     }
+++     genericData := cOptions[netlabel.GenericData]
+++     if genericData == nil {
+++             return nil, nil
+++     }
+++     switch opt := genericData.(type) {
+++     case options.Generic:
+++             opaqueConfig, err := options.GenerateFromModel(opt, &containerConfiguration{})
+++             if err != nil {
+++                     return nil, err
+++             }
+++             return opaqueConfig.(*containerConfiguration), nil
+++     case *containerConfiguration:
+++             return opt, nil
+++     default:
+++             return nil, nil
+++     }
+++}
+++
+++// Generate a IEEE802 compliant MAC address from the given IP address.
+++//
+++// The generator is guaranteed to be consistent: the same IP will always yield the same
+++// MAC address. This is to avoid ARP cache issues.
+++func generateMacAddr(ip net.IP) net.HardwareAddr {
+++     hw := make(net.HardwareAddr, 6)
+++
+++     // The first byte of the MAC address has to comply with these rules:
+++     // 1. Unicast: Set the least-significant bit to 0.
+++     // 2. Address is locally administered: Set the second-least-significant bit (U/L) to 1.
+++     // 3. As "small" as possible: The veth address has to be "smaller" than the bridge address.
+++     hw[0] = 0x02
+++
+++     // The first 24 bits of the MAC represent the Organizationally Unique Identifier (OUI).
+++     // Since this address is locally administered, we can do whatever we want as long as
+++     // it doesn't conflict with other addresses.
+++     hw[1] = 0x42
+++
+++     // Insert the IP address into the last 32 bits of the MAC address.
+++     // This is a simple way to guarantee the address will be consistent and unique.
+++     copy(hw[2:], ip.To4())
+++
+++     return hw
+++}
+++
+++func electMacAddress(epConfig *endpointConfiguration, ip net.IP) net.HardwareAddr {
+++     if epConfig != nil && epConfig.MacAddress != nil {
+++             return epConfig.MacAddress
+++     }
+++     return generateMacAddr(ip)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..94f820e65f9cd15ddbd3d1214e1796a253393416
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,644 @@@@
+++package bridge
+++
+++import (
+++     "bytes"
+++     "fmt"
+++     "net"
+++     "regexp"
+++     "testing"
+++
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/iptables"
+++     "github.com/docker/libnetwork/netlabel"
+++     "github.com/docker/libnetwork/sandbox"
+++     "github.com/docker/libnetwork/types"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++func TestCreateFullOptions(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++     d := newDriver()
+++
+++     config := &configuration{
+++             EnableIPForwarding: true,
+++     }
+++
+++     // Test this scenario: Default gw address does not belong to
+++     // container network and it's greater than bridge address
+++     cip, cnw, _ := net.ParseCIDR("172.16.122.0/24")
+++     cnw.IP = cip
+++     ip, nw, _ := net.ParseCIDR("172.16.0.10/16")
+++     nw.IP = ip
+++     gw := net.ParseIP("172.16.0.1")
+++
+++     netConfig := &networkConfiguration{
+++             BridgeName:         DefaultBridgeName,
+++             AddressIPv4:        nw,
+++             FixedCIDR:          cnw,
+++             DefaultGatewayIPv4: gw,
+++             EnableIPv6:         true,
+++             EnableIPTables:     true,
+++     }
+++     _, netConfig.FixedCIDRv6, _ = net.ParseCIDR("2001:db8::/48")
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = config
+++
+++     if err := d.Config(genericOption); err != nil {
+++             t.Fatalf("Failed to setup driver config: %v", err)
+++     }
+++
+++     netOption := make(map[string]interface{})
+++     netOption[netlabel.GenericData] = netConfig
+++
+++     err := d.CreateNetwork("dummy", netOption)
+++     if err != nil {
+++             t.Fatalf("Failed to create bridge: %v", err)
+++     }
+++
+++     // Verify the IP address allocated for the endpoint belongs to the container network
+++     epOptions := make(map[string]interface{})
+++     te := &testEndpoint{ifaces: []*testInterface{}}
+++     err = d.CreateEndpoint("dummy", "ep1", te, epOptions)
+++     if err != nil {
+++             t.Fatalf("Failed to create an endpoint : %s", err.Error())
+++     }
+++     if !cnw.Contains(te.Interfaces()[0].Address().IP) {
+++             t.Fatalf("endpoint got assigned address outside of container network(%s): %s", cnw.String(), te.Interfaces()[0].Address())
+++     }
+++}
+++
+++func TestCreate(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++     d := newDriver()
+++
+++     config := &networkConfiguration{BridgeName: DefaultBridgeName}
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = config
+++
+++     if err := d.CreateNetwork("dummy", genericOption); err != nil {
+++             t.Fatalf("Failed to create bridge: %v", err)
+++     }
+++
+++     err := d.CreateNetwork("dummy", genericOption)
+++     if err == nil {
+++             t.Fatalf("Expected bridge driver to refuse creation of second network with default name")
+++     }
+++     if _, ok := err.(types.ForbiddenError); !ok {
+++             t.Fatalf("Creation of second network with default name failed with unexpected error type")
+++     }
+++
+++     err = d.DeleteNetwork("dummy")
+++     if err == nil {
+++             t.Fatalf("deletion of network with default name should fail on this driver")
+++     }
+++     if _, ok := err.(types.ForbiddenError); !ok {
+++             t.Fatalf("deletion of network with default name failed with unexpected error type")
+++     }
+++}
+++
+++func TestCreateFail(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++     d := newDriver()
+++
+++     config := &networkConfiguration{BridgeName: "dummy0"}
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = config
+++
+++     if err := d.CreateNetwork("dummy", genericOption); err == nil {
+++             t.Fatal("Bridge creation was expected to fail")
+++     }
+++}
+++
+++func TestCreateMultipleNetworks(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++     d := newDriver()
+++     dd, _ := d.(*driver)
+++
+++     config1 := &networkConfiguration{BridgeName: "net_test_1", AllowNonDefaultBridge: true, EnableIPTables: true}
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = config1
+++     if err := d.CreateNetwork("1", genericOption); err != nil {
+++             t.Fatalf("Failed to create bridge: %v", err)
+++     }
+++
+++     config2 := &networkConfiguration{BridgeName: "net_test_2", AllowNonDefaultBridge: true, EnableIPTables: true}
+++     genericOption[netlabel.GenericData] = config2
+++     if err := d.CreateNetwork("2", genericOption); err != nil {
+++             t.Fatalf("Failed to create bridge: %v", err)
+++     }
+++
+++     config3 := &networkConfiguration{BridgeName: "net_test_3", AllowNonDefaultBridge: true, EnableIPTables: true}
+++     genericOption[netlabel.GenericData] = config3
+++     if err := d.CreateNetwork("3", genericOption); err != nil {
+++             t.Fatalf("Failed to create bridge: %v", err)
+++     }
+++
+++     // Verify the network isolation rules are installed, each network subnet should appear 4 times
+++     verifyV4INCEntries(dd.networks, 4, t)
+++
+++     config4 := &networkConfiguration{BridgeName: "net_test_4", AllowNonDefaultBridge: true, EnableIPTables: true}
+++     genericOption[netlabel.GenericData] = config4
+++     if err := d.CreateNetwork("4", genericOption); err != nil {
+++             t.Fatalf("Failed to create bridge: %v", err)
+++     }
+++
+++     // Now 6 times
+++     verifyV4INCEntries(dd.networks, 6, t)
+++
+++     d.DeleteNetwork("1")
+++     verifyV4INCEntries(dd.networks, 4, t)
+++
+++     d.DeleteNetwork("2")
+++     verifyV4INCEntries(dd.networks, 2, t)
+++
+++     d.DeleteNetwork("3")
+++     verifyV4INCEntries(dd.networks, 0, t)
+++
+++     d.DeleteNetwork("4")
+++     verifyV4INCEntries(dd.networks, 0, t)
+++}
+++
+++func verifyV4INCEntries(networks map[types.UUID]*bridgeNetwork, numEntries int, t *testing.T) {
+++     out, err := iptables.Raw("-L", "FORWARD")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     for _, nw := range networks {
+++             nt := types.GetIPNetCopy(nw.bridge.bridgeIPv4)
+++             nt.IP = nt.IP.Mask(nt.Mask)
+++             re := regexp.MustCompile(nt.String())
+++             matches := re.FindAllString(string(out[:]), -1)
+++             if len(matches) != numEntries {
+++                     t.Fatalf("Cannot find expected inter-network isolation rules in IP Tables:\n%s", string(out[:]))
+++             }
+++     }
+++}
+++
+++type testInterface struct {
+++     id      int
+++     mac     net.HardwareAddr
+++     addr    net.IPNet
+++     addrv6  net.IPNet
+++     srcName string
+++     dstName string
+++}
+++
+++type testEndpoint struct {
+++     ifaces         []*testInterface
+++     gw             net.IP
+++     gw6            net.IP
+++     hostsPath      string
+++     resolvConfPath string
+++     routes         []types.StaticRoute
+++}
+++
+++func (te *testEndpoint) Interfaces() []driverapi.InterfaceInfo {
+++     iList := make([]driverapi.InterfaceInfo, len(te.ifaces))
+++
+++     for i, iface := range te.ifaces {
+++             iList[i] = iface
+++     }
+++
+++     return iList
+++}
+++
+++func (te *testEndpoint) AddInterface(id int, mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error {
+++     iface := &testInterface{id: id, addr: ipv4, addrv6: ipv6}
+++     te.ifaces = append(te.ifaces, iface)
+++     return nil
+++}
+++
+++func (i *testInterface) ID() int {
+++     return i.id
+++}
+++
+++func (i *testInterface) MacAddress() net.HardwareAddr {
+++     return i.mac
+++}
+++
+++func (i *testInterface) Address() net.IPNet {
+++     return i.addr
+++}
+++
+++func (i *testInterface) AddressIPv6() net.IPNet {
+++     return i.addrv6
+++}
+++
+++func (i *testInterface) SetNames(srcName string, dstName string) error {
+++     i.srcName = srcName
+++     i.dstName = dstName
+++     return nil
+++}
+++
+++func (te *testEndpoint) InterfaceNames() []driverapi.InterfaceNameInfo {
+++     iList := make([]driverapi.InterfaceNameInfo, len(te.ifaces))
+++
+++     for i, iface := range te.ifaces {
+++             iList[i] = iface
+++     }
+++
+++     return iList
+++}
+++
+++func (te *testEndpoint) SetGateway(gw net.IP) error {
+++     te.gw = gw
+++     return nil
+++}
+++
+++func (te *testEndpoint) SetGatewayIPv6(gw6 net.IP) error {
+++     te.gw6 = gw6
+++     return nil
+++}
+++
+++func (te *testEndpoint) SetHostsPath(path string) error {
+++     te.hostsPath = path
+++     return nil
+++}
+++
+++func (te *testEndpoint) SetResolvConfPath(path string) error {
+++     te.resolvConfPath = path
+++     return nil
+++}
+++
+++func (te *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP, interfaceID int) error {
+++     te.routes = append(te.routes, types.StaticRoute{Destination: destination, RouteType: routeType, NextHop: nextHop, InterfaceID: interfaceID})
+++     return nil
+++}
+++
+++func TestQueryEndpointInfo(t *testing.T) {
+++     testQueryEndpointInfo(t, true)
+++}
+++
+++func TestQueryEndpointInfoHairpin(t *testing.T) {
+++     testQueryEndpointInfo(t, false)
+++}
+++
+++func testQueryEndpointInfo(t *testing.T, ulPxyEnabled bool) {
+++     defer sandbox.SetupTestOSContext(t)()
+++     d := newDriver()
+++     dd, _ := d.(*driver)
+++
+++     config := &networkConfiguration{
+++             BridgeName:          DefaultBridgeName,
+++             EnableIPTables:      true,
+++             EnableICC:           false,
+++             EnableUserlandProxy: ulPxyEnabled,
+++     }
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = config
+++
+++     err := d.CreateNetwork("net1", genericOption)
+++     if err != nil {
+++             t.Fatalf("Failed to create bridge: %v", err)
+++     }
+++
+++     portMappings := getPortMapping()
+++     epOptions := make(map[string]interface{})
+++     epOptions[netlabel.PortMap] = portMappings
+++
+++     te := &testEndpoint{ifaces: []*testInterface{}}
+++     err = d.CreateEndpoint("net1", "ep1", te, epOptions)
+++     if err != nil {
+++             t.Fatalf("Failed to create an endpoint : %s", err.Error())
+++     }
+++
+++     network, ok := dd.networks["net1"]
+++     if !ok {
+++             t.Fatalf("Cannot find network %s inside driver", "net1")
+++     }
+++     ep, _ := network.endpoints["ep1"]
+++     data, err := d.EndpointOperInfo(network.id, ep.id)
+++     if err != nil {
+++             t.Fatalf("Failed to ask for endpoint operational data:  %v", err)
+++     }
+++     pmd, ok := data[netlabel.PortMap]
+++     if !ok {
+++             t.Fatalf("Endpoint operational data does not contain port mapping data")
+++     }
+++     pm, ok := pmd.([]types.PortBinding)
+++     if !ok {
+++             t.Fatalf("Unexpected format for port mapping in endpoint operational data")
+++     }
+++     if len(ep.portMapping) != len(pm) {
+++             t.Fatalf("Incomplete data for port mapping in endpoint operational data")
+++     }
+++     for i, pb := range ep.portMapping {
+++             if !pb.Equal(&pm[i]) {
+++                     t.Fatalf("Unexpected data for port mapping in endpoint operational data")
+++             }
+++     }
+++
+++     // Cleanup as host ports are there
+++     err = network.releasePorts(ep)
+++     if err != nil {
+++             t.Fatalf("Failed to release mapped ports: %v", err)
+++     }
+++}
+++
+++func TestCreateLinkWithOptions(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++     d := newDriver()
+++
+++     config := &networkConfiguration{BridgeName: DefaultBridgeName}
+++     netOptions := make(map[string]interface{})
+++     netOptions[netlabel.GenericData] = config
+++
+++     err := d.CreateNetwork("net1", netOptions)
+++     if err != nil {
+++             t.Fatalf("Failed to create bridge: %v", err)
+++     }
+++
+++     mac := net.HardwareAddr([]byte{0x1e, 0x67, 0x66, 0x44, 0x55, 0x66})
+++     epOptions := make(map[string]interface{})
+++     epOptions[netlabel.MacAddress] = mac
+++
+++     te := &testEndpoint{ifaces: []*testInterface{}}
+++     err = d.CreateEndpoint("net1", "ep", te, epOptions)
+++     if err != nil {
+++             t.Fatalf("Failed to create an endpoint: %s", err.Error())
+++     }
+++
+++     err = d.Join("net1", "ep", "sbox", te, nil)
+++     if err != nil {
+++             t.Fatalf("Failed to join the endpoint: %v", err)
+++     }
+++
+++     ifaceName := te.ifaces[0].srcName
+++     veth, err := netlink.LinkByName(ifaceName)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if !bytes.Equal(mac, veth.Attrs().HardwareAddr) {
+++             t.Fatalf("Failed to parse and program endpoint configuration")
+++     }
+++}
+++
+++func getExposedPorts() []types.TransportPort {
+++     return []types.TransportPort{
+++             types.TransportPort{Proto: types.TCP, Port: uint16(5000)},
+++             types.TransportPort{Proto: types.UDP, Port: uint16(400)},
+++             types.TransportPort{Proto: types.TCP, Port: uint16(600)},
+++     }
+++}
+++
+++func getPortMapping() []types.PortBinding {
+++     return []types.PortBinding{
+++             types.PortBinding{Proto: types.TCP, Port: uint16(230), HostPort: uint16(23000)},
+++             types.PortBinding{Proto: types.UDP, Port: uint16(200), HostPort: uint16(22000)},
+++             types.PortBinding{Proto: types.TCP, Port: uint16(120), HostPort: uint16(12000)},
+++     }
+++}
+++
+++func TestLinkContainers(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     d := newDriver()
+++
+++     config := &networkConfiguration{
+++             BridgeName:     DefaultBridgeName,
+++             EnableIPTables: true,
+++             EnableICC:      false,
+++     }
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = config
+++
+++     err := d.CreateNetwork("net1", genericOption)
+++     if err != nil {
+++             t.Fatalf("Failed to create bridge: %v", err)
+++     }
+++
+++     exposedPorts := getExposedPorts()
+++     epOptions := make(map[string]interface{})
+++     epOptions[netlabel.ExposedPorts] = exposedPorts
+++
+++     te1 := &testEndpoint{ifaces: []*testInterface{}}
+++     err = d.CreateEndpoint("net1", "ep1", te1, epOptions)
+++     if err != nil {
+++             t.Fatalf("Failed to create an endpoint : %s", err.Error())
+++     }
+++
+++     addr1 := te1.ifaces[0].addr
+++     if addr1.IP.To4() == nil {
+++             t.Fatalf("No Ipv4 address assigned to the endpoint:  ep1")
+++     }
+++
+++     te2 := &testEndpoint{ifaces: []*testInterface{}}
+++     err = d.CreateEndpoint("net1", "ep2", te2, nil)
+++     if err != nil {
+++             t.Fatalf("Failed to create an endpoint : %s", err.Error())
+++     }
+++
+++     addr2 := te2.ifaces[0].addr
+++     if addr2.IP.To4() == nil {
+++             t.Fatalf("No Ipv4 address assigned to the endpoint:  ep2")
+++     }
+++
+++     ce := []string{"ep1"}
+++     cConfig := &containerConfiguration{ChildEndpoints: ce}
+++     genericOption = make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = cConfig
+++
+++     err = d.Join("net1", "ep2", "", te2, genericOption)
+++     if err != nil {
+++             t.Fatalf("Failed to link ep1 and ep2")
+++     }
+++
+++     out, err := iptables.Raw("-L", DockerChain)
+++     for _, pm := range exposedPorts {
+++             regex := fmt.Sprintf("%s dpt:%d", pm.Proto.String(), pm.Port)
+++             re := regexp.MustCompile(regex)
+++             matches := re.FindAllString(string(out[:]), -1)
+++             if len(matches) != 1 {
+++                     t.Fatalf("IP Tables programming failed %s", string(out[:]))
+++             }
+++
+++             regex = fmt.Sprintf("%s spt:%d", pm.Proto.String(), pm.Port)
+++             matched, _ := regexp.MatchString(regex, string(out[:]))
+++             if !matched {
+++                     t.Fatalf("IP Tables programming failed %s", string(out[:]))
+++             }
+++     }
+++
+++     err = d.Leave("net1", "ep2")
+++     if err != nil {
+++             t.Fatalf("Failed to unlink ep1 and ep2")
+++     }
+++
+++     out, err = iptables.Raw("-L", DockerChain)
+++     for _, pm := range exposedPorts {
+++             regex := fmt.Sprintf("%s dpt:%d", pm.Proto.String(), pm.Port)
+++             re := regexp.MustCompile(regex)
+++             matches := re.FindAllString(string(out[:]), -1)
+++             if len(matches) != 0 {
+++                     t.Fatalf("Leave should have deleted relevant IPTables rules  %s", string(out[:]))
+++             }
+++
+++             regex = fmt.Sprintf("%s spt:%d", pm.Proto.String(), pm.Port)
+++             matched, _ := regexp.MatchString(regex, string(out[:]))
+++             if matched {
+++                     t.Fatalf("Leave should have deleted relevant IPTables rules  %s", string(out[:]))
+++             }
+++     }
+++
+++     // Error condition test with an invalid endpoint-id "ep4"
+++     ce = []string{"ep1", "ep4"}
+++     cConfig = &containerConfiguration{ChildEndpoints: ce}
+++     genericOption = make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = cConfig
+++
+++     err = d.Join("net1", "ep2", "", te2, genericOption)
+++     if err != nil {
+++             out, err = iptables.Raw("-L", DockerChain)
+++             for _, pm := range exposedPorts {
+++                     regex := fmt.Sprintf("%s dpt:%d", pm.Proto.String(), pm.Port)
+++                     re := regexp.MustCompile(regex)
+++                     matches := re.FindAllString(string(out[:]), -1)
+++                     if len(matches) != 0 {
+++                             t.Fatalf("Error handling should rollback relevant IPTables rules  %s", string(out[:]))
+++                     }
+++
+++                     regex = fmt.Sprintf("%s spt:%d", pm.Proto.String(), pm.Port)
+++                     matched, _ := regexp.MatchString(regex, string(out[:]))
+++                     if matched {
+++                             t.Fatalf("Error handling should rollback relevant IPTables rules  %s", string(out[:]))
+++                     }
+++             }
+++     } else {
+++             t.Fatalf("Expected Join to fail given link conditions are not satisfied")
+++     }
+++}
+++
+++func TestValidateConfig(t *testing.T) {
+++
+++     // Test mtu
+++     c := networkConfiguration{Mtu: -2}
+++     err := c.Validate()
+++     if err == nil {
+++             t.Fatalf("Failed to detect invalid MTU number")
+++     }
+++
+++     c.Mtu = 9000
+++     err = c.Validate()
+++     if err != nil {
+++             t.Fatalf("unexpected validation error on MTU number")
+++     }
+++
+++     // Bridge network
+++     _, network, _ := net.ParseCIDR("172.28.0.0/16")
+++
+++     // Test FixedCIDR
+++     _, containerSubnet, _ := net.ParseCIDR("172.27.0.0/16")
+++     c = networkConfiguration{
+++             AddressIPv4: network,
+++             FixedCIDR:   containerSubnet,
+++     }
+++
+++     err = c.Validate()
+++     if err == nil {
+++             t.Fatalf("Failed to detect invalid FixedCIDR network")
+++     }
+++
+++     _, containerSubnet, _ = net.ParseCIDR("172.28.0.0/16")
+++     c.FixedCIDR = containerSubnet
+++     err = c.Validate()
+++     if err != nil {
+++             t.Fatalf("Unexpected validation error on FixedCIDR network")
+++     }
+++
+++     _, containerSubnet, _ = net.ParseCIDR("172.28.0.0/15")
+++     c.FixedCIDR = containerSubnet
+++     err = c.Validate()
+++     if err == nil {
+++             t.Fatalf("Failed to detect invalid FixedCIDR network")
+++     }
+++
+++     _, containerSubnet, _ = net.ParseCIDR("172.28.0.0/17")
+++     c.FixedCIDR = containerSubnet
+++     err = c.Validate()
+++     if err != nil {
+++             t.Fatalf("Unexpected validation error on FixedCIDR network")
+++     }
+++
+++     // Test v4 gw
+++     c.DefaultGatewayIPv4 = net.ParseIP("172.27.30.234")
+++     err = c.Validate()
+++     if err == nil {
+++             t.Fatalf("Failed to detect invalid default gateway")
+++     }
+++
+++     c.DefaultGatewayIPv4 = net.ParseIP("172.28.30.234")
+++     err = c.Validate()
+++     if err != nil {
+++             t.Fatalf("Unexpected validation error on default gateway")
+++     }
+++
+++     // Test v6 gw
+++     _, containerSubnet, _ = net.ParseCIDR("2001:1234:ae:b004::/64")
+++     c = networkConfiguration{
+++             EnableIPv6:         true,
+++             FixedCIDRv6:        containerSubnet,
+++             DefaultGatewayIPv6: net.ParseIP("2001:1234:ac:b004::bad:a55"),
+++     }
+++     err = c.Validate()
+++     if err == nil {
+++             t.Fatalf("Failed to detect invalid v6 default gateway")
+++     }
+++
+++     c.DefaultGatewayIPv6 = net.ParseIP("2001:1234:ae:b004::bad:a55")
+++     err = c.Validate()
+++     if err != nil {
+++             t.Fatalf("Unexpected validation error on v6 default gateway")
+++     }
+++
+++     c.FixedCIDRv6 = nil
+++     err = c.Validate()
+++     if err == nil {
+++             t.Fatalf("Failed to detect invalid v6 default gateway")
+++     }
+++}
+++
+++func TestSetDefaultGw(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++     d := newDriver()
+++
+++     _, subnetv6, _ := net.ParseCIDR("2001:db8:ea9:9abc:b0c4::/80")
+++     gw4 := bridgeNetworks[0].IP.To4()
+++     gw4[3] = 254
+++     gw6 := net.ParseIP("2001:db8:ea9:9abc:b0c4::254")
+++
+++     config := &networkConfiguration{
+++             BridgeName:         DefaultBridgeName,
+++             EnableIPv6:         true,
+++             FixedCIDRv6:        subnetv6,
+++             DefaultGatewayIPv4: gw4,
+++             DefaultGatewayIPv6: gw6,
+++     }
+++
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = config
+++
+++     err := d.CreateNetwork("dummy", genericOption)
+++     if err != nil {
+++             t.Fatalf("Failed to create bridge: %v", err)
+++     }
+++
+++     te := &testEndpoint{ifaces: []*testInterface{}}
+++     err = d.CreateEndpoint("dummy", "ep", te, nil)
+++     if err != nil {
+++             t.Fatalf("Failed to create endpoint: %v", err)
+++     }
+++
+++     err = d.Join("dummy", "ep", "sbox", te, nil)
+++     if err != nil {
+++             t.Fatalf("Failed to join endpoint: %v", err)
+++     }
+++
+++     if !gw4.Equal(te.gw) {
+++             t.Fatalf("Failed to configure default gateway. Expected %v. Found %v", gw4, te.gw)
+++     }
+++
+++     if !gw6.Equal(te.gw6) {
+++             t.Fatalf("Failed to configure default gateway. Expected %v. Found %v", gw6, te.gw6)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d22912c5c7cd031b9cfa40babfec4c83fd521ab1
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,341 @@@@
+++package bridge
+++
+++import (
+++     "fmt"
+++     "net"
+++)
+++
+++// ErrConfigExists error is returned when driver already has a config applied.
+++type ErrConfigExists struct{}
+++
+++func (ece *ErrConfigExists) Error() string {
+++     return "configuration already exists, bridge configuration can be applied only once"
+++}
+++
+++// Forbidden denotes the type of this error
+++func (ece *ErrConfigExists) Forbidden() {}
+++
+++// ErrInvalidDriverConfig error is returned when Bridge Driver is passed an invalid config
+++type ErrInvalidDriverConfig struct{}
+++
+++func (eidc *ErrInvalidDriverConfig) Error() string {
+++     return "Invalid configuration passed to Bridge Driver"
+++}
+++
+++// BadRequest denotes the type of this error
+++func (eidc *ErrInvalidDriverConfig) BadRequest() {}
+++
+++// ErrInvalidNetworkConfig error is returned when a network is created on a driver without valid config.
+++type ErrInvalidNetworkConfig struct{}
+++
+++func (einc *ErrInvalidNetworkConfig) Error() string {
+++     return "trying to create a network on a driver without valid config"
+++}
+++
+++// Forbidden denotes the type of this error
+++func (einc *ErrInvalidNetworkConfig) Forbidden() {}
+++
+++// ErrInvalidContainerConfig error is returned when a endpoint create is attempted with an invalid configuration.
+++type ErrInvalidContainerConfig struct{}
+++
+++func (eicc *ErrInvalidContainerConfig) Error() string {
+++     return "Error in joining a container due to invalid configuration"
+++}
+++
+++// BadRequest denotes the type of this error
+++func (eicc *ErrInvalidContainerConfig) BadRequest() {}
+++
+++// ErrInvalidEndpointConfig error is returned when a endpoint create is attempted with an invalid endpoint configuration.
+++type ErrInvalidEndpointConfig struct{}
+++
+++func (eiec *ErrInvalidEndpointConfig) Error() string {
+++     return "trying to create an endpoint with an invalid endpoint configuration"
+++}
+++
+++// BadRequest denotes the type of this error
+++func (eiec *ErrInvalidEndpointConfig) BadRequest() {}
+++
+++// ErrNetworkExists error is returned when a network already exists and another network is created.
+++type ErrNetworkExists struct{}
+++
+++func (ene *ErrNetworkExists) Error() string {
+++     return "network already exists, bridge can only have one network"
+++}
+++
+++// Forbidden denotes the type of this error
+++func (ene *ErrNetworkExists) Forbidden() {}
+++
+++// ErrIfaceName error is returned when a new name could not be generated.
+++type ErrIfaceName struct{}
+++
+++func (ein *ErrIfaceName) Error() string {
+++     return "failed to find name for new interface"
+++}
+++
+++// InternalError denotes the type of this error
+++func (ein *ErrIfaceName) InternalError() {}
+++
+++// ErrNoIPAddr error is returned when bridge has no IPv4 address configured.
+++type ErrNoIPAddr struct{}
+++
+++func (enip *ErrNoIPAddr) Error() string {
+++     return "bridge has no IPv4 address configured"
+++}
+++
+++// InternalError denotes the type of this error
+++func (enip *ErrNoIPAddr) InternalError() {}
+++
+++// ErrInvalidGateway is returned when the user provided default gateway (v4/v6) is not not valid.
+++type ErrInvalidGateway struct{}
+++
+++func (eig *ErrInvalidGateway) Error() string {
+++     return "default gateway ip must be part of the network"
+++}
+++
+++// BadRequest denotes the type of this error
+++func (eig *ErrInvalidGateway) BadRequest() {}
+++
+++// ErrInvalidContainerSubnet is returned when the container subnet (FixedCIDR) is not valid.
+++type ErrInvalidContainerSubnet struct{}
+++
+++func (eis *ErrInvalidContainerSubnet) Error() string {
+++     return "container subnet must be a subset of bridge network"
+++}
+++
+++// BadRequest denotes the type of this error
+++func (eis *ErrInvalidContainerSubnet) BadRequest() {}
+++
+++// ErrInvalidMtu is returned when the user provided MTU is not valid.
+++type ErrInvalidMtu int
+++
+++func (eim ErrInvalidMtu) Error() string {
+++     return fmt.Sprintf("invalid MTU number: %d", int(eim))
+++}
+++
+++// BadRequest denotes the type of this error
+++func (eim ErrInvalidMtu) BadRequest() {}
+++
+++// ErrIPFwdCfg is returned when ip forwarding setup is invoked when the configuration
+++// not enabled.
+++type ErrIPFwdCfg struct{}
+++
+++func (eipf *ErrIPFwdCfg) Error() string {
+++     return "unexpected request to enable IP Forwarding"
+++}
+++
+++// BadRequest denotes the type of this error
+++func (eipf *ErrIPFwdCfg) BadRequest() {}
+++
+++// ErrInvalidPort is returned when the container or host port specified in the port binding is not valid.
+++type ErrInvalidPort string
+++
+++func (ip ErrInvalidPort) Error() string {
+++     return fmt.Sprintf("invalid transport port: %s", string(ip))
+++}
+++
+++// BadRequest denotes the type of this error
+++func (ip ErrInvalidPort) BadRequest() {}
+++
+++// ErrUnsupportedAddressType is returned when the specified address type is not supported.
+++type ErrUnsupportedAddressType string
+++
+++func (uat ErrUnsupportedAddressType) Error() string {
+++     return fmt.Sprintf("unsupported address type: %s", string(uat))
+++}
+++
+++// BadRequest denotes the type of this error
+++func (uat ErrUnsupportedAddressType) BadRequest() {}
+++
+++// ErrInvalidAddressBinding is returned when the host address specified in the port binding is not valid.
+++type ErrInvalidAddressBinding string
+++
+++func (iab ErrInvalidAddressBinding) Error() string {
+++     return fmt.Sprintf("invalid host address in port binding: %s", string(iab))
+++}
+++
+++// BadRequest denotes the type of this error
+++func (iab ErrInvalidAddressBinding) BadRequest() {}
+++
+++// ActiveEndpointsError is returned when there are
+++// still active endpoints in the network being deleted.
+++type ActiveEndpointsError string
+++
+++func (aee ActiveEndpointsError) Error() string {
+++     return fmt.Sprintf("network %s has active endpoint", string(aee))
+++}
+++
+++// Forbidden denotes the type of this error
+++func (aee ActiveEndpointsError) Forbidden() {}
+++
+++// InvalidNetworkIDError is returned when the passed
+++// network id for an existing network is not a known id.
+++type InvalidNetworkIDError string
+++
+++func (inie InvalidNetworkIDError) Error() string {
+++     return fmt.Sprintf("invalid network id %s", string(inie))
+++}
+++
+++// NotFound denotes the type of this error
+++func (inie InvalidNetworkIDError) NotFound() {}
+++
+++// InvalidEndpointIDError is returned when the passed
+++// endpoint id is not valid.
+++type InvalidEndpointIDError string
+++
+++func (ieie InvalidEndpointIDError) Error() string {
+++     return fmt.Sprintf("invalid endpoint id: %s", string(ieie))
+++}
+++
+++// BadRequest denotes the type of this error
+++func (ieie InvalidEndpointIDError) BadRequest() {}
+++
+++// InvalidSandboxIDError is returned when the passed
+++// sandbox id is not valid.
+++type InvalidSandboxIDError string
+++
+++func (isie InvalidSandboxIDError) Error() string {
+++     return fmt.Sprintf("invalid sanbox id: %s", string(isie))
+++}
+++
+++// BadRequest denotes the type of this error
+++func (isie InvalidSandboxIDError) BadRequest() {}
+++
+++// EndpointNotFoundError is returned when the no endpoint
+++// with the passed endpoint id is found.
+++type EndpointNotFoundError string
+++
+++func (enfe EndpointNotFoundError) Error() string {
+++     return fmt.Sprintf("endpoint not found: %s", string(enfe))
+++}
+++
+++// NotFound denotes the type of this error
+++func (enfe EndpointNotFoundError) NotFound() {}
+++
+++// NonDefaultBridgeExistError is returned when a non-default
+++// bridge config is passed but it does not already exist.
+++type NonDefaultBridgeExistError string
+++
+++func (ndbee NonDefaultBridgeExistError) Error() string {
+++     return fmt.Sprintf("bridge device with non default name %s must be created manually", string(ndbee))
+++}
+++
+++// Forbidden denotes the type of this error
+++func (ndbee NonDefaultBridgeExistError) Forbidden() {}
+++
+++// FixedCIDRv4Error is returned when fixed-cidrv4 configuration
+++// failed.
+++type FixedCIDRv4Error struct {
+++     Net    *net.IPNet
+++     Subnet *net.IPNet
+++     Err    error
+++}
+++
+++func (fcv4 *FixedCIDRv4Error) Error() string {
+++     return fmt.Sprintf("setup FixedCIDRv4 failed for subnet %s in %s: %v", fcv4.Subnet, fcv4.Net, fcv4.Err)
+++}
+++
+++// InternalError denotes the type of this error
+++func (fcv4 *FixedCIDRv4Error) InternalError() {}
+++
+++// FixedCIDRv6Error is returned when fixed-cidrv6 configuration
+++// failed.
+++type FixedCIDRv6Error struct {
+++     Net *net.IPNet
+++     Err error
+++}
+++
+++func (fcv6 *FixedCIDRv6Error) Error() string {
+++     return fmt.Sprintf("setup FixedCIDRv6 failed for subnet %s in %s: %v", fcv6.Net, fcv6.Net, fcv6.Err)
+++}
+++
+++// InternalError denotes the type of this error
+++func (fcv6 *FixedCIDRv6Error) InternalError() {}
+++
+++// IPTableCfgError is returned when an unexpected ip tables configuration is entered
+++type IPTableCfgError string
+++
+++func (name IPTableCfgError) Error() string {
+++     return fmt.Sprintf("unexpected request to set IP tables for interface: %s", string(name))
+++}
+++
+++// BadRequest denotes the type of this error
+++func (name IPTableCfgError) BadRequest() {}
+++
+++// InvalidIPTablesCfgError is returned when an invalid ip tables configuration is entered
+++type InvalidIPTablesCfgError string
+++
+++func (action InvalidIPTablesCfgError) Error() string {
+++     return fmt.Sprintf("Invalid IPTables action '%s'", string(action))
+++}
+++
+++// BadRequest denotes the type of this error
+++func (action InvalidIPTablesCfgError) BadRequest() {}
+++
+++// IPv4AddrRangeError is returned when a valid IP address range couldn't be found.
+++type IPv4AddrRangeError string
+++
+++func (name IPv4AddrRangeError) Error() string {
+++     return fmt.Sprintf("can't find an address range for interface %q", string(name))
+++}
+++
+++// BadRequest denotes the type of this error
+++func (name IPv4AddrRangeError) BadRequest() {}
+++
+++// IPv4AddrAddError is returned when IPv4 address could not be added to the bridge.
+++type IPv4AddrAddError struct {
+++     IP  *net.IPNet
+++     Err error
+++}
+++
+++func (ipv4 *IPv4AddrAddError) Error() string {
+++     return fmt.Sprintf("failed to add IPv4 address %s to bridge: %v", ipv4.IP, ipv4.Err)
+++}
+++
+++// InternalError denotes the type of this error
+++func (ipv4 *IPv4AddrAddError) InternalError() {}
+++
+++// IPv6AddrAddError is returned when IPv6 address could not be added to the bridge.
+++type IPv6AddrAddError struct {
+++     IP  *net.IPNet
+++     Err error
+++}
+++
+++func (ipv6 *IPv6AddrAddError) Error() string {
+++     return fmt.Sprintf("failed to add IPv6 address %s to bridge: %v", ipv6.IP, ipv6.Err)
+++}
+++
+++// InternalError denotes the type of this error
+++func (ipv6 *IPv6AddrAddError) InternalError() {}
+++
+++// IPv4AddrNoMatchError is returned when the bridge's IPv4 address does not match configured.
+++type IPv4AddrNoMatchError struct {
+++     IP    net.IP
+++     CfgIP net.IP
+++}
+++
+++func (ipv4 *IPv4AddrNoMatchError) Error() string {
+++     return fmt.Sprintf("bridge IPv4 (%s) does not match requested configuration %s", ipv4.IP, ipv4.CfgIP)
+++}
+++
+++// BadRequest denotes the type of this error
+++func (ipv4 *IPv4AddrNoMatchError) BadRequest() {}
+++
+++// IPv6AddrNoMatchError is returned when the bridge's IPv6 address does not match configured.
+++type IPv6AddrNoMatchError net.IPNet
+++
+++func (ipv6 *IPv6AddrNoMatchError) Error() string {
+++     return fmt.Sprintf("bridge IPv6 addresses do not match the expected bridge configuration %s", (*net.IPNet)(ipv6).String())
+++}
+++
+++// BadRequest denotes the type of this error
+++func (ipv6 *IPv6AddrNoMatchError) BadRequest() {}
+++
+++// InvalidLinkIPAddrError is returned when a link is configured to a container with an invalid ip address
+++type InvalidLinkIPAddrError string
+++
+++func (address InvalidLinkIPAddrError) Error() string {
+++     return fmt.Sprintf("Cannot link to a container with Invalid IP Address '%s'", string(address))
+++}
+++
+++// BadRequest denotes the type of this error
+++func (address InvalidLinkIPAddrError) BadRequest() {}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b05f2ea8045640d4b14f7df31607d2404abb9fbf
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,63 @@@@
+++package bridge
+++
+++import (
+++     "net"
+++
+++     "github.com/vishvananda/netlink"
+++)
+++
+++const (
+++     // DefaultBridgeName is the default name for the bridge interface managed
+++     // by the driver when unspecified by the caller.
+++     DefaultBridgeName = "docker0"
+++)
+++
+++// Interface models the bridge network device.
+++type bridgeInterface struct {
+++     Link        netlink.Link
+++     bridgeIPv4  *net.IPNet
+++     bridgeIPv6  *net.IPNet
+++     gatewayIPv4 net.IP
+++     gatewayIPv6 net.IP
+++}
+++
+++// newInterface creates a new bridge interface structure. It attempts to find
+++// an already existing device identified by the configuration BridgeName field,
+++// or the default bridge name when unspecified), but doesn't attempt to create
+++// one when missing
+++func newInterface(config *networkConfiguration) *bridgeInterface {
+++     i := &bridgeInterface{}
+++
+++     // Initialize the bridge name to the default if unspecified.
+++     if config.BridgeName == "" {
+++             config.BridgeName = DefaultBridgeName
+++     }
+++
+++     // Attempt to find an existing bridge named with the specified name.
+++     i.Link, _ = netlink.LinkByName(config.BridgeName)
+++     return i
+++}
+++
+++// exists indicates if the existing bridge interface exists on the system.
+++func (i *bridgeInterface) exists() bool {
+++     return i.Link != nil
+++}
+++
+++// addresses returns a single IPv4 address and all IPv6 addresses for the
+++// bridge interface.
+++func (i *bridgeInterface) addresses() (netlink.Addr, []netlink.Addr, error) {
+++     v4addr, err := netlink.AddrList(i.Link, netlink.FAMILY_V4)
+++     if err != nil {
+++             return netlink.Addr{}, nil, err
+++     }
+++
+++     v6addr, err := netlink.AddrList(i.Link, netlink.FAMILY_V6)
+++     if err != nil {
+++             return netlink.Addr{}, nil, err
+++     }
+++
+++     if len(v4addr) == 0 {
+++             return netlink.Addr{}, v6addr, nil
+++     }
+++     return v4addr[0], v6addr, nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..542d6d55e313517e70eece48e11ba3de4048ffdf
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,33 @@@@
+++package bridge
+++
+++import (
+++     "testing"
+++
+++     "github.com/docker/libnetwork/sandbox"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++func TestInterfaceDefaultName(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     config := &networkConfiguration{}
+++     if _ = newInterface(config); config.BridgeName != DefaultBridgeName {
+++             t.Fatalf("Expected default interface name %q, got %q", DefaultBridgeName, config.BridgeName)
+++     }
+++}
+++
+++func TestAddressesEmptyInterface(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     inf := newInterface(&networkConfiguration{})
+++     addrv4, addrsv6, err := inf.addresses()
+++     if err != nil {
+++             t.Fatalf("Failed to get addresses of default interface: %v", err)
+++     }
+++     if expected := (netlink.Addr{}); addrv4 != expected {
+++             t.Fatalf("Default interface has unexpected IPv4: %s", addrv4)
+++     }
+++     if len(addrsv6) != 0 {
+++             t.Fatalf("Default interface has unexpected IPv6: %v", addrsv6)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..894c5e772c1643d5f820a082c0743013b4361d2d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,85 @@@@
+++package bridge
+++
+++import (
+++     "fmt"
+++     "net"
+++
+++     log "github.com/Sirupsen/logrus"
+++     "github.com/docker/libnetwork/iptables"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++type link struct {
+++     parentIP string
+++     childIP  string
+++     ports    []types.TransportPort
+++     bridge   string
+++}
+++
+++func (l *link) String() string {
+++     return fmt.Sprintf("%s <-> %s [%v] on %s", l.parentIP, l.childIP, l.ports, l.bridge)
+++}
+++
+++func newLink(parentIP, childIP string, ports []types.TransportPort, bridge string) *link {
+++     return &link{
+++             childIP:  childIP,
+++             parentIP: parentIP,
+++             ports:    ports,
+++             bridge:   bridge,
+++     }
+++
+++}
+++
+++func (l *link) Enable() error {
+++     // -A == iptables append flag
+++     linkFunction := func() error {
+++             return linkContainers("-A", l.parentIP, l.childIP, l.ports, l.bridge, false)
+++     }
+++
+++     iptables.OnReloaded(func() { linkFunction() })
+++     return linkFunction()
+++}
+++
+++func (l *link) Disable() {
+++     // -D == iptables delete flag
+++     err := linkContainers("-D", l.parentIP, l.childIP, l.ports, l.bridge, true)
+++     if err != nil {
+++             log.Errorf("Error removing IPTables rules for a link %s due to %s", l.String(), err.Error())
+++     }
+++     // Return proper error once we move to use a proper iptables package
+++     // that returns typed errors
+++}
+++
+++func linkContainers(action, parentIP, childIP string, ports []types.TransportPort, bridge string,
+++     ignoreErrors bool) error {
+++     var nfAction iptables.Action
+++
+++     switch action {
+++     case "-A":
+++             nfAction = iptables.Append
+++     case "-I":
+++             nfAction = iptables.Insert
+++     case "-D":
+++             nfAction = iptables.Delete
+++     default:
+++             return InvalidIPTablesCfgError(action)
+++     }
+++
+++     ip1 := net.ParseIP(parentIP)
+++     if ip1 == nil {
+++             return InvalidLinkIPAddrError(parentIP)
+++     }
+++     ip2 := net.ParseIP(childIP)
+++     if ip2 == nil {
+++             return InvalidLinkIPAddrError(childIP)
+++     }
+++
+++     chain := iptables.Chain{Name: DockerChain, Bridge: bridge}
+++     for _, port := range ports {
+++             err := chain.Link(nfAction, ip1, ip2, int(port.Port), port.Proto.String())
+++             if !ignoreErrors && err != nil {
+++                     return err
+++             }
+++     }
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..fc4a6251f326bf5fa50be78c5890f21c21d096af
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,39 @@@@
+++package bridge
+++
+++import (
+++     "testing"
+++
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++func getPorts() []types.TransportPort {
+++     return []types.TransportPort{
+++             types.TransportPort{Proto: types.TCP, Port: uint16(5000)},
+++             types.TransportPort{Proto: types.UDP, Port: uint16(400)},
+++             types.TransportPort{Proto: types.TCP, Port: uint16(600)},
+++     }
+++}
+++
+++func TestLinkNew(t *testing.T) {
+++     ports := getPorts()
+++
+++     link := newLink("172.0.17.3", "172.0.17.2", ports, "docker0")
+++
+++     if link == nil {
+++             t.FailNow()
+++     }
+++     if link.parentIP != "172.0.17.3" {
+++             t.Fail()
+++     }
+++     if link.childIP != "172.0.17.2" {
+++             t.Fail()
+++     }
+++     for i, p := range link.ports {
+++             if p != ports[i] {
+++                     t.Fail()
+++             }
+++     }
+++     if link.bridge != "docker0" {
+++             t.Fail()
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..007ccb285a67761178dc16dab57a39f7bf61e01d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,139 @@@@
+++package bridge
+++
+++import (
+++     "fmt"
+++     "math/rand"
+++     "net"
+++     "syscall"
+++     "time"
+++     "unsafe"
+++)
+++
+++const (
+++     ifNameSize   = 16
+++     ioctlBrAdd   = 0x89a0
+++     ioctlBrAddIf = 0x89a2
+++)
+++
+++type ifreqIndex struct {
+++     IfrnName  [ifNameSize]byte
+++     IfruIndex int32
+++}
+++
+++type ifreqHwaddr struct {
+++     IfrnName   [ifNameSize]byte
+++     IfruHwaddr syscall.RawSockaddr
+++}
+++
+++var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
+++
+++// THIS CODE DOES NOT COMMUNICATE WITH KERNEL VIA RTNETLINK INTERFACE
+++// IT IS HERE FOR BACKWARDS COMPATIBILITY WITH OLDER LINUX KERNELS
+++// WHICH SHIP WITH OLDER NOT ENTIRELY FUNCTIONAL VERSION OF NETLINK
+++func getIfSocket() (fd int, err error) {
+++     for _, socket := range []int{
+++             syscall.AF_INET,
+++             syscall.AF_PACKET,
+++             syscall.AF_INET6,
+++     } {
+++             if fd, err = syscall.Socket(socket, syscall.SOCK_DGRAM, 0); err == nil {
+++                     break
+++             }
+++     }
+++     if err == nil {
+++             return fd, nil
+++     }
+++     return -1, err
+++}
+++
+++func ifIoctBridge(iface, master *net.Interface, op uintptr) error {
+++     if len(master.Name) >= ifNameSize {
+++             return fmt.Errorf("Interface name %s too long", master.Name)
+++     }
+++
+++     s, err := getIfSocket()
+++     if err != nil {
+++             return err
+++     }
+++     defer syscall.Close(s)
+++
+++     ifr := ifreqIndex{}
+++     copy(ifr.IfrnName[:len(ifr.IfrnName)-1], master.Name)
+++     ifr.IfruIndex = int32(iface.Index)
+++
+++     if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), op, uintptr(unsafe.Pointer(&ifr))); err != 0 {
+++             return err
+++     }
+++
+++     return nil
+++}
+++
+++// Add a slave to a bridge device.  This is more backward-compatible than
+++// netlink.NetworkSetMaster and works on RHEL 6.
+++func ioctlAddToBridge(iface, master *net.Interface) error {
+++     return ifIoctBridge(iface, master, ioctlBrAddIf)
+++}
+++
+++func randMacAddr() string {
+++     hw := make(net.HardwareAddr, 6)
+++     for i := 0; i < 6; i++ {
+++             hw[i] = byte(rnd.Intn(255))
+++     }
+++     hw[0] &^= 0x1 // clear multicast bit
+++     hw[0] |= 0x2  // set local assignment bit (IEEE802)
+++     return hw.String()
+++}
+++
+++func ioctlSetMacAddress(name, addr string) error {
+++     if len(name) >= ifNameSize {
+++             return fmt.Errorf("Interface name %s too long", name)
+++     }
+++
+++     hw, err := net.ParseMAC(addr)
+++     if err != nil {
+++             return err
+++     }
+++
+++     s, err := getIfSocket()
+++     if err != nil {
+++             return err
+++     }
+++     defer syscall.Close(s)
+++
+++     ifr := ifreqHwaddr{}
+++     ifr.IfruHwaddr.Family = syscall.ARPHRD_ETHER
+++     copy(ifr.IfrnName[:len(ifr.IfrnName)-1], name)
+++
+++     for i := 0; i < 6; i++ {
+++             ifr.IfruHwaddr.Data[i] = ifrDataByte(hw[i])
+++     }
+++
+++     if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), syscall.SIOCSIFHWADDR, uintptr(unsafe.Pointer(&ifr))); err != 0 {
+++             return err
+++     }
+++     return nil
+++}
+++
+++func ioctlCreateBridge(name string, setMacAddr bool) error {
+++     if len(name) >= ifNameSize {
+++             return fmt.Errorf("Interface name %s too long", name)
+++     }
+++
+++     s, err := getIfSocket()
+++     if err != nil {
+++             return err
+++     }
+++     defer syscall.Close(s)
+++
+++     nameBytePtr, err := syscall.BytePtrFromString(name)
+++     if err != nil {
+++             return err
+++     }
+++     if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), ioctlBrAdd, uintptr(unsafe.Pointer(nameBytePtr))); err != 0 {
+++             return err
+++     }
+++     if setMacAddr {
+++             return ioctlSetMacAddress(name, randMacAddr())
+++     }
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d3eadfa21cc5895f79a34a5d4b33f4a17fb71868
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,7 @@@@
+++// +build arm ppc64
+++
+++package bridge
+++
+++func ifrDataByte(b byte) uint8 {
+++     return uint8(b)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..97f51e1edc1ef28f1dea7d32d8eb0bd5b930f2e4
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,7 @@@@
+++// +build !arm,!ppc64
+++
+++package bridge
+++
+++func ifrDataByte(b byte) int8 {
+++     return int8(b)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..7e2d57b660eee8b0055c1aecdc491d3782602a0a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,18 @@@@
+++// +build !linux
+++
+++package bridge
+++
+++import (
+++     "errors"
+++     "net"
+++)
+++
+++// Add a slave to a bridge device.  This is more backward-compatible than
+++// netlink.NetworkSetMaster and works on RHEL 6.
+++func ioctlAddToBridge(iface, master *net.Interface) error {
+++     return errors.New("not implemented")
+++}
+++
+++func ioctlCreateBridge(name string, setMacAddr bool) error {
+++     return errors.New("not implemented")
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..005448b5e4325454e903f2916161c0f5908db836
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,203 @@@@
+++package bridge
+++
+++import (
+++     "testing"
+++
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/netlabel"
+++     "github.com/docker/libnetwork/sandbox"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++func TestLinkCreate(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++     d := newDriver()
+++     dr := d.(*driver)
+++
+++     mtu := 1490
+++     config := &networkConfiguration{
+++             BridgeName: DefaultBridgeName,
+++             Mtu:        mtu,
+++             EnableIPv6: true,
+++     }
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = config
+++
+++     err := d.CreateNetwork("dummy", genericOption)
+++     if err != nil {
+++             t.Fatalf("Failed to create bridge: %v", err)
+++     }
+++
+++     te := &testEndpoint{ifaces: []*testInterface{}}
+++     err = d.CreateEndpoint("dummy", "", te, nil)
+++     if err != nil {
+++             if _, ok := err.(InvalidEndpointIDError); !ok {
+++                     t.Fatalf("Failed with a wrong error :%s", err.Error())
+++             }
+++     } else {
+++             t.Fatalf("Failed to detect invalid config")
+++     }
+++
+++     // Good endpoint creation
+++     err = d.CreateEndpoint("dummy", "ep", te, nil)
+++     if err != nil {
+++             t.Fatalf("Failed to create a link: %s", err.Error())
+++     }
+++
+++     err = d.Join("dummy", "ep", "sbox", te, nil)
+++     if err != nil {
+++             t.Fatalf("Failed to create a link: %s", err.Error())
+++     }
+++
+++     // Verify sbox endoint interface inherited MTU value from bridge config
+++     sboxLnk, err := netlink.LinkByName(te.ifaces[0].srcName)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if mtu != sboxLnk.Attrs().MTU {
+++             t.Fatalf("Sandbox endpoint interface did not inherit bridge interface MTU config")
+++     }
+++     // TODO: if we could get peer name from (sboxLnk.(*netlink.Veth)).PeerName
+++     // then we could check the MTU on hostLnk as well.
+++
+++     te1 := &testEndpoint{ifaces: []*testInterface{}}
+++     err = d.CreateEndpoint("dummy", "ep", te1, nil)
+++     if err == nil {
+++             t.Fatalf("Failed to detect duplicate endpoint id on same network")
+++     }
+++
+++     if len(te.ifaces) != 1 {
+++             t.Fatalf("Expected exactly one interface. Instead got %d interface(s)", len(te.ifaces))
+++     }
+++
+++     if te.ifaces[0].dstName == "" {
+++             t.Fatal("Invalid Dstname returned")
+++     }
+++
+++     _, err = netlink.LinkByName(te.ifaces[0].srcName)
+++     if err != nil {
+++             t.Fatalf("Could not find source link %s: %v", te.ifaces[0].srcName, err)
+++     }
+++
+++     n, ok := dr.networks["dummy"]
+++     if !ok {
+++             t.Fatalf("Cannot find network %s inside driver", "dummy")
+++     }
+++     ip := te.ifaces[0].addr.IP
+++     if !n.bridge.bridgeIPv4.Contains(ip) {
+++             t.Fatalf("IP %s is not a valid ip in the subnet %s", ip.String(), n.bridge.bridgeIPv4.String())
+++     }
+++
+++     ip6 := te.ifaces[0].addrv6.IP
+++     if !n.bridge.bridgeIPv6.Contains(ip6) {
+++             t.Fatalf("IP %s is not a valid ip in the subnet %s", ip6.String(), bridgeIPv6.String())
+++     }
+++
+++     if !te.gw.Equal(n.bridge.bridgeIPv4.IP) {
+++             t.Fatalf("Invalid default gateway. Expected %s. Got %s", n.bridge.bridgeIPv4.IP.String(),
+++                     te.gw.String())
+++     }
+++
+++     if !te.gw6.Equal(n.bridge.bridgeIPv6.IP) {
+++             t.Fatalf("Invalid default gateway for IPv6. Expected %s. Got %s", n.bridge.bridgeIPv6.IP.String(),
+++                     te.gw6.String())
+++     }
+++}
+++
+++func TestLinkCreateTwo(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++     d := newDriver()
+++
+++     config := &networkConfiguration{
+++             BridgeName: DefaultBridgeName,
+++             EnableIPv6: true}
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = config
+++
+++     err := d.CreateNetwork("dummy", genericOption)
+++     if err != nil {
+++             t.Fatalf("Failed to create bridge: %v", err)
+++     }
+++
+++     te1 := &testEndpoint{ifaces: []*testInterface{}}
+++     err = d.CreateEndpoint("dummy", "ep", te1, nil)
+++     if err != nil {
+++             t.Fatalf("Failed to create a link: %s", err.Error())
+++     }
+++
+++     te2 := &testEndpoint{ifaces: []*testInterface{}}
+++     err = d.CreateEndpoint("dummy", "ep", te2, nil)
+++     if err != nil {
+++             if _, ok := err.(driverapi.ErrEndpointExists); !ok {
+++                     t.Fatalf("Failed with a wrong error: %s", err.Error())
+++             }
+++     } else {
+++             t.Fatalf("Expected to fail while trying to add same endpoint twice")
+++     }
+++}
+++
+++func TestLinkCreateNoEnableIPv6(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++     d := newDriver()
+++
+++     config := &networkConfiguration{
+++             BridgeName: DefaultBridgeName}
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = config
+++
+++     err := d.CreateNetwork("dummy", genericOption)
+++     if err != nil {
+++             t.Fatalf("Failed to create bridge: %v", err)
+++     }
+++
+++     te := &testEndpoint{ifaces: []*testInterface{}}
+++     err = d.CreateEndpoint("dummy", "ep", te, nil)
+++     if err != nil {
+++             t.Fatalf("Failed to create a link: %s", err.Error())
+++     }
+++
+++     interfaces := te.ifaces
+++     if interfaces[0].addrv6.IP.To16() != nil {
+++             t.Fatalf("Expectd IPv6 address to be nil when IPv6 is not enabled. Got IPv6 = %s", interfaces[0].addrv6.String())
+++     }
+++
+++     if te.gw6.To16() != nil {
+++             t.Fatalf("Expected GatewayIPv6 to be nil when IPv6 is not enabled. Got GatewayIPv6 = %s", te.gw6.String())
+++     }
+++}
+++
+++func TestLinkDelete(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++     d := newDriver()
+++
+++     config := &networkConfiguration{
+++             BridgeName: DefaultBridgeName,
+++             EnableIPv6: true}
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = config
+++
+++     err := d.CreateNetwork("dummy", genericOption)
+++     if err != nil {
+++             t.Fatalf("Failed to create bridge: %v", err)
+++     }
+++
+++     te := &testEndpoint{ifaces: []*testInterface{}}
+++     err = d.CreateEndpoint("dummy", "ep1", te, nil)
+++     if err != nil {
+++             t.Fatalf("Failed to create a link: %s", err.Error())
+++     }
+++
+++     err = d.DeleteEndpoint("dummy", "")
+++     if err != nil {
+++             if _, ok := err.(InvalidEndpointIDError); !ok {
+++                     t.Fatalf("Failed with a wrong error :%s", err.Error())
+++             }
+++     } else {
+++             t.Fatalf("Failed to detect invalid config")
+++     }
+++
+++     err = d.DeleteEndpoint("dummy", "ep1")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b1021321906be720b79353ef7ff8a855cfb4c501
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,123 @@@@
+++package bridge
+++
+++import (
+++     "bytes"
+++     "errors"
+++     "fmt"
+++     "net"
+++
+++     "github.com/Sirupsen/logrus"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++var (
+++     defaultBindingIP = net.IPv4(0, 0, 0, 0)
+++)
+++
+++func (n *bridgeNetwork) allocatePorts(epConfig *endpointConfiguration, ep *bridgeEndpoint, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
+++     if epConfig == nil || epConfig.PortBindings == nil {
+++             return nil, nil
+++     }
+++
+++     defHostIP := defaultBindingIP
+++     if reqDefBindIP != nil {
+++             defHostIP = reqDefBindIP
+++     }
+++
+++     return n.allocatePortsInternal(epConfig.PortBindings, ep.addr.IP, defHostIP, ulPxyEnabled)
+++}
+++
+++func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
+++     bs := make([]types.PortBinding, 0, len(bindings))
+++     for _, c := range bindings {
+++             b := c.GetCopy()
+++             if err := n.allocatePort(&b, containerIP, defHostIP, ulPxyEnabled); err != nil {
+++                     // On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
+++                     if cuErr := n.releasePortsInternal(bs); cuErr != nil {
+++                             logrus.Warnf("Upon allocation failure for %v, failed to clear previously allocated port bindings: %v", b, cuErr)
+++                     }
+++                     return nil, err
+++             }
+++             bs = append(bs, b)
+++     }
+++     return bs, nil
+++}
+++
+++func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) error {
+++     var (
+++             host net.Addr
+++             err  error
+++     )
+++
+++     // Store the container interface address in the operational binding
+++     bnd.IP = containerIP
+++
+++     // Adjust the host address in the operational binding
+++     if len(bnd.HostIP) == 0 {
+++             bnd.HostIP = defHostIP
+++     }
+++
+++     // Construct the container side transport address
+++     container, err := bnd.ContainerAddr()
+++     if err != nil {
+++             return err
+++     }
+++
+++     // Try up to maxAllocatePortAttempts times to get a port that's not already allocated.
+++     for i := 0; i < maxAllocatePortAttempts; i++ {
+++             if host, err = n.portMapper.Map(container, bnd.HostIP, int(bnd.HostPort), ulPxyEnabled); err == nil {
+++                     break
+++             }
+++             // There is no point in immediately retrying to map an explicitly chosen port.
+++             if bnd.HostPort != 0 {
+++                     logrus.Warnf("Failed to allocate and map port %d: %s", bnd.HostPort, err)
+++                     break
+++             }
+++             logrus.Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1)
+++     }
+++     if err != nil {
+++             return err
+++     }
+++
+++     // Save the host port (regardless it was or not specified in the binding)
+++     switch netAddr := host.(type) {
+++     case *net.TCPAddr:
+++             bnd.HostPort = uint16(host.(*net.TCPAddr).Port)
+++             return nil
+++     case *net.UDPAddr:
+++             bnd.HostPort = uint16(host.(*net.UDPAddr).Port)
+++             return nil
+++     default:
+++             // For completeness
+++             return ErrUnsupportedAddressType(fmt.Sprintf("%T", netAddr))
+++     }
+++}
+++
+++func (n *bridgeNetwork) releasePorts(ep *bridgeEndpoint) error {
+++     return n.releasePortsInternal(ep.portMapping)
+++}
+++
+++func (n *bridgeNetwork) releasePortsInternal(bindings []types.PortBinding) error {
+++     var errorBuf bytes.Buffer
+++
+++     // Attempt to release all port bindings, do not stop on failure
+++     for _, m := range bindings {
+++             if err := n.releasePort(m); err != nil {
+++                     errorBuf.WriteString(fmt.Sprintf("\ncould not release %v because of %v", m, err))
+++             }
+++     }
+++
+++     if errorBuf.Len() != 0 {
+++             return errors.New(errorBuf.String())
+++     }
+++     return nil
+++}
+++
+++func (n *bridgeNetwork) releasePort(bnd types.PortBinding) error {
+++     // Construct the host side transport address
+++     host, err := bnd.HostAddr()
+++     if err != nil {
+++             return err
+++     }
+++     return n.portMapper.Unmap(host)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..0094335761aa309ab1ec4c225115556863189c87
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,71 @@@@
+++package bridge
+++
+++import (
+++     "os"
+++     "testing"
+++
+++     "github.com/docker/docker/pkg/reexec"
+++     "github.com/docker/libnetwork/netlabel"
+++     "github.com/docker/libnetwork/sandbox"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++func TestMain(m *testing.M) {
+++     if reexec.Init() {
+++             return
+++     }
+++     os.Exit(m.Run())
+++}
+++
+++func TestPortMappingConfig(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++     d := newDriver()
+++
+++     binding1 := types.PortBinding{Proto: types.UDP, Port: uint16(400), HostPort: uint16(54000)}
+++     binding2 := types.PortBinding{Proto: types.TCP, Port: uint16(500), HostPort: uint16(65000)}
+++     portBindings := []types.PortBinding{binding1, binding2}
+++
+++     epOptions := make(map[string]interface{})
+++     epOptions[netlabel.PortMap] = portBindings
+++
+++     netConfig := &networkConfiguration{
+++             BridgeName:     DefaultBridgeName,
+++             EnableIPTables: true,
+++     }
+++     netOptions := make(map[string]interface{})
+++     netOptions[netlabel.GenericData] = netConfig
+++
+++     err := d.CreateNetwork("dummy", netOptions)
+++     if err != nil {
+++             t.Fatalf("Failed to create bridge: %v", err)
+++     }
+++
+++     te := &testEndpoint{ifaces: []*testInterface{}}
+++     err = d.CreateEndpoint("dummy", "ep1", te, epOptions)
+++     if err != nil {
+++             t.Fatalf("Failed to create the endpoint: %s", err.Error())
+++     }
+++
+++     dd := d.(*driver)
+++     network, ok := dd.networks["dummy"]
+++     if !ok {
+++             t.Fatalf("Cannot find network %s inside driver", "dummy")
+++     }
+++     ep, _ := network.endpoints["ep1"]
+++     if len(ep.portMapping) != 2 {
+++             t.Fatalf("Failed to store the port bindings into the sandbox info. Found: %v", ep.portMapping)
+++     }
+++     if ep.portMapping[0].Proto != binding1.Proto || ep.portMapping[0].Port != binding1.Port ||
+++             ep.portMapping[1].Proto != binding2.Proto || ep.portMapping[1].Port != binding2.Port {
+++             t.Fatalf("bridgeEndpoint has incorrect port mapping values")
+++     }
+++     if ep.portMapping[0].HostIP == nil || ep.portMapping[0].HostPort == 0 ||
+++             ep.portMapping[1].HostIP == nil || ep.portMapping[1].HostPort == 0 {
+++             t.Fatalf("operational port mapping data not found on bridgeEndpoint")
+++     }
+++
+++     err = network.releasePorts(ep)
+++     if err != nil {
+++             t.Fatalf("Failed to release mapped ports: %v", err)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8861184220ae5d979bee9eb4bdd1ff565f464e32
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,67 @@@@
+++package bridge
+++
+++import (
+++     "bytes"
+++     "io/ioutil"
+++     "regexp"
+++)
+++
+++const (
+++     ipv4NumBlock = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`
+++     ipv4Address  = `(` + ipv4NumBlock + `\.){3}` + ipv4NumBlock
+++
+++     // This is not an IPv6 address verifier as it will accept a super-set of IPv6, and also
+++     // will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants
+++     // -- e.g. other link-local types -- either won't work in containers or are unnecessary.
+++     // For readability and sufficiency for Docker purposes this seemed more reasonable than a
+++     // 1000+ character regexp with exact and complete IPv6 validation
+++     ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})`
+++)
+++
+++var nsRegexp = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`)
+++
+++func readResolvConf() ([]byte, error) {
+++     resolv, err := ioutil.ReadFile("/etc/resolv.conf")
+++     if err != nil {
+++             return nil, err
+++     }
+++     return resolv, nil
+++}
+++
+++// getLines parses input into lines and strips away comments.
+++func getLines(input []byte, commentMarker []byte) [][]byte {
+++     lines := bytes.Split(input, []byte("\n"))
+++     var output [][]byte
+++     for _, currentLine := range lines {
+++             var commentIndex = bytes.Index(currentLine, commentMarker)
+++             if commentIndex == -1 {
+++                     output = append(output, currentLine)
+++             } else {
+++                     output = append(output, currentLine[:commentIndex])
+++             }
+++     }
+++     return output
+++}
+++
+++// GetNameserversAsCIDR returns nameservers (if any) listed in
+++// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32")
+++// This function's output is intended for net.ParseCIDR
+++func getNameserversAsCIDR(resolvConf []byte) []string {
+++     nameservers := []string{}
+++     for _, nameserver := range getNameservers(resolvConf) {
+++             nameservers = append(nameservers, nameserver+"/32")
+++     }
+++     return nameservers
+++}
+++
+++// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf
+++func getNameservers(resolvConf []byte) []string {
+++     nameservers := []string{}
+++     for _, line := range getLines(resolvConf, []byte("#")) {
+++             var ns = nsRegexp.FindSubmatch(line)
+++             if len(ns) > 0 {
+++                     nameservers = append(nameservers, string(ns[1]))
+++             }
+++     }
+++     return nameservers
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..029f41c78c1c30ba7dec471497fe5d3dd3980845
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,53 @@@@
+++package bridge
+++
+++import (
+++     "bytes"
+++     "testing"
+++)
+++
+++func TestResolveConfRead(t *testing.T) {
+++     b, err := readResolvConf()
+++     if err != nil {
+++             t.Fatalf("Failed to read resolv.conf: %v", err)
+++     }
+++
+++     if b == nil {
+++             t.Fatal("Reading resolv.conf returned no content")
+++     }
+++}
+++
+++func TestResolveConfReadLines(t *testing.T) {
+++     commentChar := []byte("#")
+++
+++     b, _ := readResolvConf()
+++     lines := getLines(b, commentChar)
+++     if lines == nil {
+++             t.Fatal("Failed to read resolv.conf lines")
+++     }
+++
+++     for _, line := range lines {
+++             if bytes.Index(line, commentChar) != -1 {
+++                     t.Fatal("Returned comment content from resolv.conf")
+++             }
+++     }
+++}
+++
+++func TestResolvConfNameserversAsCIDR(t *testing.T) {
+++     resolvConf := `# Commented line
+++nameserver 1.2.3.4
+++
+++nameserver 5.6.7.8 # Test
+++`
+++
+++     cidrs := getNameserversAsCIDR([]byte(resolvConf))
+++     if expected := 2; len(cidrs) != expected {
+++             t.Fatalf("Expected %d nameservers, got %d", expected, len(cidrs))
+++     }
+++
+++     expected := []string{"1.2.3.4/32", "5.6.7.8/32"}
+++     for i, exp := range expected {
+++             if cidrs[i] != exp {
+++                     t.Fatalf("Expected nameservers %s, got %s", exp, cidrs[i])
+++             }
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..eeb3611b78e5632abb9e54c4def5774189ca327a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,26 @@@@
+++package bridge
+++
+++type setupStep func(*networkConfiguration, *bridgeInterface) error
+++
+++type bridgeSetup struct {
+++     config *networkConfiguration
+++     bridge *bridgeInterface
+++     steps  []setupStep
+++}
+++
+++func newBridgeSetup(c *networkConfiguration, i *bridgeInterface) *bridgeSetup {
+++     return &bridgeSetup{config: c, bridge: i}
+++}
+++
+++func (b *bridgeSetup) apply() error {
+++     for _, fn := range b.steps {
+++             if err := fn(b.config, b.bridge); err != nil {
+++                     return err
+++             }
+++     }
+++     return nil
+++}
+++
+++func (b *bridgeSetup) queueStep(step setupStep) {
+++     b.steps = append(b.steps, step)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e7a5f4b0779f8d9fac70411ae8d1f289d148b955
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,162 @@@@
+++package bridge
+++
+++import (
+++     "fmt"
+++     "io/ioutil"
+++     "os"
+++     "syscall"
+++
+++     "github.com/Sirupsen/logrus"
+++)
+++
+++// Enumeration type saying which versions of IP protocol to process.
+++type ipVersion int
+++
+++const (
+++     ipvnone ipVersion = iota
+++     ipv4
+++     ipv6
+++     ipvboth
+++)
+++
+++//Gets the IP version in use ( [ipv4], [ipv6] or [ipv4 and ipv6] )
+++func getIPVersion(config *networkConfiguration) ipVersion {
+++     ipVersion := ipv4
+++     if config.FixedCIDRv6 != nil || config.EnableIPv6 {
+++             ipVersion |= ipv6
+++     }
+++     return ipVersion
+++}
+++
+++func setupBridgeNetFiltering(config *networkConfiguration, i *bridgeInterface) error {
+++     err := checkBridgeNetFiltering(config, i)
+++     if err != nil {
+++             if ptherr, ok := err.(*os.PathError); ok {
+++                     if errno, ok := ptherr.Err.(syscall.Errno); ok && errno == syscall.ENOENT {
+++                             if isRunningInContainer() {
+++                                     logrus.Warnf("running inside docker container, ignoring missing kernel params: %v", err)
+++                                     err = nil
+++                             } else {
+++                                     err = fmt.Errorf("please ensure that br_netfilter kernel module is loaded")
+++                             }
+++                     }
+++             }
+++             if err != nil {
+++                     return fmt.Errorf("cannot restrict inter-container communication: %v", err)
+++             }
+++     }
+++     return nil
+++}
+++
+++//Enable bridge net filtering if ip forwarding is enabled. See github issue #11404
+++func checkBridgeNetFiltering(config *networkConfiguration, i *bridgeInterface) error {
+++     ipVer := getIPVersion(config)
+++     iface := config.BridgeName
+++     doEnable := func(ipVer ipVersion) error {
+++             var ipVerName string
+++             if ipVer == ipv4 {
+++                     ipVerName = "IPv4"
+++             } else {
+++                     ipVerName = "IPv6"
+++             }
+++             enabled, err := isPacketForwardingEnabled(ipVer, iface)
+++             if err != nil {
+++                     logrus.Warnf("failed to check %s forwarding: %v", ipVerName, err)
+++             } else if enabled {
+++                     enabled, err := getKernelBoolParam(getBridgeNFKernelParam(ipVer))
+++                     if err != nil || enabled {
+++                             return err
+++                     }
+++                     return setKernelBoolParam(getBridgeNFKernelParam(ipVer), true)
+++             }
+++             return nil
+++     }
+++
+++     switch ipVer {
+++     case ipv4, ipv6:
+++             return doEnable(ipVer)
+++     case ipvboth:
+++             v4err := doEnable(ipv4)
+++             v6err := doEnable(ipv6)
+++             if v4err == nil {
+++                     return v6err
+++             }
+++             return v4err
+++     default:
+++             return nil
+++     }
+++}
+++
+++// Get kernel param path saying whether IPv${ipVer} traffic is being forwarded
+++// on particular interface. Interface may be specified for IPv6 only. If
+++// `iface` is empty, `default` will be assumed, which represents default value
+++// for new interfaces.
+++func getForwardingKernelParam(ipVer ipVersion, iface string) string {
+++     switch ipVer {
+++     case ipv4:
+++             return "/proc/sys/net/ipv4/ip_forward"
+++     case ipv6:
+++             if iface == "" {
+++                     iface = "default"
+++             }
+++             return fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/forwarding", iface)
+++     default:
+++             return ""
+++     }
+++}
+++
+++// Get kernel param path saying whether bridged IPv${ipVer} traffic shall be
+++// passed to ip${ipVer}tables' chains.
+++func getBridgeNFKernelParam(ipVer ipVersion) string {
+++     switch ipVer {
+++     case ipv4:
+++             return "/proc/sys/net/bridge/bridge-nf-call-iptables"
+++     case ipv6:
+++             return "/proc/sys/net/bridge/bridge-nf-call-ip6tables"
+++     default:
+++             return ""
+++     }
+++}
+++
+++//Gets the value of the kernel parameters located at the given path
+++func getKernelBoolParam(path string) (bool, error) {
+++     enabled := false
+++     line, err := ioutil.ReadFile(path)
+++     if err != nil {
+++             return false, err
+++     }
+++     if len(line) > 0 {
+++             enabled = line[0] == '1'
+++     }
+++     return enabled, err
+++}
+++
+++//Sets the value of the kernel parameter located at the given path
+++func setKernelBoolParam(path string, on bool) error {
+++     value := byte('0')
+++     if on {
+++             value = byte('1')
+++     }
+++     return ioutil.WriteFile(path, []byte{value, '\n'}, 0644)
+++}
+++
+++//Checks to see if packet forwarding is enabled
+++func isPacketForwardingEnabled(ipVer ipVersion, iface string) (bool, error) {
+++     switch ipVer {
+++     case ipv4, ipv6:
+++             return getKernelBoolParam(getForwardingKernelParam(ipVer, iface))
+++     case ipvboth:
+++             enabled, err := getKernelBoolParam(getForwardingKernelParam(ipv4, ""))
+++             if err != nil || !enabled {
+++                     return enabled, err
+++             }
+++             return getKernelBoolParam(getForwardingKernelParam(ipv6, iface))
+++     default:
+++             return true, nil
+++     }
+++}
+++
+++func isRunningInContainer() bool {
+++     _, err := os.Stat("/.dockerinit")
+++     return !os.IsNotExist(err)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4ff5fed8afbd40a793c71177f35beba086f228da
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,12 @@@@
+++package bridge
+++
+++import "testing"
+++
+++func TestIPConstantValues(t *testing.T) {
+++     if ipv4|ipv6 != ipvboth {
+++             t.Fatalf("bitwise or of ipv4(%04b) and ipv6(%04b) must yield ipvboth(%04b)", ipv4, ipv6, ipvboth)
+++     }
+++     if ipvboth&(^(ipv4 | ipv6)) != ipvnone {
+++             t.Fatalf("ipvboth(%04b) with unset ipv4(%04b) and ipv6(%04b) bits shall equal to ipvnone", ipvboth, ipv4, ipv6)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..22bf64b2f78023d8f9bd09a9bb3a1b9e4b35186b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,66 @@@@
+++package bridge
+++
+++import (
+++     "fmt"
+++
+++     "github.com/Sirupsen/logrus"
+++     "github.com/docker/docker/pkg/parsers/kernel"
+++     "github.com/docker/libnetwork/netutils"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++// SetupDevice create a new bridge interface/
+++func setupDevice(config *networkConfiguration, i *bridgeInterface) error {
+++     var setMac bool
+++
+++     // We only attempt to create the bridge when the requested device name is
+++     // the default one.
+++     if config.BridgeName != DefaultBridgeName && !config.AllowNonDefaultBridge {
+++             return NonDefaultBridgeExistError(config.BridgeName)
+++     }
+++
+++     // Set the bridgeInterface netlink.Bridge.
+++     i.Link = &netlink.Bridge{
+++             LinkAttrs: netlink.LinkAttrs{
+++                     Name: config.BridgeName,
+++             },
+++     }
+++
+++     // Only set the bridge's MAC address if the kernel version is > 3.3, as it
+++     // was not supported before that.
+++     kv, err := kernel.GetKernelVersion()
+++     if err != nil {
+++             logrus.Errorf("Failed to check kernel versions: %v. Will not assign a MAC address to the bridge interface", err)
+++     } else {
+++             setMac = kv.Kernel > 3 || (kv.Kernel == 3 && kv.Major >= 3)
+++     }
+++
+++     if err = netlink.LinkAdd(i.Link); err != nil {
+++             logrus.Debugf("Failed to create bridge %s via netlink. Trying ioctl", config.BridgeName)
+++             return ioctlCreateBridge(config.BridgeName, setMac)
+++     }
+++
+++     if setMac {
+++             hwAddr := netutils.GenerateRandomMAC()
+++             if err = netlink.LinkSetHardwareAddr(i.Link, hwAddr); err != nil {
+++                     return fmt.Errorf("failed to set bridge mac-address %s : %s", hwAddr, err.Error())
+++             }
+++             logrus.Debugf("Setting bridge mac address to %s", hwAddr)
+++     }
+++     return err
+++}
+++
+++// SetupDeviceUp ups the given bridge interface.
+++func setupDeviceUp(config *networkConfiguration, i *bridgeInterface) error {
+++     err := netlink.LinkSetUp(i.Link)
+++     if err != nil {
+++             return err
+++     }
+++
+++     // Attempt to update the bridge interface to refresh the flags status,
+++     // ignoring any failure to do so.
+++     if lnk, err := netlink.LinkByName(config.BridgeName); err == nil {
+++             i.Link = lnk
+++     }
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1ce867c1894b19b9a826205de86681fedf2e431f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,76 @@@@
+++package bridge
+++
+++import (
+++     "bytes"
+++     "net"
+++     "testing"
+++
+++     "github.com/docker/libnetwork/netutils"
+++     "github.com/docker/libnetwork/sandbox"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++func TestSetupNewBridge(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     config := &networkConfiguration{BridgeName: DefaultBridgeName}
+++     br := &bridgeInterface{}
+++
+++     if err := setupDevice(config, br); err != nil {
+++             t.Fatalf("Bridge creation failed: %v", err)
+++     }
+++     if br.Link == nil {
+++             t.Fatal("bridgeInterface link is nil (expected valid link)")
+++     }
+++     if _, err := netlink.LinkByName(DefaultBridgeName); err != nil {
+++             t.Fatalf("Failed to retrieve bridge device: %v", err)
+++     }
+++     if br.Link.Attrs().Flags&net.FlagUp == net.FlagUp {
+++             t.Fatalf("bridgeInterface should be created down")
+++     }
+++}
+++
+++func TestSetupNewNonDefaultBridge(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     config := &networkConfiguration{BridgeName: "test0"}
+++     br := &bridgeInterface{}
+++
+++     err := setupDevice(config, br)
+++     if err == nil {
+++             t.Fatal("Expected bridge creation failure with \"non default name\", succeeded")
+++     }
+++
+++     if _, ok := err.(NonDefaultBridgeExistError); !ok {
+++             t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+++     }
+++}
+++
+++func TestSetupDeviceUp(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     config := &networkConfiguration{BridgeName: DefaultBridgeName}
+++     br := &bridgeInterface{}
+++
+++     if err := setupDevice(config, br); err != nil {
+++             t.Fatalf("Bridge creation failed: %v", err)
+++     }
+++     if err := setupDeviceUp(config, br); err != nil {
+++             t.Fatalf("Failed to up bridge device: %v", err)
+++     }
+++
+++     lnk, _ := netlink.LinkByName(DefaultBridgeName)
+++     if lnk.Attrs().Flags&net.FlagUp != net.FlagUp {
+++             t.Fatalf("bridgeInterface should be up")
+++     }
+++}
+++
+++func TestGenerateRandomMAC(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     mac1 := netutils.GenerateRandomMAC()
+++     mac2 := netutils.GenerateRandomMAC()
+++     if bytes.Compare(mac1, mac2) == 0 {
+++             t.Fatalf("Generated twice the same MAC address %v", mac1)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..eeb77648014969a471fd322180d7843f17f34795
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,15 @@@@
+++package bridge
+++
+++import "github.com/docker/libnetwork/iptables"
+++
+++func (n *bridgeNetwork) setupFirewalld(config *networkConfiguration, i *bridgeInterface) error {
+++     // Sanity check.
+++     if config.EnableIPTables == false {
+++             return IPTableCfgError(config.BridgeName)
+++     }
+++
+++     iptables.OnReloaded(func() { n.setupIPTables(config, i) })
+++     iptables.OnReloaded(n.portMapper.ReMapAll)
+++
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..0702053430cf8affa11e8c29970097a0e45cfc6c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,19 @@@@
+++package bridge
+++
+++import (
+++     log "github.com/Sirupsen/logrus"
+++)
+++
+++func setupFixedCIDRv4(config *networkConfiguration, i *bridgeInterface) error {
+++     addrv4, _, err := i.addresses()
+++     if err != nil {
+++             return err
+++     }
+++
+++     log.Debugf("Using IPv4 subnet: %v", config.FixedCIDR)
+++     if err := ipAllocator.RegisterSubnet(addrv4.IPNet, config.FixedCIDR); err != nil {
+++             return &FixedCIDRv4Error{Subnet: config.FixedCIDR, Net: addrv4.IPNet, Err: err}
+++     }
+++
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8daf0f020492cb16340790517d48ae56cfec864b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,62 @@@@
+++package bridge
+++
+++import (
+++     "net"
+++     "testing"
+++
+++     "github.com/docker/libnetwork/sandbox"
+++)
+++
+++func TestSetupFixedCIDRv4(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     config := &networkConfiguration{
+++             BridgeName:  DefaultBridgeName,
+++             AddressIPv4: &net.IPNet{IP: net.ParseIP("192.168.1.1"), Mask: net.CIDRMask(16, 32)},
+++             FixedCIDR:   &net.IPNet{IP: net.ParseIP("192.168.2.0"), Mask: net.CIDRMask(24, 32)}}
+++     br := &bridgeInterface{}
+++
+++     if err := setupDevice(config, br); err != nil {
+++             t.Fatalf("Bridge creation failed: %v", err)
+++     }
+++     if err := setupBridgeIPv4(config, br); err != nil {
+++             t.Fatalf("Assign IPv4 to bridge failed: %v", err)
+++     }
+++
+++     if err := setupFixedCIDRv4(config, br); err != nil {
+++             t.Fatalf("Failed to setup bridge FixedCIDRv4: %v", err)
+++     }
+++
+++     if ip, err := ipAllocator.RequestIP(config.FixedCIDR, nil); err != nil {
+++             t.Fatalf("Failed to request IP to allocator: %v", err)
+++     } else if expected := "192.168.2.1"; ip.String() != expected {
+++             t.Fatalf("Expected allocated IP %s, got %s", expected, ip)
+++     }
+++}
+++
+++func TestSetupBadFixedCIDRv4(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     config := &networkConfiguration{
+++             BridgeName:  DefaultBridgeName,
+++             AddressIPv4: &net.IPNet{IP: net.ParseIP("192.168.1.1"), Mask: net.CIDRMask(24, 32)},
+++             FixedCIDR:   &net.IPNet{IP: net.ParseIP("192.168.2.0"), Mask: net.CIDRMask(24, 32)}}
+++     br := &bridgeInterface{}
+++
+++     if err := setupDevice(config, br); err != nil {
+++             t.Fatalf("Bridge creation failed: %v", err)
+++     }
+++     if err := setupBridgeIPv4(config, br); err != nil {
+++             t.Fatalf("Assign IPv4 to bridge failed: %v", err)
+++     }
+++
+++     err := setupFixedCIDRv4(config, br)
+++     if err == nil {
+++             t.Fatal("Setup bridge FixedCIDRv4 should have failed")
+++     }
+++
+++     if _, ok := err.(*FixedCIDRv4Error); !ok {
+++             t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+++     }
+++
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b2a949be5bfd8abe99bf1ee6878fdaf537d9fbfd
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,27 @@@@
+++package bridge
+++
+++import (
+++     "os"
+++
+++     log "github.com/Sirupsen/logrus"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++func setupFixedCIDRv6(config *networkConfiguration, i *bridgeInterface) error {
+++     log.Debugf("Using IPv6 subnet: %v", config.FixedCIDRv6)
+++     if err := ipAllocator.RegisterSubnet(config.FixedCIDRv6, config.FixedCIDRv6); err != nil {
+++             return &FixedCIDRv6Error{Net: config.FixedCIDRv6, Err: err}
+++     }
+++
+++     // Setting route to global IPv6 subnet
+++     log.Debugf("Adding route to IPv6 network %s via device %s", config.FixedCIDRv6.String(), config.BridgeName)
+++     err := netlink.RouteAdd(&netlink.Route{
+++             Scope:     netlink.SCOPE_UNIVERSE,
+++             LinkIndex: i.Link.Attrs().Index,
+++             Dst:       config.FixedCIDRv6,
+++     })
+++     if err != nil && !os.IsExist(err) {
+++             log.Errorf("Could not add route to IPv6 network %s via device %s", config.FixedCIDRv6.String(), config.BridgeName)
+++     }
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1fa1327851cd59959de225edec5a28dadac6e262
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,44 @@@@
+++package bridge
+++
+++import (
+++     "net"
+++     "testing"
+++
+++     "github.com/docker/libnetwork/sandbox"
+++)
+++
+++func TestSetupFixedCIDRv6(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     config := &networkConfiguration{}
+++     br := newInterface(config)
+++
+++     _, config.FixedCIDRv6, _ = net.ParseCIDR("2002:db8::/48")
+++     if err := setupDevice(config, br); err != nil {
+++             t.Fatalf("Bridge creation failed: %v", err)
+++     }
+++     if err := setupBridgeIPv4(config, br); err != nil {
+++             t.Fatalf("Assign IPv4 to bridge failed: %v", err)
+++     }
+++
+++     if err := setupBridgeIPv6(config, br); err != nil {
+++             t.Fatalf("Assign IPv4 to bridge failed: %v", err)
+++     }
+++
+++     if err := setupFixedCIDRv6(config, br); err != nil {
+++             t.Fatalf("Failed to setup bridge FixedCIDRv6: %v", err)
+++     }
+++
+++     var ip net.IP
+++     if ip, err := ipAllocator.RequestIP(config.FixedCIDRv6, nil); err != nil {
+++             t.Fatalf("Failed to request IP to allocator: %v", err)
+++     } else if expected := "2002:db8::1"; ip.String() != expected {
+++             t.Fatalf("Expected allocated IP %s, got %s", expected, ip)
+++     }
+++
+++     if err := ipAllocator.ReleaseIP(config.FixedCIDRv6, ip); err != nil {
+++             t.Fatalf("Failed to release IP from allocator: %v", err)
+++     } else if _, err := ipAllocator.RequestIP(config.FixedCIDRv6, ip); err != nil {
+++             t.Fatalf("Failed to request a released IP: %v", err)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..2a8c73301a66064e5bae870e9ac7b32334aede03
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,25 @@@@
+++package bridge
+++
+++import (
+++     "fmt"
+++     "io/ioutil"
+++)
+++
+++const (
+++     ipv4ForwardConf     = "/proc/sys/net/ipv4/ip_forward"
+++     ipv4ForwardConfPerm = 0644
+++)
+++
+++func setupIPForwarding(config *configuration) error {
+++     // Sanity Check
+++     if config.EnableIPForwarding == false {
+++             return &ErrIPFwdCfg{}
+++     }
+++
+++     // Enable IPv4 forwarding
+++     if err := ioutil.WriteFile(ipv4ForwardConf, []byte{'1', '\n'}, ipv4ForwardConfPerm); err != nil {
+++             return fmt.Errorf("Setup IP forwarding failed: %v", err)
+++     }
+++
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..bea4cc00cf2bd8be6783b09048f744c0a616c466
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,75 @@@@
+++package bridge
+++
+++import (
+++     "bytes"
+++     "io/ioutil"
+++     "testing"
+++)
+++
+++func TestSetupIPForwarding(t *testing.T) {
+++     // Read current setting and ensure the original value gets restored
+++     procSetting := readCurrentIPForwardingSetting(t)
+++     defer reconcileIPForwardingSetting(t, procSetting)
+++
+++     // Disable IP Forwarding if enabled
+++     if bytes.Compare(procSetting, []byte("1\n")) == 0 {
+++             writeIPForwardingSetting(t, []byte{'0', '\n'})
+++     }
+++
+++     // Create test interface with ip forwarding setting enabled
+++     config := &configuration{
+++             EnableIPForwarding: true}
+++
+++     // Set IP Forwarding
+++     if err := setupIPForwarding(config); err != nil {
+++             t.Fatalf("Failed to setup IP forwarding: %v", err)
+++     }
+++
+++     // Read new setting
+++     procSetting = readCurrentIPForwardingSetting(t)
+++     if bytes.Compare(procSetting, []byte("1\n")) != 0 {
+++             t.Fatalf("Failed to effectively setup IP forwarding")
+++     }
+++}
+++
+++func TestUnexpectedSetupIPForwarding(t *testing.T) {
+++     // Read current setting and ensure the original value gets restored
+++     procSetting := readCurrentIPForwardingSetting(t)
+++     defer reconcileIPForwardingSetting(t, procSetting)
+++
+++     // Create test interface without ip forwarding setting enabled
+++     config := &configuration{
+++             EnableIPForwarding: false}
+++
+++     // Attempt Set IP Forwarding
+++     err := setupIPForwarding(config)
+++     if err == nil {
+++             t.Fatal("Setup IP forwarding was expected to fail")
+++     }
+++
+++     if _, ok := err.(*ErrIPFwdCfg); !ok {
+++             t.Fatalf("Setup IP forwarding failed with unexpected error: %v", err)
+++     }
+++}
+++
+++func readCurrentIPForwardingSetting(t *testing.T) []byte {
+++     procSetting, err := ioutil.ReadFile(ipv4ForwardConf)
+++     if err != nil {
+++             t.Fatalf("Can't execute test: Failed to read current IP forwarding setting: %v", err)
+++     }
+++     return procSetting
+++}
+++
+++func writeIPForwardingSetting(t *testing.T, chars []byte) {
+++     err := ioutil.WriteFile(ipv4ForwardConf, chars, ipv4ForwardConfPerm)
+++     if err != nil {
+++             t.Fatalf("Can't execute or cleanup after test: Failed to reset IP forwarding: %v", err)
+++     }
+++}
+++
+++func reconcileIPForwardingSetting(t *testing.T, original []byte) {
+++     current := readCurrentIPForwardingSetting(t)
+++     if bytes.Compare(original, current) != 0 {
+++             writeIPForwardingSetting(t, original)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..fa241c1b347aa0feca5a012b3710f2051c8b70c4
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,208 @@@@
+++package bridge
+++
+++import (
+++     "fmt"
+++     "net"
+++
+++     "github.com/docker/libnetwork/iptables"
+++     "github.com/docker/libnetwork/netutils"
+++)
+++
+++// DockerChain: DOCKER iptable chain name
+++const (
+++     DockerChain = "DOCKER"
+++)
+++
+++func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInterface) error {
+++     // Sanity check.
+++     if config.EnableIPTables == false {
+++             return IPTableCfgError(config.BridgeName)
+++     }
+++
+++     hairpinMode := !config.EnableUserlandProxy
+++
+++     addrv4, _, err := netutils.GetIfaceAddr(config.BridgeName)
+++     if err != nil {
+++             return fmt.Errorf("Failed to setup IP tables, cannot acquire Interface address: %s", err.Error())
+++     }
+++     if err = setupIPTablesInternal(config.BridgeName, addrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, true); err != nil {
+++             return fmt.Errorf("Failed to Setup IP tables: %s", err.Error())
+++     }
+++
+++     _, err = iptables.NewChain(DockerChain, config.BridgeName, iptables.Nat, hairpinMode)
+++     if err != nil {
+++             return fmt.Errorf("Failed to create NAT chain: %s", err.Error())
+++     }
+++
+++     chain, err := iptables.NewChain(DockerChain, config.BridgeName, iptables.Filter, hairpinMode)
+++     if err != nil {
+++             return fmt.Errorf("Failed to create FILTER chain: %s", err.Error())
+++     }
+++
+++     n.portMapper.SetIptablesChain(chain)
+++
+++     return nil
+++}
+++
+++type iptRule struct {
+++     table   iptables.Table
+++     chain   string
+++     preArgs []string
+++     args    []string
+++}
+++
+++func setupIPTablesInternal(bridgeIface string, addr net.Addr, icc, ipmasq, hairpin, enable bool) error {
+++
+++     var (
+++             address   = addr.String()
+++             natRule   = iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-s", address, "!", "-o", bridgeIface, "-j", "MASQUERADE"}}
+++             hpNatRule = iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "MASQUERADE"}}
+++             outRule   = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}}
+++             inRule    = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-o", bridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}}
+++     )
+++
+++     // Set NAT.
+++     if ipmasq {
+++             if err := programChainRule(natRule, "NAT", enable); err != nil {
+++                     return err
+++             }
+++     }
+++
+++     // In hairpin mode, masquerade traffic from localhost
+++     if hairpin {
+++             if err := programChainRule(hpNatRule, "MASQ LOCAL HOST", enable); err != nil {
+++                     return err
+++             }
+++     }
+++
+++     // Set Inter Container Communication.
+++     if err := setIcc(bridgeIface, icc, enable); err != nil {
+++             return err
+++     }
+++
+++     // Set Accept on all non-intercontainer outgoing packets.
+++     if err := programChainRule(outRule, "ACCEPT NON_ICC OUTGOING", enable); err != nil {
+++             return err
+++     }
+++
+++     // Set Accept on incoming packets for existing connections.
+++     if err := programChainRule(inRule, "ACCEPT INCOMING", enable); err != nil {
+++             return err
+++     }
+++
+++     return nil
+++}
+++
+++func programChainRule(rule iptRule, ruleDescr string, insert bool) error {
+++     var (
+++             prefix    []string
+++             operation string
+++             condition bool
+++             doesExist = iptables.Exists(rule.table, rule.chain, rule.args...)
+++     )
+++
+++     if insert {
+++             condition = !doesExist
+++             prefix = []string{"-I", rule.chain}
+++             operation = "enable"
+++     } else {
+++             condition = doesExist
+++             prefix = []string{"-D", rule.chain}
+++             operation = "disable"
+++     }
+++     if rule.preArgs != nil {
+++             prefix = append(rule.preArgs, prefix...)
+++     }
+++
+++     if condition {
+++             if output, err := iptables.Raw(append(prefix, rule.args...)...); err != nil {
+++                     return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error())
+++             } else if len(output) != 0 {
+++                     return &iptables.ChainError{Chain: rule.chain, Output: output}
+++             }
+++     }
+++
+++     return nil
+++}
+++
+++func setIcc(bridgeIface string, iccEnable, insert bool) error {
+++     var (
+++             table      = iptables.Filter
+++             chain      = "FORWARD"
+++             args       = []string{"-i", bridgeIface, "-o", bridgeIface, "-j"}
+++             acceptArgs = append(args, "ACCEPT")
+++             dropArgs   = append(args, "DROP")
+++     )
+++
+++     if insert {
+++             if !iccEnable {
+++                     iptables.Raw(append([]string{"-D", chain}, acceptArgs...)...)
+++
+++                     if !iptables.Exists(table, chain, dropArgs...) {
+++                             if output, err := iptables.Raw(append([]string{"-A", chain}, dropArgs...)...); err != nil {
+++                                     return fmt.Errorf("Unable to prevent intercontainer communication: %s", err.Error())
+++                             } else if len(output) != 0 {
+++                                     return fmt.Errorf("Error disabling intercontainer communication: %s", output)
+++                             }
+++                     }
+++             } else {
+++                     iptables.Raw(append([]string{"-D", chain}, dropArgs...)...)
+++
+++                     if !iptables.Exists(table, chain, acceptArgs...) {
+++                             if output, err := iptables.Raw(append([]string{"-I", chain}, acceptArgs...)...); err != nil {
+++                                     return fmt.Errorf("Unable to allow intercontainer communication: %s", err.Error())
+++                             } else if len(output) != 0 {
+++                                     return fmt.Errorf("Error enabling intercontainer communication: %s", output)
+++                             }
+++                     }
+++             }
+++     } else {
+++             // Remove any ICC rule.
+++             if !iccEnable {
+++                     if iptables.Exists(table, chain, dropArgs...) {
+++                             iptables.Raw(append([]string{"-D", chain}, dropArgs...)...)
+++                     }
+++             } else {
+++                     if iptables.Exists(table, chain, acceptArgs...) {
+++                             iptables.Raw(append([]string{"-D", chain}, acceptArgs...)...)
+++                     }
+++             }
+++     }
+++
+++     return nil
+++}
+++
+++// Control Inter Network Communication. Install/remove only if it is not/is present.
+++func setINC(network1, network2 string, enable bool) error {
+++     var (
+++             table = iptables.Filter
+++             chain = "FORWARD"
+++             args  = [2][]string{{"-s", network1, "-d", network2, "-j", "DROP"}, {"-s", network2, "-d", network1, "-j", "DROP"}}
+++     )
+++
+++     if enable {
+++             for i := 0; i < 2; i++ {
+++                     if iptables.Exists(table, chain, args[i]...) {
+++                             continue
+++                     }
+++                     if output, err := iptables.Raw(append([]string{"-I", chain}, args[i]...)...); err != nil {
+++                             return fmt.Errorf("unable to add inter-network communication rule: %s", err.Error())
+++                     } else if len(output) != 0 {
+++                             return fmt.Errorf("error adding inter-network communication rule: %s", string(output))
+++                     }
+++             }
+++     } else {
+++             for i := 0; i < 2; i++ {
+++                     if !iptables.Exists(table, chain, args[i]...) {
+++                             continue
+++                     }
+++                     if output, err := iptables.Raw(append([]string{"-D", chain}, args[i]...)...); err != nil {
+++                             return fmt.Errorf("unable to remove inter-network communication rule: %s", err.Error())
+++                     } else if len(output) != 0 {
+++                             return fmt.Errorf("error removing inter-network communication rule: %s", string(output))
+++                     }
+++             }
+++     }
+++
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..14420bf1215884b30e5e7965ba1cea8b8694ec9b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,105 @@@@
+++package bridge
+++
+++import (
+++     "net"
+++     "testing"
+++
+++     "github.com/docker/libnetwork/iptables"
+++     "github.com/docker/libnetwork/portmapper"
+++     "github.com/docker/libnetwork/sandbox"
+++)
+++
+++const (
+++     iptablesTestBridgeIP = "192.168.42.1"
+++)
+++
+++func TestProgramIPTable(t *testing.T) {
+++     // Create a test bridge with a basic bridge configuration (name + IPv4).
+++     defer sandbox.SetupTestOSContext(t)()
+++     createTestBridge(getBasicTestConfig(), &bridgeInterface{}, t)
+++
+++     // Store various iptables chain rules we care for.
+++     rules := []struct {
+++             rule  iptRule
+++             descr string
+++     }{
+++             {iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-d", "127.1.2.3", "-i", "lo", "-o", "lo", "-j", "DROP"}}, "Test Loopback"},
+++             {iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-s", iptablesTestBridgeIP, "!", "-o", DefaultBridgeName, "-j", "MASQUERADE"}}, "NAT Test"},
+++             {iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", DefaultBridgeName, "!", "-o", DefaultBridgeName, "-j", "ACCEPT"}}, "Test ACCEPT NON_ICC OUTGOING"},
+++             {iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-o", DefaultBridgeName, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}}, "Test ACCEPT INCOMING"},
+++             {iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", DefaultBridgeName, "-o", DefaultBridgeName, "-j", "ACCEPT"}}, "Test enable ICC"},
+++             {iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", DefaultBridgeName, "-o", DefaultBridgeName, "-j", "DROP"}}, "Test disable ICC"},
+++     }
+++
+++     // Assert the chain rules' insertion and removal.
+++     for _, c := range rules {
+++             assertIPTableChainProgramming(c.rule, c.descr, t)
+++     }
+++}
+++
+++func TestSetupIPTables(t *testing.T) {
+++     // Create a test bridge with a basic bridge configuration (name + IPv4).
+++     defer sandbox.SetupTestOSContext(t)()
+++     config := getBasicTestConfig()
+++     br := &bridgeInterface{}
+++
+++     createTestBridge(config, br, t)
+++
+++     // Modify iptables params in base configuration and apply them.
+++     config.EnableIPTables = true
+++     assertBridgeConfig(config, br, t)
+++
+++     config.EnableIPMasquerade = true
+++     assertBridgeConfig(config, br, t)
+++
+++     config.EnableICC = true
+++     assertBridgeConfig(config, br, t)
+++
+++     config.EnableIPMasquerade = false
+++     assertBridgeConfig(config, br, t)
+++}
+++
+++func getBasicTestConfig() *networkConfiguration {
+++     config := &networkConfiguration{
+++             BridgeName:  DefaultBridgeName,
+++             AddressIPv4: &net.IPNet{IP: net.ParseIP(iptablesTestBridgeIP), Mask: net.CIDRMask(16, 32)}}
+++     return config
+++}
+++
+++func createTestBridge(config *networkConfiguration, br *bridgeInterface, t *testing.T) {
+++     if err := setupDevice(config, br); err != nil {
+++             t.Fatalf("Failed to create the testing Bridge: %s", err.Error())
+++     }
+++     if err := setupBridgeIPv4(config, br); err != nil {
+++             t.Fatalf("Failed to bring up the testing Bridge: %s", err.Error())
+++     }
+++}
+++
+++// Assert base function which pushes iptables chain rules on insertion and removal.
+++func assertIPTableChainProgramming(rule iptRule, descr string, t *testing.T) {
+++     // Add
+++     if err := programChainRule(rule, descr, true); err != nil {
+++             t.Fatalf("Failed to program iptable rule %s: %s", descr, err.Error())
+++     }
+++     if iptables.Exists(rule.table, rule.chain, rule.args...) == false {
+++             t.Fatalf("Failed to effectively program iptable rule: %s", descr)
+++     }
+++
+++     // Remove
+++     if err := programChainRule(rule, descr, false); err != nil {
+++             t.Fatalf("Failed to remove iptable rule %s: %s", descr, err.Error())
+++     }
+++     if iptables.Exists(rule.table, rule.chain, rule.args...) == true {
+++             t.Fatalf("Failed to effectively remove iptable rule: %s", descr)
+++     }
+++}
+++
+++// Assert function which pushes chains based on bridge config parameters.
+++func assertBridgeConfig(config *networkConfiguration, br *bridgeInterface, t *testing.T) {
+++     nw := bridgeNetwork{portMapper: portmapper.New()}
+++     // Attempt programming of ip tables.
+++     err := nw.setupIPTables(config, br)
+++     if err != nil {
+++             t.Fatalf("%v", err)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..91a9a6bcf124a48f835df2690194c14fc9f921ec
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,140 @@@@
+++package bridge
+++
+++import (
+++     "fmt"
+++     "io/ioutil"
+++     "net"
+++     "path/filepath"
+++
+++     log "github.com/Sirupsen/logrus"
+++     "github.com/docker/libnetwork/netutils"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++var bridgeNetworks []*net.IPNet
+++
+++func init() {
+++     // Here we don't follow the convention of using the 1st IP of the range for the gateway.
+++     // This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges.
+++     // In theory this shouldn't matter - in practice there's bound to be a few scripts relying
+++     // on the internal addressing or other stupid things like that.
+++     // They shouldn't, but hey, let's not break them unless we really have to.
+++     // Don't use 172.16.0.0/16, it conflicts with EC2 DNS 172.16.0.23
+++
+++     // 172.[17-31].42.1/16
+++     mask := []byte{255, 255, 0, 0}
+++     for i := 17; i < 32; i++ {
+++             bridgeNetworks = append(bridgeNetworks, &net.IPNet{IP: []byte{172, byte(i), 42, 1}, Mask: mask})
+++     }
+++     // 10.[0-255].42.1/16
+++     for i := 0; i < 256; i++ {
+++             bridgeNetworks = append(bridgeNetworks, &net.IPNet{IP: []byte{10, byte(i), 42, 1}, Mask: mask})
+++     }
+++     // 192.168.[42-44].1/24
+++     mask24 := []byte{255, 255, 255, 0}
+++     for i := 42; i < 45; i++ {
+++             bridgeNetworks = append(bridgeNetworks, &net.IPNet{IP: []byte{192, 168, byte(i), 1}, Mask: mask24})
+++     }
+++}
+++
+++func setupBridgeIPv4(config *networkConfiguration, i *bridgeInterface) error {
+++     addrv4, _, err := i.addresses()
+++     if err != nil {
+++             return err
+++     }
+++
+++     // Check if we have an IP address already on the bridge.
+++     if addrv4.IPNet != nil {
+++             // Make sure to store bridge network and default gateway before getting out.
+++             i.bridgeIPv4 = addrv4.IPNet
+++             i.gatewayIPv4 = addrv4.IPNet.IP
+++             return nil
+++     }
+++
+++     // Do not try to configure IPv4 on a non-default bridge unless you are
+++     // specifically asked to do so.
+++     if config.BridgeName != DefaultBridgeName && !config.AllowNonDefaultBridge {
+++             return NonDefaultBridgeExistError(config.BridgeName)
+++     }
+++
+++     bridgeIPv4, err := electBridgeIPv4(config)
+++     if err != nil {
+++             return err
+++     }
+++
+++     log.Debugf("Creating bridge interface %q with network %s", config.BridgeName, bridgeIPv4)
+++     if err := netlink.AddrAdd(i.Link, &netlink.Addr{IPNet: bridgeIPv4}); err != nil {
+++             return &IPv4AddrAddError{IP: bridgeIPv4, Err: err}
+++     }
+++
+++     // Store bridge network and default gateway
+++     i.bridgeIPv4 = bridgeIPv4
+++     i.gatewayIPv4 = i.bridgeIPv4.IP
+++
+++     return nil
+++}
+++
+++func allocateBridgeIP(config *networkConfiguration, i *bridgeInterface) error {
+++     // Because of the way ipallocator manages the container address space,
+++     // reserve bridge address only if it belongs to the container network
+++     // (if defined), no need otherwise
+++     if config.FixedCIDR == nil || config.FixedCIDR.Contains(i.bridgeIPv4.IP) {
+++             ipAllocator.RequestIP(i.bridgeIPv4, i.bridgeIPv4.IP)
+++     }
+++     return nil
+++}
+++
+++func electBridgeIPv4(config *networkConfiguration) (*net.IPNet, error) {
+++     // Use the requested IPv4 CIDR when available.
+++     if config.AddressIPv4 != nil {
+++             return config.AddressIPv4, nil
+++     }
+++
+++     // We don't check for an error here, because we don't really care if we
+++     // can't read /etc/resolv.conf. So instead we skip the append if resolvConf
+++     // is nil. It either doesn't exist, or we can't read it for some reason.
+++     nameservers := []string{}
+++     if resolvConf, _ := readResolvConf(); resolvConf != nil {
+++             nameservers = append(nameservers, getNameserversAsCIDR(resolvConf)...)
+++     }
+++
+++     // Try to automatically elect appropriate bridge IPv4 settings.
+++     for _, n := range bridgeNetworks {
+++             if err := netutils.CheckNameserverOverlaps(nameservers, n); err == nil {
+++                     if err := netutils.CheckRouteOverlaps(n); err == nil {
+++                             return n, nil
+++                     }
+++             }
+++     }
+++
+++     return nil, IPv4AddrRangeError(config.BridgeName)
+++}
+++
+++func setupGatewayIPv4(config *networkConfiguration, i *bridgeInterface) error {
+++     if !i.bridgeIPv4.Contains(config.DefaultGatewayIPv4) {
+++             return &ErrInvalidGateway{}
+++     }
+++
+++     // Because of the way ipallocator manages the container address space,
+++     // reserve default gw address only if it belongs to the container network
+++     // (if defined), no need otherwise
+++     if config.FixedCIDR == nil || config.FixedCIDR.Contains(config.DefaultGatewayIPv4) {
+++             if _, err := ipAllocator.RequestIP(i.bridgeIPv4, config.DefaultGatewayIPv4); err != nil {
+++                     return err
+++             }
+++     }
+++
+++     // Store requested default gateway
+++     i.gatewayIPv4 = config.DefaultGatewayIPv4
+++
+++     return nil
+++}
+++
+++func setupLoopbackAdressesRouting(config *networkConfiguration, i *bridgeInterface) error {
+++     // Enable loopback adresses routing
+++     sysPath := filepath.Join("/proc/sys/net/ipv4/conf", config.BridgeName, "route_localnet")
+++     if err := ioutil.WriteFile(sysPath, []byte{'1', '\n'}, 0644); err != nil {
+++             return fmt.Errorf("Unable to enable local routing for hairpin mode: %v", err)
+++     }
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..371021974451452eb1a2b1e8800e31500b21e210
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,111 @@@@
+++package bridge
+++
+++import (
+++     "net"
+++     "testing"
+++
+++     "github.com/docker/libnetwork/sandbox"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++func setupTestInterface(t *testing.T) (*networkConfiguration, *bridgeInterface) {
+++     config := &networkConfiguration{
+++             BridgeName: DefaultBridgeName}
+++     br := &bridgeInterface{}
+++
+++     if err := setupDevice(config, br); err != nil {
+++             t.Fatalf("Bridge creation failed: %v", err)
+++     }
+++     return config, br
+++}
+++
+++func TestSetupBridgeIPv4Fixed(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     ip, netw, err := net.ParseCIDR("192.168.1.1/24")
+++     if err != nil {
+++             t.Fatalf("Failed to parse bridge IPv4: %v", err)
+++     }
+++
+++     config, br := setupTestInterface(t)
+++     config.AddressIPv4 = &net.IPNet{IP: ip, Mask: netw.Mask}
+++     if err := setupBridgeIPv4(config, br); err != nil {
+++             t.Fatalf("Failed to setup bridge IPv4: %v", err)
+++     }
+++
+++     addrsv4, err := netlink.AddrList(br.Link, netlink.FAMILY_V4)
+++     if err != nil {
+++             t.Fatalf("Failed to list device IPv4 addresses: %v", err)
+++     }
+++
+++     var found bool
+++     for _, addr := range addrsv4 {
+++             if config.AddressIPv4.String() == addr.IPNet.String() {
+++                     found = true
+++                     break
+++             }
+++     }
+++
+++     if !found {
+++             t.Fatalf("Bridge device does not have requested IPv4 address %v", config.AddressIPv4)
+++     }
+++}
+++
+++func TestSetupBridgeIPv4Auto(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     config, br := setupTestInterface(t)
+++     if err := setupBridgeIPv4(config, br); err != nil {
+++             t.Fatalf("Failed to setup bridge IPv4: %v", err)
+++     }
+++
+++     addrsv4, err := netlink.AddrList(br.Link, netlink.FAMILY_V4)
+++     if err != nil {
+++             t.Fatalf("Failed to list device IPv4 addresses: %v", err)
+++     }
+++
+++     var found bool
+++     for _, addr := range addrsv4 {
+++             if bridgeNetworks[0].String() == addr.IPNet.String() {
+++                     found = true
+++                     break
+++             }
+++     }
+++
+++     if !found {
+++             t.Fatalf("Bridge device does not have the automatic IPv4 address %v", bridgeNetworks[0].String())
+++     }
+++}
+++
+++func TestSetupGatewayIPv4(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     ip, nw, _ := net.ParseCIDR("192.168.0.24/16")
+++     nw.IP = ip
+++     gw := net.ParseIP("192.168.2.254")
+++
+++     config := &networkConfiguration{
+++             BridgeName:         DefaultBridgeName,
+++             DefaultGatewayIPv4: gw}
+++
+++     br := &bridgeInterface{bridgeIPv4: nw}
+++
+++     if err := setupGatewayIPv4(config, br); err != nil {
+++             t.Fatalf("Set Default Gateway failed: %v", err)
+++     }
+++
+++     if !gw.Equal(br.gatewayIPv4) {
+++             t.Fatalf("Set Default Gateway failed. Expected %v, Found %v", gw, br.gatewayIPv4)
+++     }
+++}
+++
+++func TestCheckPreallocatedBridgeNetworks(t *testing.T) {
+++     // Just make sure the bridge networks are created the way we want (172.17.x.x/16)
+++     for i := 0; i < len(bridgeNetworks); i++ {
+++             fb := bridgeNetworks[i].IP[0]
+++             ones, _ := bridgeNetworks[i].Mask.Size()
+++             if ((fb == 172 || fb == 10) && ones != 16) || (fb == 192 && ones != 24) {
+++                     t.Fatalf("Wrong mask for preallocated bridge network: %s", bridgeNetworks[i].String())
+++             }
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b534644edf093f566b74a61093e02842479a2592
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,81 @@@@
+++package bridge
+++
+++import (
+++     "fmt"
+++     "io/ioutil"
+++     "net"
+++
+++     "github.com/Sirupsen/logrus"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++var bridgeIPv6 *net.IPNet
+++
+++const (
+++     bridgeIPv6Str       = "fe80::1/64"
+++     ipv6ForwardConfPerm = 0644
+++)
+++
+++func init() {
+++     // We allow ourselves to panic in this special case because we indicate a
+++     // failure to parse a compile-time define constant.
+++     if ip, netw, err := net.ParseCIDR(bridgeIPv6Str); err == nil {
+++             bridgeIPv6 = &net.IPNet{IP: ip, Mask: netw.Mask}
+++     } else {
+++             panic(fmt.Sprintf("Cannot parse default bridge IPv6 address %q: %v", bridgeIPv6Str, err))
+++     }
+++}
+++
+++func setupBridgeIPv6(config *networkConfiguration, i *bridgeInterface) error {
+++     // Enable IPv6 on the bridge
+++     procFile := "/proc/sys/net/ipv6/conf/" + config.BridgeName + "/disable_ipv6"
+++     if err := ioutil.WriteFile(procFile, []byte{'0', '\n'}, ipv6ForwardConfPerm); err != nil {
+++             return fmt.Errorf("Unable to enable IPv6 addresses on bridge: %v", err)
+++     }
+++
+++     _, addrsv6, err := i.addresses()
+++     if err != nil {
+++             return err
+++     }
+++
+++     // Add the default link local ipv6 address if it doesn't exist
+++     if !findIPv6Address(netlink.Addr{IPNet: bridgeIPv6}, addrsv6) {
+++             if err := netlink.AddrAdd(i.Link, &netlink.Addr{IPNet: bridgeIPv6}); err != nil {
+++                     return &IPv6AddrAddError{IP: bridgeIPv6, Err: err}
+++             }
+++     }
+++
+++     // Store bridge network and default gateway
+++     i.bridgeIPv6 = bridgeIPv6
+++     i.gatewayIPv6 = i.bridgeIPv6.IP
+++
+++     return nil
+++}
+++
+++func setupGatewayIPv6(config *networkConfiguration, i *bridgeInterface) error {
+++     if config.FixedCIDRv6 == nil {
+++             return &ErrInvalidContainerSubnet{}
+++     }
+++     if !config.FixedCIDRv6.Contains(config.DefaultGatewayIPv6) {
+++             return &ErrInvalidGateway{}
+++     }
+++     if _, err := ipAllocator.RequestIP(config.FixedCIDRv6, config.DefaultGatewayIPv6); err != nil {
+++             return err
+++     }
+++
+++     // Store requested default gateway
+++     i.gatewayIPv6 = config.DefaultGatewayIPv6
+++
+++     return nil
+++}
+++
+++func setupIPv6Forwarding(config *networkConfiguration, i *bridgeInterface) error {
+++     // Enable IPv6 forwarding
+++     if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/default/forwarding", []byte{'1', '\n'}, ipv6ForwardConfPerm); err != nil {
+++             logrus.Warnf("Unable to enable IPv6 default forwarding: %v", err)
+++     }
+++     if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/all/forwarding", []byte{'1', '\n'}, ipv6ForwardConfPerm); err != nil {
+++             logrus.Warnf("Unable to enable IPv6 all forwarding: %v", err)
+++     }
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b3dc499de4d559950ef3d20f4af0739e9d9deb2c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,70 @@@@
+++package bridge
+++
+++import (
+++     "bytes"
+++     "fmt"
+++     "io/ioutil"
+++     "net"
+++     "testing"
+++
+++     "github.com/docker/libnetwork/sandbox"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++func TestSetupIPv6(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     config, br := setupTestInterface(t)
+++     if err := setupBridgeIPv6(config, br); err != nil {
+++             t.Fatalf("Failed to setup bridge IPv6: %v", err)
+++     }
+++
+++     procSetting, err := ioutil.ReadFile(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/disable_ipv6", config.BridgeName))
+++     if err != nil {
+++             t.Fatalf("Failed to read disable_ipv6 kernel setting: %v", err)
+++     }
+++
+++     if expected := []byte("0\n"); bytes.Compare(expected, procSetting) != 0 {
+++             t.Fatalf("Invalid kernel setting disable_ipv6: expected %q, got %q", string(expected), string(procSetting))
+++     }
+++
+++     addrsv6, err := netlink.AddrList(br.Link, netlink.FAMILY_V6)
+++     if err != nil {
+++             t.Fatalf("Failed to list device IPv6 addresses: %v", err)
+++     }
+++
+++     var found bool
+++     for _, addr := range addrsv6 {
+++             if bridgeIPv6Str == addr.IPNet.String() {
+++                     found = true
+++                     break
+++             }
+++     }
+++
+++     if !found {
+++             t.Fatalf("Bridge device does not have requested IPv6 address %v", bridgeIPv6Str)
+++     }
+++
+++}
+++
+++func TestSetupGatewayIPv6(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     _, nw, _ := net.ParseCIDR("2001:db8:ea9:9abc:ffff::/80")
+++     gw := net.ParseIP("2001:db8:ea9:9abc:ffff::254")
+++
+++     config := &networkConfiguration{
+++             BridgeName:         DefaultBridgeName,
+++             FixedCIDRv6:        nw,
+++             DefaultGatewayIPv6: gw}
+++
+++     br := &bridgeInterface{}
+++
+++     if err := setupGatewayIPv6(config, br); err != nil {
+++             t.Fatalf("Set Default Gateway failed: %v", err)
+++     }
+++
+++     if !gw.Equal(br.gatewayIPv6) {
+++             t.Fatalf("Set Default Gateway failed. Expected %v, Found %v", gw, br.gatewayIPv6)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..6d4d66982c19d50e85763f151b3392ca18c06538
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,46 @@@@
+++package bridge
+++
+++import (
+++     "github.com/vishvananda/netlink"
+++)
+++
+++func setupVerifyAndReconcile(config *networkConfiguration, i *bridgeInterface) error {
+++     // Fetch a single IPv4 and a slice of IPv6 addresses from the bridge.
+++     addrv4, addrsv6, err := i.addresses()
+++     if err != nil {
+++             return err
+++     }
+++
+++     // Verify that the bridge does have an IPv4 address.
+++     if addrv4.IPNet == nil {
+++             return &ErrNoIPAddr{}
+++     }
+++
+++     // Verify that the bridge IPv4 address matches the requested configuration.
+++     if config.AddressIPv4 != nil && !addrv4.IP.Equal(config.AddressIPv4.IP) {
+++             return &IPv4AddrNoMatchError{IP: addrv4.IP, CfgIP: config.AddressIPv4.IP}
+++     }
+++
+++     // Verify that one of the bridge IPv6 addresses matches the requested
+++     // configuration.
+++     if config.EnableIPv6 && !findIPv6Address(netlink.Addr{IPNet: bridgeIPv6}, addrsv6) {
+++             return (*IPv6AddrNoMatchError)(bridgeIPv6)
+++     }
+++
+++     // By this time we have either configured a new bridge with an IP address
+++     // or made sure an existing bridge's IP matches the configuration
+++     // Now is the time to cache these states in the bridgeInterface.
+++     i.bridgeIPv4 = addrv4.IPNet
+++     i.bridgeIPv6 = bridgeIPv6
+++
+++     return nil
+++}
+++
+++func findIPv6Address(addr netlink.Addr, addresses []netlink.Addr) bool {
+++     for _, addrv6 := range addresses {
+++             if addrv6.String() == addr.String() {
+++                     return true
+++             }
+++     }
+++     return false
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..27f3fa235e6f2880c249d5dab6dbe4ee24f6f9a0
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,110 @@@@
+++package bridge
+++
+++import (
+++     "net"
+++     "testing"
+++
+++     "github.com/docker/libnetwork/sandbox"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++func setupVerifyTest(t *testing.T) *bridgeInterface {
+++     inf := &bridgeInterface{}
+++
+++     br := netlink.Bridge{}
+++     br.LinkAttrs.Name = "default0"
+++     if err := netlink.LinkAdd(&br); err == nil {
+++             inf.Link = &br
+++     } else {
+++             t.Fatalf("Failed to create bridge interface: %v", err)
+++     }
+++
+++     return inf
+++}
+++
+++func TestSetupVerify(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     addrv4 := net.IPv4(192, 168, 1, 1)
+++     inf := setupVerifyTest(t)
+++     config := &networkConfiguration{}
+++     config.AddressIPv4 = &net.IPNet{IP: addrv4, Mask: addrv4.DefaultMask()}
+++
+++     if err := netlink.AddrAdd(inf.Link, &netlink.Addr{IPNet: config.AddressIPv4}); err != nil {
+++             t.Fatalf("Failed to assign IPv4 %s to interface: %v", config.AddressIPv4, err)
+++     }
+++
+++     if err := setupVerifyAndReconcile(config, inf); err != nil {
+++             t.Fatalf("Address verification failed: %v", err)
+++     }
+++}
+++
+++func TestSetupVerifyBad(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     addrv4 := net.IPv4(192, 168, 1, 1)
+++     inf := setupVerifyTest(t)
+++     config := &networkConfiguration{}
+++     config.AddressIPv4 = &net.IPNet{IP: addrv4, Mask: addrv4.DefaultMask()}
+++
+++     ipnet := &net.IPNet{IP: net.IPv4(192, 168, 1, 2), Mask: addrv4.DefaultMask()}
+++     if err := netlink.AddrAdd(inf.Link, &netlink.Addr{IPNet: ipnet}); err != nil {
+++             t.Fatalf("Failed to assign IPv4 %s to interface: %v", ipnet, err)
+++     }
+++
+++     if err := setupVerifyAndReconcile(config, inf); err == nil {
+++             t.Fatal("Address verification was expected to fail")
+++     }
+++}
+++
+++func TestSetupVerifyMissing(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     addrv4 := net.IPv4(192, 168, 1, 1)
+++     inf := setupVerifyTest(t)
+++     config := &networkConfiguration{}
+++     config.AddressIPv4 = &net.IPNet{IP: addrv4, Mask: addrv4.DefaultMask()}
+++
+++     if err := setupVerifyAndReconcile(config, inf); err == nil {
+++             t.Fatal("Address verification was expected to fail")
+++     }
+++}
+++
+++func TestSetupVerifyIPv6(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     addrv4 := net.IPv4(192, 168, 1, 1)
+++     inf := setupVerifyTest(t)
+++     config := &networkConfiguration{}
+++     config.AddressIPv4 = &net.IPNet{IP: addrv4, Mask: addrv4.DefaultMask()}
+++     config.EnableIPv6 = true
+++
+++     if err := netlink.AddrAdd(inf.Link, &netlink.Addr{IPNet: bridgeIPv6}); err != nil {
+++             t.Fatalf("Failed to assign IPv6 %s to interface: %v", bridgeIPv6, err)
+++     }
+++     if err := netlink.AddrAdd(inf.Link, &netlink.Addr{IPNet: config.AddressIPv4}); err != nil {
+++             t.Fatalf("Failed to assign IPv4 %s to interface: %v", config.AddressIPv4, err)
+++     }
+++
+++     if err := setupVerifyAndReconcile(config, inf); err != nil {
+++             t.Fatalf("Address verification failed: %v", err)
+++     }
+++}
+++
+++func TestSetupVerifyIPv6Missing(t *testing.T) {
+++     defer sandbox.SetupTestOSContext(t)()
+++
+++     addrv4 := net.IPv4(192, 168, 1, 1)
+++     inf := setupVerifyTest(t)
+++     config := &networkConfiguration{}
+++     config.AddressIPv4 = &net.IPNet{IP: addrv4, Mask: addrv4.DefaultMask()}
+++     config.EnableIPv6 = true
+++
+++     if err := netlink.AddrAdd(inf.Link, &netlink.Addr{IPNet: config.AddressIPv4}); err != nil {
+++             t.Fatalf("Failed to assign IPv4 %s to interface: %v", config.AddressIPv4, err)
+++     }
+++
+++     if err := setupVerifyAndReconcile(config, inf); err == nil {
+++             t.Fatal("Address verification was expected to fail")
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..5dbc4ef2fbff073eb74ae1dfbfe6f2dc361ed095
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,74 @@@@
+++package host
+++
+++import (
+++     "sync"
+++
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++const networkType = "host"
+++
+++type driver struct {
+++     network types.UUID
+++     sync.Mutex
+++}
+++
+++// Init registers a new instance of host driver
+++func Init(dc driverapi.DriverCallback) error {
+++     c := driverapi.Capability{
+++             Scope: driverapi.LocalScope,
+++     }
+++     return dc.RegisterDriver(networkType, &driver{}, c)
+++}
+++
+++func (d *driver) Config(option map[string]interface{}) error {
+++     return nil
+++}
+++
+++func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) error {
+++     d.Lock()
+++     defer d.Unlock()
+++
+++     if d.network != "" {
+++             return types.ForbiddenErrorf("only one instance of \"%s\" network is allowed", networkType)
+++     }
+++
+++     d.network = id
+++
+++     return nil
+++}
+++
+++func (d *driver) DeleteNetwork(nid types.UUID) error {
+++     return types.ForbiddenErrorf("network of type \"%s\" cannot be deleted", networkType)
+++}
+++
+++func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error {
+++     return nil
+++}
+++
+++func (d *driver) DeleteEndpoint(nid, eid types.UUID) error {
+++     return nil
+++}
+++
+++func (d *driver) EndpointOperInfo(nid, eid types.UUID) (map[string]interface{}, error) {
+++     return make(map[string]interface{}, 0), nil
+++}
+++
+++// Join method is invoked when a Sandbox is attached to an endpoint.
+++func (d *driver) Join(nid, eid types.UUID, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+++     if err := jinfo.SetHostsPath("/etc/hosts"); err != nil {
+++             return err
+++     }
+++
+++     return jinfo.SetResolvConfPath("/etc/resolv.conf")
+++}
+++
+++// Leave method is invoked when a Sandbox detaches from an endpoint.
+++func (d *driver) Leave(nid, eid types.UUID) error {
+++     return nil
+++}
+++
+++func (d *driver) Type() string {
+++     return networkType
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4a02de2204a5144ac64af2409881e33cb91b3e1e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,50 @@@@
+++package host
+++
+++import (
+++     "testing"
+++
+++     _ "github.com/docker/libnetwork/netutils"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++func TestDriver(t *testing.T) {
+++     d := &driver{}
+++
+++     if d.Type() != networkType {
+++             t.Fatalf("Unexpected network type returned by driver")
+++     }
+++
+++     err := d.CreateNetwork("first", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if d.network != "first" {
+++             t.Fatalf("Unexpected network id stored")
+++     }
+++
+++     err = d.CreateNetwork("second", nil)
+++     if err == nil {
+++             t.Fatalf("Second network creation should fail on this driver")
+++     }
+++     if _, ok := err.(types.ForbiddenError); !ok {
+++             t.Fatalf("Second network creation failed with unexpected error type")
+++     }
+++
+++     err = d.DeleteNetwork("first")
+++     if err == nil {
+++             t.Fatalf("network deletion should fail on this driver")
+++     }
+++     if _, ok := err.(types.ForbiddenError); !ok {
+++             t.Fatalf("network deletion failed with unexpected error type")
+++     }
+++
+++     // we don't really check if it is there or not, delete is not allowed for this driver, period.
+++     err = d.DeleteNetwork("unknown")
+++     if err == nil {
+++             t.Fatalf("any network deletion should fail on this driver")
+++     }
+++     if _, ok := err.(types.ForbiddenError); !ok {
+++             t.Fatalf("any network deletion failed with unexpected error type")
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d1f2797e5dbdbdecec0bb1bd224230f1895929a1
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,70 @@@@
+++package null
+++
+++import (
+++     "sync"
+++
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++const networkType = "null"
+++
+++type driver struct {
+++     network types.UUID
+++     sync.Mutex
+++}
+++
+++// Init registers a new instance of null driver
+++func Init(dc driverapi.DriverCallback) error {
+++     c := driverapi.Capability{
+++             Scope: driverapi.LocalScope,
+++     }
+++     return dc.RegisterDriver(networkType, &driver{}, c)
+++}
+++
+++func (d *driver) Config(option map[string]interface{}) error {
+++     return nil
+++}
+++
+++func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) error {
+++     d.Lock()
+++     defer d.Unlock()
+++
+++     if d.network != "" {
+++             return types.ForbiddenErrorf("only one instance of \"%s\" network is allowed", networkType)
+++     }
+++
+++     d.network = id
+++
+++     return nil
+++}
+++
+++func (d *driver) DeleteNetwork(nid types.UUID) error {
+++     return types.ForbiddenErrorf("network of type \"%s\" cannot be deleted", networkType)
+++}
+++
+++func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error {
+++     return nil
+++}
+++
+++func (d *driver) DeleteEndpoint(nid, eid types.UUID) error {
+++     return nil
+++}
+++
+++func (d *driver) EndpointOperInfo(nid, eid types.UUID) (map[string]interface{}, error) {
+++     return make(map[string]interface{}, 0), nil
+++}
+++
+++// Join method is invoked when a Sandbox is attached to an endpoint.
+++func (d *driver) Join(nid, eid types.UUID, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+++     return nil
+++}
+++
+++// Leave method is invoked when a Sandbox detaches from an endpoint.
+++func (d *driver) Leave(nid, eid types.UUID) error {
+++     return nil
+++}
+++
+++func (d *driver) Type() string {
+++     return networkType
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..7dfa27723493d37c3e30f1dcab00018f7490084c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,50 @@@@
+++package null
+++
+++import (
+++     "testing"
+++
+++     _ "github.com/docker/libnetwork/netutils"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++func TestDriver(t *testing.T) {
+++     d := &driver{}
+++
+++     if d.Type() != networkType {
+++             t.Fatalf("Unexpected network type returned by driver")
+++     }
+++
+++     err := d.CreateNetwork("first", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if d.network != "first" {
+++             t.Fatalf("Unexpected network id stored")
+++     }
+++
+++     err = d.CreateNetwork("second", nil)
+++     if err == nil {
+++             t.Fatalf("Second network creation should fail on this driver")
+++     }
+++     if _, ok := err.(types.ForbiddenError); !ok {
+++             t.Fatalf("Second network creation failed with unexpected error type")
+++     }
+++
+++     err = d.DeleteNetwork("first")
+++     if err == nil {
+++             t.Fatalf("network deletion should fail on this driver")
+++     }
+++     if _, ok := err.(types.ForbiddenError); !ok {
+++             t.Fatalf("network deletion failed with unexpected error type")
+++     }
+++
+++     // we don't really check if it is there or not, delete is not allowed for this driver, period.
+++     err = d.DeleteNetwork("unknown")
+++     if err == nil {
+++             t.Fatalf("any network deletion should fail on this driver")
+++     }
+++     if _, ok := err.(types.ForbiddenError); !ok {
+++             t.Fatalf("any network deletion failed with unexpected error type")
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..474970bcf534ccdbe4612968bc4598538df85e61
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,99 @@@@
+++package overlay
+++
+++import (
+++     "fmt"
+++
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/types"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++// Join method is invoked when a Sandbox is attached to an endpoint.
+++func (d *driver) Join(nid, eid types.UUID, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+++     if err := validateID(nid, eid); err != nil {
+++             return err
+++     }
+++
+++     n := d.network(nid)
+++     if n == nil {
+++             return fmt.Errorf("could not find network with id %s", nid)
+++     }
+++
+++     ep := n.endpoint(eid)
+++     if ep == nil {
+++             return fmt.Errorf("could not find endpoint with id %s", eid)
+++     }
+++
+++     if err := n.joinSandbox(); err != nil {
+++             return fmt.Errorf("network sandbox join failed: %v",
+++                     err)
+++     }
+++
+++     sbox := n.sandbox()
+++
+++     name1, name2, err := createVethPair()
+++     if err != nil {
+++             return err
+++     }
+++
+++     if err := sbox.AddInterface(name1, "veth",
+++             sbox.InterfaceOptions().Master("bridge1")); err != nil {
+++             return fmt.Errorf("could not add veth pair inside the network sandbox: %v", err)
+++     }
+++
+++     veth, err := netlink.LinkByName(name2)
+++     if err != nil {
+++             return fmt.Errorf("could not find link by name %s: %v", name2, err)
+++     }
+++
+++     if err := netlink.LinkSetHardwareAddr(veth, ep.mac); err != nil {
+++             return fmt.Errorf("could not set mac address to the container interface: %v", err)
+++     }
+++
+++     for _, iNames := range jinfo.InterfaceNames() {
+++             // Make sure to set names on the correct interface ID.
+++             if iNames.ID() == 1 {
+++                     err = iNames.SetNames(name2, "eth")
+++                     if err != nil {
+++                             return err
+++                     }
+++             }
+++     }
+++
+++     err = jinfo.SetGateway(bridgeIP.IP)
+++     if err != nil {
+++             return err
+++     }
+++
+++     d.peerDbAdd(nid, eid, ep.addr.IP, ep.mac,
+++             d.serfInstance.LocalMember().Addr, true)
+++     d.notifyCh <- ovNotify{
+++             action: "join",
+++             nid:    nid,
+++             eid:    eid,
+++     }
+++
+++     return nil
+++}
+++
+++// Leave method is invoked when a Sandbox detaches from an endpoint.
+++func (d *driver) Leave(nid, eid types.UUID) error {
+++     if err := validateID(nid, eid); err != nil {
+++             return err
+++     }
+++
+++     n := d.network(nid)
+++     if n == nil {
+++             return fmt.Errorf("could not find network with id %s", nid)
+++     }
+++
+++     d.notifyCh <- ovNotify{
+++             action: "leave",
+++             nid:    nid,
+++             eid:    eid,
+++     }
+++
+++     n.leaveSandbox()
+++
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..cbcb6074aa66eb6a3fc7b0905f9413700ee2607e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,110 @@@@
+++package overlay
+++
+++import (
+++     "encoding/binary"
+++     "fmt"
+++     "net"
+++
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/netutils"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++type endpointTable map[types.UUID]*endpoint
+++
+++type endpoint struct {
+++     id   types.UUID
+++     mac  net.HardwareAddr
+++     addr *net.IPNet
+++}
+++
+++func (n *network) endpoint(eid types.UUID) *endpoint {
+++     n.Lock()
+++     defer n.Unlock()
+++
+++     return n.endpoints[eid]
+++}
+++
+++func (n *network) addEndpoint(ep *endpoint) {
+++     n.Lock()
+++     n.endpoints[ep.id] = ep
+++     n.Unlock()
+++}
+++
+++func (n *network) deleteEndpoint(eid types.UUID) {
+++     n.Lock()
+++     delete(n.endpoints, eid)
+++     n.Unlock()
+++}
+++
+++func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointInfo,
+++     epOptions map[string]interface{}) error {
+++     if err := validateID(nid, eid); err != nil {
+++             return err
+++     }
+++
+++     n := d.network(nid)
+++     if n == nil {
+++             return fmt.Errorf("network id %q not found", nid)
+++     }
+++
+++     ep := &endpoint{
+++             id: eid,
+++     }
+++
+++     if epInfo != nil && (len(epInfo.Interfaces()) > 0) {
+++             addr := epInfo.Interfaces()[0].Address()
+++             ep.addr = &addr
+++             ep.mac = epInfo.Interfaces()[0].MacAddress()
+++             n.addEndpoint(ep)
+++             return nil
+++     }
+++
+++     ipID, err := d.ipAllocator.GetID()
+++     if err != nil {
+++             return fmt.Errorf("could not allocate ip from subnet %s: %v",
+++                     bridgeSubnet.String(), err)
+++     }
+++
+++     ep.addr = &net.IPNet{
+++             Mask: bridgeSubnet.Mask,
+++     }
+++     ep.addr.IP = make([]byte, 4)
+++
+++     binary.BigEndian.PutUint32(ep.addr.IP, bridgeSubnetInt+ipID)
+++
+++     ep.mac = netutils.GenerateRandomMAC()
+++
+++     err = epInfo.AddInterface(1, ep.mac, *ep.addr, net.IPNet{})
+++     if err != nil {
+++             return fmt.Errorf("could not add interface to endpoint info: %v", err)
+++     }
+++
+++     n.addEndpoint(ep)
+++
+++     return nil
+++}
+++
+++func (d *driver) DeleteEndpoint(nid, eid types.UUID) error {
+++     if err := validateID(nid, eid); err != nil {
+++             return err
+++     }
+++
+++     n := d.network(nid)
+++     if n == nil {
+++             return fmt.Errorf("network id %q not found", nid)
+++     }
+++
+++     ep := n.endpoint(eid)
+++     if ep == nil {
+++             return fmt.Errorf("endpoint id %q not found", eid)
+++     }
+++
+++     d.ipAllocator.Release(binary.BigEndian.Uint32(ep.addr.IP) - bridgeSubnetInt)
+++     n.deleteEndpoint(eid)
+++     return nil
+++}
+++
+++func (d *driver) EndpointOperInfo(nid, eid types.UUID) (map[string]interface{}, error) {
+++     return make(map[string]interface{}, 0), nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..be33e49b7cbb91328f3fd73b3f05d935d6a3aace
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,337 @@@@
+++package overlay
+++
+++import (
+++     "encoding/json"
+++     "fmt"
+++     "net"
+++     "sync"
+++     "syscall"
+++
+++     "github.com/Sirupsen/logrus"
+++     "github.com/docker/libnetwork/datastore"
+++     "github.com/docker/libnetwork/ipallocator"
+++     "github.com/docker/libnetwork/sandbox"
+++     "github.com/docker/libnetwork/types"
+++     "github.com/vishvananda/netlink"
+++     "github.com/vishvananda/netlink/nl"
+++)
+++
+++type networkTable map[types.UUID]*network
+++
+++type network struct {
+++     id          types.UUID
+++     vni         uint32
+++     dbIndex     uint64
+++     dbExists    bool
+++     sbox        sandbox.Sandbox
+++     endpoints   endpointTable
+++     ipAllocator *ipallocator.IPAllocator
+++     gw          net.IP
+++     vxlanName   string
+++     driver      *driver
+++     joinCnt     int
+++     sync.Mutex
+++}
+++
+++func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) error {
+++     if id == "" {
+++             return fmt.Errorf("invalid network id")
+++     }
+++
+++     n := &network{
+++             id:        id,
+++             driver:    d,
+++             endpoints: endpointTable{},
+++     }
+++
+++     n.gw = bridgeIP.IP
+++
+++     d.addNetwork(n)
+++
+++     if err := n.obtainVxlanID(); err != nil {
+++             return err
+++     }
+++
+++     return nil
+++}
+++
+++func (d *driver) DeleteNetwork(nid types.UUID) error {
+++     if nid == "" {
+++             return fmt.Errorf("invalid network id")
+++     }
+++
+++     n := d.network(nid)
+++     if n == nil {
+++             return fmt.Errorf("could not find network with id %s", nid)
+++     }
+++
+++     d.deleteNetwork(nid)
+++
+++     return n.releaseVxlanID()
+++}
+++
+++func (n *network) joinSandbox() error {
+++     n.Lock()
+++     if n.joinCnt != 0 {
+++             n.joinCnt++
+++             n.Unlock()
+++             return nil
+++     }
+++     n.joinCnt++
+++     n.Unlock()
+++
+++     return n.initSandbox()
+++}
+++
+++func (n *network) leaveSandbox() {
+++     n.Lock()
+++     n.joinCnt--
+++     if n.joinCnt != 0 {
+++             n.Unlock()
+++             return
+++     }
+++     n.Unlock()
+++
+++     n.destroySandbox()
+++}
+++
+++func (n *network) destroySandbox() {
+++     sbox := n.sandbox()
+++     if sbox != nil {
+++             for _, iface := range sbox.Info().Interfaces() {
+++                     iface.Remove()
+++             }
+++
+++             if err := deleteVxlan(n.vxlanName); err != nil {
+++                     logrus.Warnf("could not cleanup sandbox properly: %v", err)
+++             }
+++
+++             sbox.Destroy()
+++     }
+++}
+++
+++func (n *network) initSandbox() error {
+++     sbox, err := sandbox.NewSandbox(sandbox.GenerateKey(string(n.id)), true)
+++     if err != nil {
+++             return fmt.Errorf("could not create network sandbox: %v", err)
+++     }
+++
+++     // Add a bridge inside the namespace
+++     if err := sbox.AddInterface("bridge1", "br",
+++             sbox.InterfaceOptions().Address(bridgeIP),
+++             sbox.InterfaceOptions().Bridge(true)); err != nil {
+++             return fmt.Errorf("could not create bridge inside the network sandbox: %v", err)
+++     }
+++
+++     vxlanName, err := createVxlan(n.vxlanID())
+++     if err != nil {
+++             return err
+++     }
+++
+++     if err := sbox.AddInterface(vxlanName, "vxlan",
+++             sbox.InterfaceOptions().Master("bridge1")); err != nil {
+++             return fmt.Errorf("could not add vxlan interface inside the network sandbox: %v",
+++                     err)
+++     }
+++
+++     n.vxlanName = vxlanName
+++
+++     n.setSandbox(sbox)
+++
+++     n.driver.peerDbUpdateSandbox(n.id)
+++
+++     var nlSock *nl.NetlinkSocket
+++     sbox.InvokeFunc(func() {
+++             nlSock, err = nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_NEIGH)
+++             if err != nil {
+++                     err = fmt.Errorf("failed to subscribe to neighbor group netlink messages")
+++             }
+++     })
+++
+++     go n.watchMiss(nlSock)
+++
+++     return nil
+++}
+++
+++func (n *network) watchMiss(nlSock *nl.NetlinkSocket) {
+++     for {
+++             msgs, err := nlSock.Receive()
+++             if err != nil {
+++                     logrus.Errorf("Failed to receive from netlink: %v ", err)
+++                     continue
+++             }
+++
+++             for _, msg := range msgs {
+++                     if msg.Header.Type != syscall.RTM_GETNEIGH && msg.Header.Type != syscall.RTM_NEWNEIGH {
+++                             continue
+++                     }
+++
+++                     neigh, err := netlink.NeighDeserialize(msg.Data)
+++                     if err != nil {
+++                             logrus.Errorf("Failed to deserialize netlink ndmsg: %v", err)
+++                             continue
+++                     }
+++
+++                     if neigh.IP.To16() != nil {
+++                             continue
+++                     }
+++
+++                     if neigh.State&(netlink.NUD_STALE|netlink.NUD_INCOMPLETE) == 0 {
+++                             continue
+++                     }
+++
+++                     mac, vtep, err := n.driver.resolvePeer(n.id, neigh.IP)
+++                     if err != nil {
+++                             logrus.Errorf("could not resolve peer %q: %v", neigh.IP, err)
+++                             continue
+++                     }
+++
+++                     if err := n.driver.peerAdd(n.id, types.UUID("dummy"), neigh.IP, mac, vtep, true); err != nil {
+++                             logrus.Errorf("could not add neighbor entry for missed peer: %v", err)
+++                     }
+++             }
+++     }
+++}
+++
+++func (d *driver) addNetwork(n *network) {
+++     d.Lock()
+++     d.networks[n.id] = n
+++     d.Unlock()
+++}
+++
+++func (d *driver) deleteNetwork(nid types.UUID) {
+++     d.Lock()
+++     delete(d.networks, nid)
+++     d.Unlock()
+++}
+++
+++func (d *driver) network(nid types.UUID) *network {
+++     d.Lock()
+++     defer d.Unlock()
+++
+++     return d.networks[nid]
+++}
+++
+++func (n *network) sandbox() sandbox.Sandbox {
+++     n.Lock()
+++     defer n.Unlock()
+++
+++     return n.sbox
+++}
+++
+++func (n *network) setSandbox(sbox sandbox.Sandbox) {
+++     n.Lock()
+++     n.sbox = sbox
+++     n.Unlock()
+++}
+++
+++func (n *network) vxlanID() uint32 {
+++     n.Lock()
+++     defer n.Unlock()
+++
+++     return n.vni
+++}
+++
+++func (n *network) setVxlanID(vni uint32) {
+++     n.Lock()
+++     n.vni = vni
+++     n.Unlock()
+++}
+++
+++func (n *network) Key() []string {
+++     return []string{"overlay", "network", string(n.id)}
+++}
+++
+++func (n *network) KeyPrefix() []string {
+++     return []string{"overlay", "network"}
+++}
+++
+++func (n *network) Value() []byte {
+++     b, err := json.Marshal(n.vxlanID())
+++     if err != nil {
+++             return []byte{}
+++     }
+++
+++     return b
+++}
+++
+++func (n *network) Index() uint64 {
+++     return n.dbIndex
+++}
+++
+++func (n *network) SetIndex(index uint64) {
+++     n.dbIndex = index
+++     n.dbExists = true
+++}
+++
+++func (n *network) Exists() bool {
+++     return n.dbExists
+++}
+++
+++func (n *network) SetValue(value []byte) error {
+++     var vni uint32
+++     err := json.Unmarshal(value, &vni)
+++     if err == nil {
+++             n.setVxlanID(vni)
+++     }
+++     return err
+++}
+++
+++func (n *network) writeToStore() error {
+++     return n.driver.store.PutObjectAtomic(n)
+++}
+++
+++func (n *network) releaseVxlanID() error {
+++     if n.driver.store == nil {
+++             return fmt.Errorf("no datastore configured. cannot release vxlan id")
+++     }
+++
+++     if n.vxlanID() == 0 {
+++             return nil
+++     }
+++
+++     if err := n.driver.store.DeleteObjectAtomic(n); err != nil {
+++             if err == datastore.ErrKeyModified || err == datastore.ErrKeyNotFound {
+++                     // In both the above cases we can safely assume that the key has been removed by some other
+++                     // instance and so simply get out of here
+++                     return nil
+++             }
+++
+++             return fmt.Errorf("failed to delete network to vxlan id map: %v", err)
+++     }
+++
+++     n.driver.vxlanIdm.Release(n.vxlanID())
+++     n.setVxlanID(0)
+++     return nil
+++}
+++
+++func (n *network) obtainVxlanID() error {
+++     if n.driver.store == nil {
+++             return fmt.Errorf("no datastore configured. cannot obtain vxlan id")
+++     }
+++
+++     for {
+++             var vxlanID uint32
+++             if err := n.driver.store.GetObject(datastore.Key(n.Key()...), n); err != nil {
+++                     if err == datastore.ErrKeyNotFound {
+++                             vxlanID, err = n.driver.vxlanIdm.GetID()
+++                             if err != nil {
+++                                     return fmt.Errorf("failed to allocate vxlan id: %v", err)
+++                             }
+++
+++                             n.setVxlanID(vxlanID)
+++                             if err := n.writeToStore(); err != nil {
+++                                     n.driver.vxlanIdm.Release(n.vxlanID())
+++                                     n.setVxlanID(0)
+++                                     if err == datastore.ErrKeyModified {
+++                                             continue
+++                                     }
+++                                     return fmt.Errorf("failed to update data store with vxlan id: %v", err)
+++                             }
+++                             return nil
+++                     }
+++                     return fmt.Errorf("failed to obtain vxlan id from data store: %v", err)
+++             }
+++             return nil
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..5f467d19fa4809671d9c1b274796087adf38e67a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,249 @@@@
+++package overlay
+++
+++import (
+++     "fmt"
+++     "net"
+++     "strings"
+++     "time"
+++
+++     "github.com/Sirupsen/logrus"
+++     "github.com/docker/libnetwork/types"
+++     "github.com/hashicorp/serf/serf"
+++)
+++
+++type ovNotify struct {
+++     action string
+++     eid    types.UUID
+++     nid    types.UUID
+++}
+++
+++type logWriter struct{}
+++
+++func (l *logWriter) Write(p []byte) (int, error) {
+++     str := string(p)
+++
+++     switch {
+++     case strings.Contains(str, "[WARN]"):
+++             logrus.Warn(str)
+++     case strings.Contains(str, "[DEBUG]"):
+++             logrus.Debug(str)
+++     case strings.Contains(str, "[INFO]"):
+++             logrus.Info(str)
+++     case strings.Contains(str, "[ERR]"):
+++             logrus.Error(str)
+++     }
+++
+++     return len(p), nil
+++}
+++
+++func getBindAddr(ifaceName string) (string, error) {
+++     iface, err := net.InterfaceByName(ifaceName)
+++     if err != nil {
+++             return "", fmt.Errorf("failed to find interface %s: %v", ifaceName, err)
+++     }
+++
+++     addrs, err := iface.Addrs()
+++     if err != nil {
+++             return "", fmt.Errorf("failed to get interface addresses: %v", err)
+++     }
+++
+++     for _, a := range addrs {
+++             addr, ok := a.(*net.IPNet)
+++             if !ok {
+++                     continue
+++             }
+++             addrIP := addr.IP
+++
+++             if addrIP.IsLinkLocalUnicast() {
+++                     continue
+++             }
+++
+++             return addrIP.String(), nil
+++     }
+++
+++     return "", fmt.Errorf("failed to get bind address")
+++}
+++
+++func (d *driver) serfInit() error {
+++     var err error
+++
+++     config := serf.DefaultConfig()
+++     config.Init()
+++     if d.ifaceName != "" {
+++             bindAddr, err := getBindAddr(d.ifaceName)
+++             if err != nil {
+++                     return fmt.Errorf("getBindAddr error: %v", err)
+++             }
+++             config.MemberlistConfig.BindAddr = bindAddr
+++     }
+++
+++     d.eventCh = make(chan serf.Event, 4)
+++     config.EventCh = d.eventCh
+++     config.UserCoalescePeriod = 1 * time.Second
+++     config.UserQuiescentPeriod = 50 * time.Millisecond
+++
+++     config.LogOutput = logrus.StandardLogger().Out
+++
+++     s, err := serf.Create(config)
+++     if err != nil {
+++             return fmt.Errorf("failed to create cluster node: %v", err)
+++     }
+++     defer func() {
+++             if err != nil {
+++                     s.Shutdown()
+++             }
+++     }()
+++
+++     if d.neighIP != "" {
+++             if _, err = s.Join([]string{d.neighIP}, false); err != nil {
+++                     return fmt.Errorf("Failed to join the cluster at neigh IP %s: %v",
+++                             d.neighIP, err)
+++             }
+++     }
+++
+++     d.serfInstance = s
+++
+++     d.notifyCh = make(chan ovNotify)
+++     d.exitCh = make(chan chan struct{})
+++
+++     go d.startSerfLoop(d.eventCh, d.notifyCh, d.exitCh)
+++     return nil
+++}
+++
+++func (d *driver) notifyEvent(event ovNotify) {
+++     n := d.network(event.nid)
+++     ep := n.endpoint(event.eid)
+++
+++     ePayload := fmt.Sprintf("%s %s %s", event.action, ep.addr.IP.String(), ep.mac.String())
+++     eName := fmt.Sprintf("jl %s %s %s", d.serfInstance.LocalMember().Addr.String(),
+++             event.nid, event.eid)
+++
+++     if err := d.serfInstance.UserEvent(eName, []byte(ePayload), true); err != nil {
+++             fmt.Printf("Sending user event failed: %v\n", err)
+++     }
+++}
+++
+++func (d *driver) processEvent(u serf.UserEvent) {
+++     fmt.Printf("Received user event name:%s, payload:%s\n", u.Name,
+++             string(u.Payload))
+++
+++     var dummy, action, vtepStr, nid, eid, ipStr, macStr string
+++     if _, err := fmt.Sscan(u.Name, &dummy, &vtepStr, &nid, &eid); err != nil {
+++             fmt.Printf("Failed to scan name string: %v\n", err)
+++     }
+++
+++     if _, err := fmt.Sscan(string(u.Payload), &action,
+++             &ipStr, &macStr); err != nil {
+++             fmt.Printf("Failed to scan value string: %v\n", err)
+++     }
+++
+++     fmt.Printf("Parsed data = %s/%s/%s/%s/%s\n", nid, eid, vtepStr, ipStr, macStr)
+++
+++     mac, err := net.ParseMAC(macStr)
+++     if err != nil {
+++             fmt.Printf("Failed to parse mac: %v\n", err)
+++     }
+++
+++     if d.serfInstance.LocalMember().Addr.String() == vtepStr {
+++             return
+++     }
+++
+++     switch action {
+++     case "join":
+++             if err := d.peerAdd(types.UUID(nid), types.UUID(eid), net.ParseIP(ipStr), mac,
+++                     net.ParseIP(vtepStr), true); err != nil {
+++                     fmt.Printf("Peer add failed in the driver: %v\n", err)
+++             }
+++     case "leave":
+++             if err := d.peerDelete(types.UUID(nid), types.UUID(eid), net.ParseIP(ipStr), mac,
+++                     net.ParseIP(vtepStr), true); err != nil {
+++                     fmt.Printf("Peer delete failed in the driver: %v\n", err)
+++             }
+++     }
+++}
+++
+++func (d *driver) processQuery(q *serf.Query) {
+++     fmt.Printf("Received query name:%s, payload:%s\n", q.Name,
+++             string(q.Payload))
+++
+++     var nid, ipStr string
+++     if _, err := fmt.Sscan(string(q.Payload), &nid, &ipStr); err != nil {
+++             fmt.Printf("Failed to scan query payload string: %v\n", err)
+++     }
+++
+++     peerMac, vtep, err := d.peerDbSearch(types.UUID(nid), net.ParseIP(ipStr))
+++     if err != nil {
+++             return
+++     }
+++
+++     q.Respond([]byte(fmt.Sprintf("%s %s", peerMac.String(), vtep.String())))
+++}
+++
+++func (d *driver) resolvePeer(nid types.UUID, peerIP net.IP) (net.HardwareAddr, net.IP, error) {
+++     qPayload := fmt.Sprintf("%s %s", string(nid), peerIP.String())
+++     resp, err := d.serfInstance.Query("peerlookup", []byte(qPayload), nil)
+++     if err != nil {
+++             return nil, nil, fmt.Errorf("resolving peer by querying the cluster failed: %v", err)
+++     }
+++
+++     respCh := resp.ResponseCh()
+++     select {
+++     case r := <-respCh:
+++             var macStr, vtepStr string
+++             if _, err := fmt.Sscan(string(r.Payload), &macStr, &vtepStr); err != nil {
+++                     return nil, nil, fmt.Errorf("bad response %q for the resolve query: %v", string(r.Payload), err)
+++             }
+++
+++             mac, err := net.ParseMAC(macStr)
+++             if err != nil {
+++                     return nil, nil, fmt.Errorf("failed to parse mac: %v", err)
+++             }
+++
+++             return mac, net.ParseIP(vtepStr), nil
+++
+++     case <-time.After(time.Second):
+++             return nil, nil, fmt.Errorf("timed out resolving peer by querying the cluster")
+++     }
+++}
+++
+++func (d *driver) startSerfLoop(eventCh chan serf.Event, notifyCh chan ovNotify,
+++     exitCh chan chan struct{}) {
+++
+++     for {
+++             select {
+++             case notify, ok := <-notifyCh:
+++                     if !ok {
+++                             break
+++                     }
+++
+++                     d.notifyEvent(notify)
+++             case ch, ok := <-exitCh:
+++                     if !ok {
+++                             break
+++                     }
+++
+++                     if err := d.serfInstance.Leave(); err != nil {
+++                             fmt.Printf("failed leaving the cluster: %v\n", err)
+++                     }
+++
+++                     d.serfInstance.Shutdown()
+++                     close(ch)
+++                     return
+++             case e, ok := <-eventCh:
+++                     if !ok {
+++                             break
+++                     }
+++
+++                     if e.EventType() == serf.EventQuery {
+++                             d.processQuery(e.(*serf.Query))
+++                             break
+++                     }
+++
+++                     u, ok := e.(serf.UserEvent)
+++                     if !ok {
+++                             break
+++                     }
+++                     d.processEvent(u)
+++             }
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..bddf8cc31249801a9da7cd2b08cfca0b0726d899
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,88 @@@@
+++package overlay
+++
+++import (
+++     "fmt"
+++
+++     "github.com/docker/libnetwork/netutils"
+++     "github.com/docker/libnetwork/sandbox"
+++     "github.com/docker/libnetwork/types"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++func validateID(nid, eid types.UUID) error {
+++     if nid == "" {
+++             return fmt.Errorf("invalid network id")
+++     }
+++
+++     if eid == "" {
+++             return fmt.Errorf("invalid endpoint id")
+++     }
+++
+++     return nil
+++}
+++
+++func createVethPair() (string, string, error) {
+++     defer sandbox.InitOSContext()()
+++
+++     // Generate a name for what will be the host side pipe interface
+++     name1, err := netutils.GenerateIfaceName(vethPrefix, vethLen)
+++     if err != nil {
+++             return "", "", fmt.Errorf("error generating veth name1: %v", err)
+++     }
+++
+++     // Generate a name for what will be the sandbox side pipe interface
+++     name2, err := netutils.GenerateIfaceName(vethPrefix, vethLen)
+++     if err != nil {
+++             return "", "", fmt.Errorf("error generating veth name2: %v", err)
+++     }
+++
+++     // Generate and add the interface pipe host <-> sandbox
+++     veth := &netlink.Veth{
+++             LinkAttrs: netlink.LinkAttrs{Name: name1, TxQLen: 0},
+++             PeerName:  name2}
+++     if err := netlink.LinkAdd(veth); err != nil {
+++             return "", "", fmt.Errorf("error creating veth pair: %v", err)
+++     }
+++
+++     return name1, name2, nil
+++}
+++
+++func createVxlan(vni uint32) (string, error) {
+++     defer sandbox.InitOSContext()()
+++
+++     name, err := netutils.GenerateIfaceName("vxlan", 7)
+++     if err != nil {
+++             return "", fmt.Errorf("error generating vxlan name: %v", err)
+++     }
+++
+++     vxlan := &netlink.Vxlan{
+++             LinkAttrs: netlink.LinkAttrs{Name: name},
+++             VxlanId:   int(vni),
+++             Learning:  true,
+++             Port:      vxlanPort,
+++             Proxy:     true,
+++             L3miss:    true,
+++             L2miss:    true,
+++     }
+++
+++     if err := netlink.LinkAdd(vxlan); err != nil {
+++             return "", fmt.Errorf("error creating vxlan interface: %v", err)
+++     }
+++
+++     return name, nil
+++}
+++
+++func deleteVxlan(name string) error {
+++     defer sandbox.InitOSContext()()
+++
+++     link, err := netlink.LinkByName(name)
+++     if err != nil {
+++             return fmt.Errorf("failed to find vxlan interface with name %s: %v", name, err)
+++     }
+++
+++     if err := netlink.LinkDel(link); err != nil {
+++             return fmt.Errorf("error deleting vxlan interface: %v", err)
+++     }
+++
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..c4fcaa379713ec6e6257633eeda122156a0c6a34
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,158 @@@@
+++package overlay
+++
+++import (
+++     "encoding/binary"
+++     "fmt"
+++     "net"
+++     "sync"
+++
+++     "github.com/docker/libnetwork/config"
+++     "github.com/docker/libnetwork/datastore"
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/idm"
+++     "github.com/docker/libnetwork/netlabel"
+++     "github.com/docker/libnetwork/types"
+++     "github.com/hashicorp/serf/serf"
+++)
+++
+++const (
+++     networkType  = "overlay"
+++     vethPrefix   = "veth"
+++     vethLen      = 7
+++     vxlanIDStart = 256
+++     vxlanIDEnd   = 1000
+++     vxlanPort    = 4789
+++)
+++
+++type driver struct {
+++     eventCh      chan serf.Event
+++     notifyCh     chan ovNotify
+++     exitCh       chan chan struct{}
+++     ifaceName    string
+++     neighIP      string
+++     peerDb       peerNetworkMap
+++     serfInstance *serf.Serf
+++     networks     networkTable
+++     store        datastore.DataStore
+++     ipAllocator  *idm.Idm
+++     vxlanIdm     *idm.Idm
+++     sync.Once
+++     sync.Mutex
+++}
+++
+++var (
+++     bridgeSubnet, bridgeIP *net.IPNet
+++     once                   sync.Once
+++     bridgeSubnetInt        uint32
+++)
+++
+++func onceInit() {
+++     var err error
+++     _, bridgeSubnet, err = net.ParseCIDR("172.21.0.0/16")
+++     if err != nil {
+++             panic("could not parse cid 172.21.0.0/16")
+++     }
+++
+++     bridgeSubnetInt = binary.BigEndian.Uint32(bridgeSubnet.IP.To4())
+++
+++     ip, subnet, err := net.ParseCIDR("172.21.255.254/16")
+++     if err != nil {
+++             panic("could not parse cid 172.21.255.254/16")
+++     }
+++
+++     bridgeIP = &net.IPNet{
+++             IP:   ip,
+++             Mask: subnet.Mask,
+++     }
+++}
+++
+++// Init registers a new instance of overlay driver
+++func Init(dc driverapi.DriverCallback) error {
+++     once.Do(onceInit)
+++
+++     c := driverapi.Capability{
+++             Scope: driverapi.GlobalScope,
+++     }
+++
+++     return dc.RegisterDriver(networkType, &driver{
+++             networks: networkTable{},
+++             peerDb: peerNetworkMap{
+++                     mp: map[types.UUID]peerMap{},
+++             },
+++     }, c)
+++}
+++
+++// Fini cleans up the driver resources
+++func Fini(drv driverapi.Driver) {
+++     d := drv.(*driver)
+++
+++     if d.exitCh != nil {
+++             waitCh := make(chan struct{})
+++
+++             d.exitCh <- waitCh
+++
+++             <-waitCh
+++     }
+++}
+++
+++func (d *driver) Config(option map[string]interface{}) error {
+++     var onceDone bool
+++     var err error
+++
+++     d.Do(func() {
+++             onceDone = true
+++
+++             if ifaceName, ok := option[netlabel.OverlayBindInterface]; ok {
+++                     d.ifaceName = ifaceName.(string)
+++             }
+++
+++             if neighIP, ok := option[netlabel.OverlayNeighborIP]; ok {
+++                     d.neighIP = neighIP.(string)
+++             }
+++
+++             provider, provOk := option[netlabel.KVProvider]
+++             provURL, urlOk := option[netlabel.KVProviderURL]
+++
+++             if provOk && urlOk {
+++                     cfg := &config.DatastoreCfg{
+++                             Client: config.DatastoreClientCfg{
+++                                     Provider: provider.(string),
+++                                     Address:  provURL.(string),
+++                             },
+++                     }
+++                     d.store, err = datastore.NewDataStore(cfg)
+++                     if err != nil {
+++                             err = fmt.Errorf("failed to initialize data store: %v", err)
+++                             return
+++                     }
+++             }
+++
+++             d.vxlanIdm, err = idm.New(d.store, "vxlan-id", vxlanIDStart, vxlanIDEnd)
+++             if err != nil {
+++                     err = fmt.Errorf("failed to initialize vxlan id manager: %v", err)
+++                     return
+++             }
+++
+++             d.ipAllocator, err = idm.New(d.store, "ipam-id", 1, 0xFFFF-2)
+++             if err != nil {
+++                     err = fmt.Errorf("failed to initalize ipam id manager: %v", err)
+++                     return
+++             }
+++
+++             err = d.serfInit()
+++             if err != nil {
+++                     err = fmt.Errorf("initializing serf instance failed: %v", err)
+++             }
+++
+++     })
+++
+++     if !onceDone {
+++             return fmt.Errorf("config already applied to driver")
+++     }
+++
+++     return err
+++}
+++
+++func (d *driver) Type() string {
+++     return networkType
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..cc22e02f917b67fa36906cec620af1c38df93208
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,130 @@@@
+++package overlay
+++
+++import (
+++     "testing"
+++     "time"
+++
+++     "github.com/docker/libnetwork/driverapi"
+++)
+++
+++type driverTester struct {
+++     t *testing.T
+++     d driverapi.Driver
+++}
+++
+++const testNetworkType = "overlay"
+++
+++func setupDriver(t *testing.T) *driverTester {
+++     dt := &driverTester{t: t}
+++     if err := Init(dt); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     opt := make(map[string]interface{})
+++     if err := dt.d.Config(opt); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     return dt
+++}
+++
+++func cleanupDriver(t *testing.T, dt *driverTester) {
+++     ch := make(chan struct{})
+++     go func() {
+++             Fini(dt.d)
+++             close(ch)
+++     }()
+++
+++     select {
+++     case <-ch:
+++     case <-time.After(10 * time.Second):
+++             t.Fatal("test timed out because Fini() did not return on time")
+++     }
+++}
+++
+++func (dt *driverTester) RegisterDriver(name string, drv driverapi.Driver,
+++     cap driverapi.Capability) error {
+++     if name != testNetworkType {
+++             dt.t.Fatalf("Expected driver register name to be %q. Instead got %q",
+++                     testNetworkType, name)
+++     }
+++
+++     if _, ok := drv.(*driver); !ok {
+++             dt.t.Fatalf("Expected driver type to be %T. Instead got %T",
+++                     &driver{}, drv)
+++     }
+++
+++     dt.d = drv.(*driver)
+++     return nil
+++}
+++
+++func TestOverlayInit(t *testing.T) {
+++     if err := Init(&driverTester{t: t}); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestOverlayFiniWithoutConfig(t *testing.T) {
+++     dt := &driverTester{t: t}
+++     if err := Init(dt); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     cleanupDriver(t, dt)
+++}
+++
+++func TestOverlayNilConfig(t *testing.T) {
+++     dt := &driverTester{t: t}
+++     if err := Init(dt); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := dt.d.Config(nil); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     cleanupDriver(t, dt)
+++}
+++
+++func TestOverlayConfig(t *testing.T) {
+++     dt := setupDriver(t)
+++
+++     time.Sleep(1 * time.Second)
+++
+++     d := dt.d.(*driver)
+++     if d.notifyCh == nil {
+++             t.Fatal("Driver notify channel wasn't initialzed after Config method")
+++     }
+++
+++     if d.exitCh == nil {
+++             t.Fatal("Driver serfloop exit channel wasn't initialzed after Config method")
+++     }
+++
+++     if d.serfInstance == nil {
+++             t.Fatal("Driver serfinstance  hasn't been initialized after Config method")
+++     }
+++
+++     cleanupDriver(t, dt)
+++}
+++
+++func TestOverlayMultipleConfig(t *testing.T) {
+++     dt := setupDriver(t)
+++
+++     if err := dt.d.Config(nil); err == nil {
+++             t.Fatal("Expected a failure, instead succeded")
+++     }
+++
+++     cleanupDriver(t, dt)
+++}
+++
+++func TestOverlayType(t *testing.T) {
+++     dt := &driverTester{t: t}
+++     if err := Init(dt); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if dt.d.Type() != testNetworkType {
+++             t.Fatalf("Expected Type() to return %q. Instead got %q", testNetworkType,
+++                     dt.d.Type())
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9e4f7f7e12a611517caddaab82cc0a172d28d095
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,284 @@@@
+++package overlay
+++
+++import (
+++     "fmt"
+++     "net"
+++     "sync"
+++     "syscall"
+++
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++type peerKey struct {
+++     peerIP  net.IP
+++     peerMac net.HardwareAddr
+++}
+++
+++type peerEntry struct {
+++     eid       types.UUID
+++     vtep      net.IP
+++     inSandbox bool
+++     isLocal   bool
+++}
+++
+++type peerMap struct {
+++     mp map[string]peerEntry
+++     sync.Mutex
+++}
+++
+++type peerNetworkMap struct {
+++     mp map[types.UUID]peerMap
+++     sync.Mutex
+++}
+++
+++func (pKey peerKey) String() string {
+++     return fmt.Sprintf("%s %s", pKey.peerIP, pKey.peerMac)
+++}
+++
+++func (pKey *peerKey) Scan(state fmt.ScanState, verb rune) error {
+++     ipB, err := state.Token(true, nil)
+++     if err != nil {
+++             return err
+++     }
+++
+++     pKey.peerIP = net.ParseIP(string(ipB))
+++
+++     macB, err := state.Token(true, nil)
+++     if err != nil {
+++             return err
+++     }
+++
+++     pKey.peerMac, err = net.ParseMAC(string(macB))
+++     if err != nil {
+++             return err
+++     }
+++
+++     return nil
+++}
+++
+++var peerDbWg sync.WaitGroup
+++
+++func (d *driver) peerDbWalk(nid types.UUID, f func(*peerKey, *peerEntry) bool) error {
+++     d.peerDb.Lock()
+++     pMap, ok := d.peerDb.mp[nid]
+++     if !ok {
+++             d.peerDb.Unlock()
+++             return nil
+++     }
+++     d.peerDb.Unlock()
+++
+++     pMap.Lock()
+++     for pKeyStr, pEntry := range pMap.mp {
+++             var pKey peerKey
+++             if _, err := fmt.Sscan(pKeyStr, &pKey); err != nil {
+++                     fmt.Printf("peer key scan failed: %v", err)
+++             }
+++
+++             if f(&pKey, &pEntry) {
+++                     pMap.Unlock()
+++                     return nil
+++             }
+++     }
+++     pMap.Unlock()
+++
+++     return nil
+++}
+++
+++func (d *driver) peerDbSearch(nid types.UUID, peerIP net.IP) (net.HardwareAddr, net.IP, error) {
+++     var (
+++             peerMac net.HardwareAddr
+++             vtep    net.IP
+++             found   bool
+++     )
+++
+++     err := d.peerDbWalk(nid, func(pKey *peerKey, pEntry *peerEntry) bool {
+++             if pKey.peerIP.Equal(peerIP) {
+++                     peerMac = pKey.peerMac
+++                     vtep = pEntry.vtep
+++                     found = true
+++                     return found
+++             }
+++
+++             return found
+++     })
+++
+++     if err != nil {
+++             return nil, nil, fmt.Errorf("peerdb search for peer ip %q failed: %v", peerIP, err)
+++     }
+++
+++     if !found {
+++             return nil, nil, fmt.Errorf("peer ip %q not found in peerdb", peerIP)
+++     }
+++
+++     return peerMac, vtep, nil
+++}
+++
+++func (d *driver) peerDbAdd(nid, eid types.UUID, peerIP net.IP,
+++     peerMac net.HardwareAddr, vtep net.IP, isLocal bool) {
+++
+++     peerDbWg.Wait()
+++
+++     d.peerDb.Lock()
+++     pMap, ok := d.peerDb.mp[nid]
+++     if !ok {
+++             d.peerDb.mp[nid] = peerMap{
+++                     mp: make(map[string]peerEntry),
+++             }
+++
+++             pMap = d.peerDb.mp[nid]
+++     }
+++     d.peerDb.Unlock()
+++
+++     pKey := peerKey{
+++             peerIP:  peerIP,
+++             peerMac: peerMac,
+++     }
+++
+++     pEntry := peerEntry{
+++             eid:     eid,
+++             vtep:    vtep,
+++             isLocal: isLocal,
+++     }
+++
+++     pMap.Lock()
+++     pMap.mp[pKey.String()] = pEntry
+++     pMap.Unlock()
+++}
+++
+++func (d *driver) peerDbDelete(nid, eid types.UUID, peerIP net.IP,
+++     peerMac net.HardwareAddr, vtep net.IP) {
+++     peerDbWg.Wait()
+++
+++     d.peerDb.Lock()
+++     pMap, ok := d.peerDb.mp[nid]
+++     if !ok {
+++             d.peerDb.Unlock()
+++             return
+++     }
+++     d.peerDb.Unlock()
+++
+++     pKey := peerKey{
+++             peerIP:  peerIP,
+++             peerMac: peerMac,
+++     }
+++
+++     pMap.Lock()
+++     delete(pMap.mp, pKey.String())
+++     pMap.Unlock()
+++}
+++
+++func (d *driver) peerDbUpdateSandbox(nid types.UUID) {
+++     d.peerDb.Lock()
+++     pMap, ok := d.peerDb.mp[nid]
+++     if !ok {
+++             d.peerDb.Unlock()
+++             return
+++     }
+++     d.peerDb.Unlock()
+++
+++     peerDbWg.Add(1)
+++
+++     var peerOps []func()
+++     pMap.Lock()
+++     for pKeyStr, pEntry := range pMap.mp {
+++             var pKey peerKey
+++             if _, err := fmt.Sscan(pKeyStr, &pKey); err != nil {
+++                     fmt.Printf("peer key scan failed: %v", err)
+++             }
+++
+++             if pEntry.isLocal {
+++                     continue
+++             }
+++
+++             // Go captures variables by reference. The pEntry could be
+++             // pointing to the same memory location for every iteration. Make
+++             // a copy of pEntry before capturing it in the following closure.
+++             entry := pEntry
+++             op := func() {
+++                     if err := d.peerAdd(nid, entry.eid, pKey.peerIP,
+++                             pKey.peerMac, entry.vtep,
+++                             false); err != nil {
+++                             fmt.Printf("peerdbupdate in sandbox failed for ip %s and mac %s: %v",
+++                                     pKey.peerIP, pKey.peerMac, err)
+++                     }
+++             }
+++
+++             peerOps = append(peerOps, op)
+++     }
+++     pMap.Unlock()
+++
+++     for _, op := range peerOps {
+++             op()
+++     }
+++
+++     peerDbWg.Done()
+++}
+++
+++func (d *driver) peerAdd(nid, eid types.UUID, peerIP net.IP,
+++     peerMac net.HardwareAddr, vtep net.IP, updateDb bool) error {
+++
+++     if err := validateID(nid, eid); err != nil {
+++             return err
+++     }
+++
+++     if updateDb {
+++             d.peerDbAdd(nid, eid, peerIP, peerMac, vtep, false)
+++     }
+++
+++     n := d.network(nid)
+++     if n == nil {
+++             return nil
+++     }
+++
+++     sbox := n.sandbox()
+++     if sbox == nil {
+++             return nil
+++     }
+++
+++     // Add neighbor entry for the peer IP
+++     if err := sbox.AddNeighbor(peerIP, peerMac, sbox.NeighborOptions().LinkName(n.vxlanName)); err != nil {
+++             return fmt.Errorf("could not add neigbor entry into the sandbox: %v", err)
+++     }
+++
+++     // Add fdb entry to the bridge for the peer mac
+++     if err := sbox.AddNeighbor(vtep, peerMac, sbox.NeighborOptions().LinkName(n.vxlanName),
+++             sbox.NeighborOptions().Family(syscall.AF_BRIDGE)); err != nil {
+++             return fmt.Errorf("could not add fdb entry into the sandbox: %v", err)
+++     }
+++
+++     return nil
+++}
+++
+++func (d *driver) peerDelete(nid, eid types.UUID, peerIP net.IP,
+++     peerMac net.HardwareAddr, vtep net.IP, updateDb bool) error {
+++
+++     if err := validateID(nid, eid); err != nil {
+++             return err
+++     }
+++
+++     if updateDb {
+++             d.peerDbDelete(nid, eid, peerIP, peerMac, vtep)
+++     }
+++
+++     n := d.network(nid)
+++     if n == nil {
+++             return nil
+++     }
+++
+++     sbox := n.sandbox()
+++     if sbox == nil {
+++             return nil
+++     }
+++
+++     // Delete fdb entry to the bridge for the peer mac
+++     if err := sbox.DeleteNeighbor(vtep, peerMac); err != nil {
+++             return fmt.Errorf("could not delete fdb entry into the sandbox: %v", err)
+++     }
+++
+++     // Delete neighbor entry for the peer IP
+++     if err := sbox.DeleteNeighbor(peerIP, peerMac); err != nil {
+++             return fmt.Errorf("could not delete neigbor entry into the sandbox: %v", err)
+++     }
+++
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1395933a172d7ad979019b77d88f01e66a23bc85
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,227 @@@@
+++package remote
+++
+++import (
+++     "fmt"
+++     "net"
+++
+++     log "github.com/Sirupsen/logrus"
+++     "github.com/docker/docker/pkg/plugins"
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++type driver struct {
+++     endpoint    *plugins.Client
+++     networkType string
+++}
+++
+++func newDriver(name string, client *plugins.Client) driverapi.Driver {
+++     return &driver{networkType: name, endpoint: client}
+++}
+++
+++// Init makes sure a remote driver is registered when a network driver
+++// plugin is activated.
+++func Init(dc driverapi.DriverCallback) error {
+++     plugins.Handle(driverapi.NetworkPluginEndpointType, func(name string, client *plugins.Client) {
+++             c := driverapi.Capability{
+++                     Scope: driverapi.GlobalScope,
+++             }
+++             if err := dc.RegisterDriver(name, newDriver(name, client), c); err != nil {
+++                     log.Errorf("error registering driver for %s due to %v", name, err)
+++             }
+++     })
+++     return nil
+++}
+++
+++// Config is not implemented for remote drivers, since it is assumed
+++// to be supplied to the remote process out-of-band (e.g., as command
+++// line arguments).
+++func (d *driver) Config(option map[string]interface{}) error {
+++     return &driverapi.ErrNotImplemented{}
+++}
+++
+++func (d *driver) call(methodName string, arg interface{}, retVal maybeError) error {
+++     method := driverapi.NetworkPluginEndpointType + "." + methodName
+++     err := d.endpoint.Call(method, arg, retVal)
+++     if err != nil {
+++             return err
+++     }
+++     if e := retVal.getError(); e != "" {
+++             return fmt.Errorf("remote: %s", e)
+++     }
+++     return nil
+++}
+++
+++func (d *driver) CreateNetwork(id types.UUID, options map[string]interface{}) error {
+++     create := &createNetworkRequest{
+++             NetworkID: string(id),
+++             Options:   options,
+++     }
+++     return d.call("CreateNetwork", create, &createNetworkResponse{})
+++}
+++
+++func (d *driver) DeleteNetwork(nid types.UUID) error {
+++     delete := &deleteNetworkRequest{NetworkID: string(nid)}
+++     return d.call("DeleteNetwork", delete, &deleteNetworkResponse{})
+++}
+++
+++func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error {
+++     if epInfo == nil {
+++             return fmt.Errorf("must not be called with nil EndpointInfo")
+++     }
+++
+++     reqIfaces := make([]*endpointInterface, len(epInfo.Interfaces()))
+++     for i, iface := range epInfo.Interfaces() {
+++             addr4 := iface.Address()
+++             addr6 := iface.AddressIPv6()
+++             reqIfaces[i] = &endpointInterface{
+++                     ID:          iface.ID(),
+++                     Address:     addr4.String(),
+++                     AddressIPv6: addr6.String(),
+++                     MacAddress:  iface.MacAddress().String(),
+++             }
+++     }
+++     create := &createEndpointRequest{
+++             NetworkID:  string(nid),
+++             EndpointID: string(eid),
+++             Interfaces: reqIfaces,
+++             Options:    epOptions,
+++     }
+++     var res createEndpointResponse
+++     if err := d.call("CreateEndpoint", create, &res); err != nil {
+++             return err
+++     }
+++
+++     ifaces, err := res.parseInterfaces()
+++     if err != nil {
+++             return err
+++     }
+++     if len(reqIfaces) > 0 && len(ifaces) > 0 {
+++             // We're not supposed to add interfaces if there already are
+++             // some. Attempt to roll back
+++             return errorWithRollback("driver attempted to add more interfaces", d.DeleteEndpoint(nid, eid))
+++     }
+++     for _, iface := range ifaces {
+++             var addr4, addr6 net.IPNet
+++             if iface.Address != nil {
+++                     addr4 = *(iface.Address)
+++             }
+++             if iface.AddressIPv6 != nil {
+++                     addr6 = *(iface.AddressIPv6)
+++             }
+++             if err := epInfo.AddInterface(iface.ID, iface.MacAddress, addr4, addr6); err != nil {
+++                     return errorWithRollback(fmt.Sprintf("failed to AddInterface %v: %s", iface, err), d.DeleteEndpoint(nid, eid))
+++             }
+++     }
+++     return nil
+++}
+++
+++func errorWithRollback(msg string, err error) error {
+++     rollback := "rolled back"
+++     if err != nil {
+++             rollback = "failed to roll back: " + err.Error()
+++     }
+++     return fmt.Errorf("%s; %s", msg, rollback)
+++}
+++
+++func (d *driver) DeleteEndpoint(nid, eid types.UUID) error {
+++     delete := &deleteEndpointRequest{
+++             NetworkID:  string(nid),
+++             EndpointID: string(eid),
+++     }
+++     return d.call("DeleteEndpoint", delete, &deleteEndpointResponse{})
+++}
+++
+++func (d *driver) EndpointOperInfo(nid, eid types.UUID) (map[string]interface{}, error) {
+++     info := &endpointInfoRequest{
+++             NetworkID:  string(nid),
+++             EndpointID: string(eid),
+++     }
+++     var res endpointInfoResponse
+++     if err := d.call("EndpointOperInfo", info, &res); err != nil {
+++             return nil, err
+++     }
+++     return res.Value, nil
+++}
+++
+++// Join method is invoked when a Sandbox is attached to an endpoint.
+++func (d *driver) Join(nid, eid types.UUID, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+++     join := &joinRequest{
+++             NetworkID:  string(nid),
+++             EndpointID: string(eid),
+++             SandboxKey: sboxKey,
+++             Options:    options,
+++     }
+++     var (
+++             res joinResponse
+++             err error
+++     )
+++     if err = d.call("Join", join, &res); err != nil {
+++             return err
+++     }
+++
+++     // Expect each interface ID given by CreateEndpoint to have an
+++     // entry at that index in the names supplied here. In other words,
+++     // if you supply 0..n interfaces with IDs 0..n above, you should
+++     // supply the names in the same order.
+++     ifaceNames := res.InterfaceNames
+++     for _, iface := range jinfo.InterfaceNames() {
+++             i := iface.ID()
+++             if i >= len(ifaceNames) || i < 0 {
+++                     return fmt.Errorf("no correlating interface %d in supplied interface names", i)
+++             }
+++             supplied := ifaceNames[i]
+++             if err := iface.SetNames(supplied.SrcName, supplied.DstPrefix); err != nil {
+++                     return errorWithRollback(fmt.Sprintf("failed to set interface name: %s", err), d.Leave(nid, eid))
+++             }
+++     }
+++
+++     var addr net.IP
+++     if res.Gateway != "" {
+++             if addr = net.ParseIP(res.Gateway); addr == nil {
+++                     return fmt.Errorf(`unable to parse Gateway "%s"`, res.Gateway)
+++             }
+++             if jinfo.SetGateway(addr) != nil {
+++                     return errorWithRollback(fmt.Sprintf("failed to set gateway: %v", addr), d.Leave(nid, eid))
+++             }
+++     }
+++     if res.GatewayIPv6 != "" {
+++             if addr = net.ParseIP(res.GatewayIPv6); addr == nil {
+++                     return fmt.Errorf(`unable to parse GatewayIPv6 "%s"`, res.GatewayIPv6)
+++             }
+++             if jinfo.SetGatewayIPv6(addr) != nil {
+++                     return errorWithRollback(fmt.Sprintf("failed to set gateway IPv6: %v", addr), d.Leave(nid, eid))
+++             }
+++     }
+++     if len(res.StaticRoutes) > 0 {
+++             routes, err := res.parseStaticRoutes()
+++             if err != nil {
+++                     return err
+++             }
+++             for _, route := range routes {
+++                     if jinfo.AddStaticRoute(route.Destination, route.RouteType, route.NextHop, route.InterfaceID) != nil {
+++                             return errorWithRollback(fmt.Sprintf("failed to set static route: %v", route), d.Leave(nid, eid))
+++                     }
+++             }
+++     }
+++     if jinfo.SetHostsPath(res.HostsPath) != nil {
+++             return errorWithRollback(fmt.Sprintf("failed to set hosts path: %s", res.HostsPath), d.Leave(nid, eid))
+++     }
+++     if jinfo.SetResolvConfPath(res.ResolvConfPath) != nil {
+++             return errorWithRollback(fmt.Sprintf("failed to set resolv.conf path: %s", res.ResolvConfPath), d.Leave(nid, eid))
+++     }
+++     return nil
+++}
+++
+++// Leave method is invoked when a Sandbox detaches from an endpoint.
+++func (d *driver) Leave(nid, eid types.UUID) error {
+++     leave := &leaveRequest{
+++             NetworkID:  string(nid),
+++             EndpointID: string(eid),
+++     }
+++     return d.call("Leave", leave, &leaveResponse{})
+++}
+++
+++func (d *driver) Type() string {
+++     return d.networkType
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e562e1a035a7b45657c3e6930c6a24f7d3264131
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,436 @@@@
+++package remote
+++
+++import (
+++     "encoding/json"
+++     "fmt"
+++     "net"
+++     "net/http"
+++     "os"
+++     "testing"
+++
+++     "github.com/docker/docker/pkg/plugins"
+++     "github.com/docker/libnetwork/driverapi"
+++     _ "github.com/docker/libnetwork/netutils"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++func decodeToMap(r *http.Request) (res map[string]interface{}, err error) {
+++     err = json.NewDecoder(r.Body).Decode(&res)
+++     return
+++}
+++
+++func handle(t *testing.T, mux *http.ServeMux, method string, h func(map[string]interface{}) interface{}) {
+++     mux.HandleFunc(fmt.Sprintf("/%s.%s", driverapi.NetworkPluginEndpointType, method), func(w http.ResponseWriter, r *http.Request) {
+++             ask, err := decodeToMap(r)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++             answer := h(ask)
+++             err = json.NewEncoder(w).Encode(&answer)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++     })
+++}
+++
+++func setupPlugin(t *testing.T, name string, mux *http.ServeMux) func() {
+++     if err := os.MkdirAll("/usr/share/docker/plugins", 0755); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     listener, err := net.Listen("unix", fmt.Sprintf("/usr/share/docker/plugins/%s.sock", name))
+++     if err != nil {
+++             t.Fatal("Could not listen to the plugin socket")
+++     }
+++
+++     mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
+++             fmt.Fprintf(w, `{"Implements": ["%s"]}`, driverapi.NetworkPluginEndpointType)
+++     })
+++
+++     go http.Serve(listener, mux)
+++
+++     return func() {
+++             listener.Close()
+++             if err := os.RemoveAll("/usr/share/docker/plugins"); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }
+++}
+++
+++type testEndpoint struct {
+++     t              *testing.T
+++     id             int
+++     src            string
+++     dst            string
+++     address        string
+++     addressIPv6    string
+++     macAddress     string
+++     gateway        string
+++     gatewayIPv6    string
+++     resolvConfPath string
+++     hostsPath      string
+++     nextHop        string
+++     destination    string
+++     routeType      int
+++}
+++
+++func (test *testEndpoint) Interfaces() []driverapi.InterfaceInfo {
+++     // return an empty one so we don't trip the check for existing
+++     // interfaces; we don't care about this after that
+++     return []driverapi.InterfaceInfo{}
+++}
+++
+++func (test *testEndpoint) AddInterface(ID int, mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error {
+++     if ID != test.id {
+++             test.t.Fatalf("Wrong ID passed to AddInterface: %d", ID)
+++     }
+++     ip4, net4, _ := net.ParseCIDR(test.address)
+++     ip6, net6, _ := net.ParseCIDR(test.addressIPv6)
+++     if ip4 != nil {
+++             net4.IP = ip4
+++             if !types.CompareIPNet(net4, &ipv4) {
+++                     test.t.Fatalf("Wrong address given %+v", ipv4)
+++             }
+++     }
+++     if ip6 != nil {
+++             net6.IP = ip6
+++             if !types.CompareIPNet(net6, &ipv6) {
+++                     test.t.Fatalf("Wrong address (IPv6) given %+v", ipv6)
+++             }
+++     }
+++     if test.macAddress != "" && mac.String() != test.macAddress {
+++             test.t.Fatalf("Wrong MAC address given %v", mac)
+++     }
+++     return nil
+++}
+++
+++func (test *testEndpoint) InterfaceNames() []driverapi.InterfaceNameInfo {
+++     return []driverapi.InterfaceNameInfo{test}
+++}
+++
+++func compareIPs(t *testing.T, kind string, shouldBe string, supplied net.IP) {
+++     ip := net.ParseIP(shouldBe)
+++     if ip == nil {
+++             t.Fatalf(`Invalid IP to test against: "%s"`, shouldBe)
+++     }
+++     if !ip.Equal(supplied) {
+++             t.Fatalf(`%s IPs are not equal: expected "%s", got %v`, kind, shouldBe, supplied)
+++     }
+++}
+++
+++func compareIPNets(t *testing.T, kind string, shouldBe string, supplied net.IPNet) {
+++     _, net, _ := net.ParseCIDR(shouldBe)
+++     if net == nil {
+++             t.Fatalf(`Invalid IP network to test against: "%s"`, shouldBe)
+++     }
+++     if !types.CompareIPNet(net, &supplied) {
+++             t.Fatalf(`%s IP networks are not equal: expected "%s", got %v`, kind, shouldBe, supplied)
+++     }
+++}
+++
+++func (test *testEndpoint) SetGateway(ipv4 net.IP) error {
+++     compareIPs(test.t, "Gateway", test.gateway, ipv4)
+++     return nil
+++}
+++
+++func (test *testEndpoint) SetGatewayIPv6(ipv6 net.IP) error {
+++     compareIPs(test.t, "GatewayIPv6", test.gatewayIPv6, ipv6)
+++     return nil
+++}
+++
+++func (test *testEndpoint) SetHostsPath(p string) error {
+++     if p != test.hostsPath {
+++             test.t.Fatalf(`Wrong HostsPath; expected "%s", got "%s"`, test.hostsPath, p)
+++     }
+++     return nil
+++}
+++
+++func (test *testEndpoint) SetResolvConfPath(p string) error {
+++     if p != test.resolvConfPath {
+++             test.t.Fatalf(`Wrong ResolvConfPath; expected "%s", got "%s"`, test.resolvConfPath, p)
+++     }
+++     return nil
+++}
+++
+++func (test *testEndpoint) SetNames(src string, dst string) error {
+++     if test.src != src {
+++             test.t.Fatalf(`Wrong SrcName; expected "%s", got "%s"`, test.src, src)
+++     }
+++     if test.dst != dst {
+++             test.t.Fatalf(`Wrong DstPrefix; expected "%s", got "%s"`, test.dst, dst)
+++     }
+++     return nil
+++}
+++
+++func (test *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP, interfaceID int) error {
+++     compareIPNets(test.t, "Destination", test.destination, *destination)
+++     compareIPs(test.t, "NextHop", test.nextHop, nextHop)
+++
+++     if test.routeType != routeType {
+++             test.t.Fatalf(`Wrong RouteType; expected "%d", got "%d"`, test.routeType, routeType)
+++     }
+++
+++     if test.id != interfaceID {
+++             test.t.Fatalf(`Wrong InterfaceID; expected "%d", got "%d"`, test.id, interfaceID)
+++     }
+++
+++     return nil
+++}
+++
+++func (test *testEndpoint) ID() int {
+++     return test.id
+++}
+++
+++func TestRemoteDriver(t *testing.T) {
+++     var plugin = "test-net-driver"
+++
+++     ep := &testEndpoint{
+++             t:              t,
+++             src:            "vethsrc",
+++             dst:            "vethdst",
+++             address:        "192.168.5.7/16",
+++             addressIPv6:    "2001:DB8::5:7/48",
+++             macAddress:     "7a:56:78:34:12:da",
+++             gateway:        "192.168.0.1",
+++             gatewayIPv6:    "2001:DB8::1",
+++             hostsPath:      "/here/comes/the/host/path",
+++             resolvConfPath: "/there/goes/the/resolv/conf",
+++             destination:    "10.0.0.0/8",
+++             nextHop:        "10.0.0.1",
+++             routeType:      1,
+++     }
+++
+++     mux := http.NewServeMux()
+++     defer setupPlugin(t, plugin, mux)()
+++
+++     var networkID string
+++
+++     handle(t, mux, "CreateNetwork", func(msg map[string]interface{}) interface{} {
+++             nid := msg["NetworkID"]
+++             var ok bool
+++             if networkID, ok = nid.(string); !ok {
+++                     t.Fatal("RPC did not include network ID string")
+++             }
+++             return map[string]interface{}{}
+++     })
+++     handle(t, mux, "DeleteNetwork", func(msg map[string]interface{}) interface{} {
+++             if nid, ok := msg["NetworkID"]; !ok || nid != networkID {
+++                     t.Fatal("Network ID missing or does not match that created")
+++             }
+++             return map[string]interface{}{}
+++     })
+++     handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} {
+++             iface := map[string]interface{}{
+++                     "ID":          ep.id,
+++                     "Address":     ep.address,
+++                     "AddressIPv6": ep.addressIPv6,
+++                     "MacAddress":  ep.macAddress,
+++             }
+++             return map[string]interface{}{
+++                     "Interfaces": []interface{}{iface},
+++             }
+++     })
+++     handle(t, mux, "Join", func(msg map[string]interface{}) interface{} {
+++             options := msg["Options"].(map[string]interface{})
+++             foo, ok := options["foo"].(string)
+++             if !ok || foo != "fooValue" {
+++                     t.Fatalf("Did not receive expected foo string in request options: %+v", msg)
+++             }
+++             return map[string]interface{}{
+++                     "Gateway":        ep.gateway,
+++                     "GatewayIPv6":    ep.gatewayIPv6,
+++                     "HostsPath":      ep.hostsPath,
+++                     "ResolvConfPath": ep.resolvConfPath,
+++                     "InterfaceNames": []map[string]interface{}{
+++                             map[string]interface{}{
+++                                     "SrcName":   ep.src,
+++                                     "DstPrefix": ep.dst,
+++                             },
+++                     },
+++                     "StaticRoutes": []map[string]interface{}{
+++                             map[string]interface{}{
+++                                     "Destination": ep.destination,
+++                                     "RouteType":   ep.routeType,
+++                                     "InterfaceID": ep.id,
+++                                     "NextHop":     ep.nextHop,
+++                             },
+++                     },
+++             }
+++     })
+++     handle(t, mux, "Leave", func(msg map[string]interface{}) interface{} {
+++             return map[string]string{}
+++     })
+++     handle(t, mux, "DeleteEndpoint", func(msg map[string]interface{}) interface{} {
+++             return map[string]interface{}{}
+++     })
+++     handle(t, mux, "EndpointOperInfo", func(msg map[string]interface{}) interface{} {
+++             return map[string]interface{}{
+++                     "Value": map[string]string{
+++                             "Arbitrary": "key",
+++                             "Value":     "pairs?",
+++                     },
+++             }
+++     })
+++
+++     p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     driver := newDriver(plugin, p.Client)
+++     if driver.Type() != plugin {
+++             t.Fatal("Driver type does not match that given")
+++     }
+++
+++     netID := types.UUID("dummy-network")
+++     err = driver.CreateNetwork(netID, map[string]interface{}{})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     endID := types.UUID("dummy-endpoint")
+++     err = driver.CreateEndpoint(netID, endID, ep, map[string]interface{}{})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     joinOpts := map[string]interface{}{"foo": "fooValue"}
+++     err = driver.Join(netID, endID, "sandbox-key", ep, joinOpts)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if _, err = driver.EndpointOperInfo(netID, endID); err != nil {
+++             t.Fatal(err)
+++     }
+++     if err = driver.Leave(netID, endID); err != nil {
+++             t.Fatal(err)
+++     }
+++     if err = driver.DeleteEndpoint(netID, endID); err != nil {
+++             t.Fatal(err)
+++     }
+++     if err = driver.DeleteNetwork(netID); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++type failEndpoint struct {
+++     t *testing.T
+++}
+++
+++func (f *failEndpoint) Interfaces() []*driverapi.InterfaceInfo {
+++     f.t.Fatal("Unexpected call of Interfaces")
+++     return nil
+++}
+++func (f *failEndpoint) AddInterface(int, net.HardwareAddr, net.IPNet, net.IPNet) error {
+++     f.t.Fatal("Unexpected call of AddInterface")
+++     return nil
+++}
+++
+++func TestDriverError(t *testing.T) {
+++     var plugin = "test-net-driver-error"
+++
+++     mux := http.NewServeMux()
+++     defer setupPlugin(t, plugin, mux)()
+++
+++     handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} {
+++             return map[string]interface{}{
+++                     "Err": "this should get raised as an error",
+++             }
+++     })
+++
+++     p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     driver := newDriver(plugin, p.Client)
+++
+++     if err := driver.CreateEndpoint(types.UUID("dummy"), types.UUID("dummy"), &testEndpoint{t: t}, map[string]interface{}{}); err == nil {
+++             t.Fatalf("Expected error from driver")
+++     }
+++}
+++
+++func TestMissingValues(t *testing.T) {
+++     var plugin = "test-net-driver-missing"
+++
+++     mux := http.NewServeMux()
+++     defer setupPlugin(t, plugin, mux)()
+++
+++     ep := &testEndpoint{
+++             t:  t,
+++             id: 0,
+++     }
+++
+++     handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} {
+++             iface := map[string]interface{}{
+++                     "ID":          ep.id,
+++                     "Address":     ep.address,
+++                     "AddressIPv6": ep.addressIPv6,
+++                     "MacAddress":  ep.macAddress,
+++             }
+++             return map[string]interface{}{
+++                     "Interfaces": []interface{}{iface},
+++             }
+++     })
+++
+++     p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     driver := newDriver(plugin, p.Client)
+++
+++     if err := driver.CreateEndpoint(types.UUID("dummy"), types.UUID("dummy"), ep, map[string]interface{}{}); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++type rollbackEndpoint struct {
+++}
+++
+++func (r *rollbackEndpoint) Interfaces() []driverapi.InterfaceInfo {
+++     return []driverapi.InterfaceInfo{}
+++}
+++
+++func (r *rollbackEndpoint) AddInterface(_ int, _ net.HardwareAddr, _ net.IPNet, _ net.IPNet) error {
+++     return fmt.Errorf("fail this to trigger a rollback")
+++}
+++
+++func TestRollback(t *testing.T) {
+++     var plugin = "test-net-driver-rollback"
+++
+++     mux := http.NewServeMux()
+++     defer setupPlugin(t, plugin, mux)()
+++
+++     rolledback := false
+++
+++     handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} {
+++             iface := map[string]interface{}{
+++                     "ID":          0,
+++                     "Address":     "192.168.4.5/16",
+++                     "AddressIPv6": "",
+++                     "MacAddress":  "7a:12:34:56:78:90",
+++             }
+++             return map[string]interface{}{
+++                     "Interfaces": []interface{}{iface},
+++             }
+++     })
+++     handle(t, mux, "DeleteEndpoint", func(msg map[string]interface{}) interface{} {
+++             rolledback = true
+++             return map[string]interface{}{}
+++     })
+++
+++     p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     driver := newDriver(plugin, p.Client)
+++
+++     ep := &rollbackEndpoint{}
+++
+++     if err := driver.CreateEndpoint(types.UUID("dummy"), types.UUID("dummy"), ep, map[string]interface{}{}); err == nil {
+++             t.Fatalf("Expected error from driver")
+++     }
+++     if !rolledback {
+++             t.Fatalf("Expected to have had DeleteEndpoint called")
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d9c9e1534e5d58cdbc9c991177fb8f90296e7250
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,178 @@@@
+++package remote
+++
+++import (
+++     "fmt"
+++     "net"
+++
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++type response struct {
+++     Err string
+++}
+++
+++type maybeError interface {
+++     getError() string
+++}
+++
+++func (r *response) getError() string {
+++     return r.Err
+++}
+++
+++type createNetworkRequest struct {
+++     NetworkID string
+++     Options   map[string]interface{}
+++}
+++
+++type createNetworkResponse struct {
+++     response
+++}
+++
+++type deleteNetworkRequest struct {
+++     NetworkID string
+++}
+++
+++type deleteNetworkResponse struct {
+++     response
+++}
+++
+++type createEndpointRequest struct {
+++     NetworkID  string
+++     EndpointID string
+++     Interfaces []*endpointInterface
+++     Options    map[string]interface{}
+++}
+++
+++type endpointInterface struct {
+++     ID          int
+++     Address     string
+++     AddressIPv6 string
+++     MacAddress  string
+++}
+++
+++type staticRoute struct {
+++     Destination string
+++     RouteType   int
+++     NextHop     string
+++     InterfaceID int
+++}
+++
+++type createEndpointResponse struct {
+++     response
+++     Interfaces []*endpointInterface
+++}
+++
+++func toAddr(ipAddr string) (*net.IPNet, error) {
+++     ip, ipnet, err := net.ParseCIDR(ipAddr)
+++     if err != nil {
+++             return nil, err
+++     }
+++     ipnet.IP = ip
+++     return ipnet, nil
+++}
+++
+++type iface struct {
+++     ID          int
+++     Address     *net.IPNet
+++     AddressIPv6 *net.IPNet
+++     MacAddress  net.HardwareAddr
+++}
+++
+++func (r *createEndpointResponse) parseInterfaces() ([]*iface, error) {
+++     var ifaces = make([]*iface, len(r.Interfaces))
+++     for i, inIf := range r.Interfaces {
+++             var err error
+++             outIf := &iface{ID: inIf.ID}
+++             if inIf.Address != "" {
+++                     if outIf.Address, err = toAddr(inIf.Address); err != nil {
+++                             return nil, err
+++                     }
+++             }
+++             if inIf.AddressIPv6 != "" {
+++                     if outIf.AddressIPv6, err = toAddr(inIf.AddressIPv6); err != nil {
+++                             return nil, err
+++                     }
+++             }
+++             if inIf.MacAddress != "" {
+++                     if outIf.MacAddress, err = net.ParseMAC(inIf.MacAddress); err != nil {
+++                             return nil, err
+++                     }
+++             }
+++             ifaces[i] = outIf
+++     }
+++     return ifaces, nil
+++}
+++
+++func (r *joinResponse) parseStaticRoutes() ([]*types.StaticRoute, error) {
+++     var routes = make([]*types.StaticRoute, len(r.StaticRoutes))
+++     for i, inRoute := range r.StaticRoutes {
+++             var err error
+++             outRoute := &types.StaticRoute{InterfaceID: inRoute.InterfaceID, RouteType: inRoute.RouteType}
+++
+++             if inRoute.Destination != "" {
+++                     if outRoute.Destination, err = toAddr(inRoute.Destination); err != nil {
+++                             return nil, err
+++                     }
+++             }
+++
+++             if inRoute.NextHop != "" {
+++                     outRoute.NextHop = net.ParseIP(inRoute.NextHop)
+++                     if outRoute.NextHop == nil {
+++                             return nil, fmt.Errorf("failed to parse nexthop IP %s", inRoute.NextHop)
+++                     }
+++             }
+++
+++             routes[i] = outRoute
+++     }
+++     return routes, nil
+++}
+++
+++type deleteEndpointRequest struct {
+++     NetworkID  string
+++     EndpointID string
+++}
+++
+++type deleteEndpointResponse struct {
+++     response
+++}
+++
+++type endpointInfoRequest struct {
+++     NetworkID  string
+++     EndpointID string
+++}
+++
+++type endpointInfoResponse struct {
+++     response
+++     Value map[string]interface{}
+++}
+++
+++type joinRequest struct {
+++     NetworkID  string
+++     EndpointID string
+++     SandboxKey string
+++     Options    map[string]interface{}
+++}
+++
+++type ifaceName struct {
+++     SrcName   string
+++     DstPrefix string
+++}
+++
+++type joinResponse struct {
+++     response
+++     InterfaceNames []*ifaceName
+++     Gateway        string
+++     GatewayIPv6    string
+++     StaticRoutes   []*staticRoute
+++     HostsPath      string
+++     ResolvConfPath string
+++}
+++
+++type leaveRequest struct {
+++     NetworkID  string
+++     EndpointID string
+++}
+++
+++type leaveResponse struct {
+++     response
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..925e402bb07abb891688d86bcad5ba289f114390
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,58 @@@@
+++package windows
+++
+++import (
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++const networkType = "windows"
+++
+++// TODO Windows. This is a placeholder for now
+++
+++type driver struct{}
+++
+++// Init registers a new instance of null driver
+++func Init(dc driverapi.DriverCallback) error {
+++     c := driverapi.Capability{
+++             Scope: driverapi.LocalScope,
+++     }
+++     return dc.RegisterDriver(networkType, &driver{}, c)
+++}
+++
+++func (d *driver) Config(option map[string]interface{}) error {
+++     return nil
+++}
+++
+++func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) error {
+++     return nil
+++}
+++
+++func (d *driver) DeleteNetwork(nid types.UUID) error {
+++     return nil
+++}
+++
+++func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error {
+++     return nil
+++}
+++
+++func (d *driver) DeleteEndpoint(nid, eid types.UUID) error {
+++     return nil
+++}
+++
+++func (d *driver) EndpointOperInfo(nid, eid types.UUID) (map[string]interface{}, error) {
+++     return make(map[string]interface{}, 0), nil
+++}
+++
+++// Join method is invoked when a Sandbox is attached to an endpoint.
+++func (d *driver) Join(nid, eid types.UUID, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+++     return nil
+++}
+++
+++// Leave method is invoked when a Sandbox detaches from an endpoint.
+++func (d *driver) Leave(nid, eid types.UUID) error {
+++     return nil
+++}
+++
+++func (d *driver) Type() string {
+++     return networkType
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..683af06ce936334c46559d3b143de5e310b53c2d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,19 @@@@
+++package libnetwork
+++
+++import (
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/drivers/null"
+++     "github.com/docker/libnetwork/drivers/remote"
+++)
+++
+++func initDrivers(dc driverapi.DriverCallback) error {
+++     for _, fn := range [](func(driverapi.DriverCallback) error){
+++             null.Init,
+++             remote.Init,
+++     } {
+++             if err := fn(dc); err != nil {
+++                     return err
+++             }
+++     }
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b8294d452e37d2b5723f0b76df9555c3f6e14136
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,25 @@@@
+++package libnetwork
+++
+++import (
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/drivers/bridge"
+++     "github.com/docker/libnetwork/drivers/host"
+++     "github.com/docker/libnetwork/drivers/null"
+++     o "github.com/docker/libnetwork/drivers/overlay"
+++     "github.com/docker/libnetwork/drivers/remote"
+++)
+++
+++func initDrivers(dc driverapi.DriverCallback) error {
+++     for _, fn := range [](func(driverapi.DriverCallback) error){
+++             bridge.Init,
+++             host.Init,
+++             null.Init,
+++             remote.Init,
+++             o.Init,
+++     } {
+++             if err := fn(dc); err != nil {
+++                     return err
+++             }
+++     }
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..334cd7c9fee778e59bd0966411b1ca98a5a3e653
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,17 @@@@
+++package libnetwork
+++
+++import (
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/drivers/windows"
+++)
+++
+++func initDrivers(dc driverapi.DriverCallback) error {
+++     for _, fn := range [](func(driverapi.DriverCallback) error){
+++             windows.Init,
+++     } {
+++             if err := fn(dc); err != nil {
+++                     return err
+++             }
+++     }
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d38a4fd1211e96c47e63caa7756a2c800f124bf4
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,999 @@@@
+++package libnetwork
+++
+++import (
+++     "bytes"
+++     "encoding/json"
+++     "fmt"
+++     "io/ioutil"
+++     "os"
+++     "path"
+++     "path/filepath"
+++     "sync"
+++
+++     log "github.com/Sirupsen/logrus"
+++     "github.com/docker/docker/pkg/ioutils"
+++     "github.com/docker/libnetwork/datastore"
+++     "github.com/docker/libnetwork/etchosts"
+++     "github.com/docker/libnetwork/netlabel"
+++     "github.com/docker/libnetwork/resolvconf"
+++     "github.com/docker/libnetwork/sandbox"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++// Endpoint represents a logical connection between a network and a sandbox.
+++type Endpoint interface {
+++     // A system generated id for this endpoint.
+++     ID() string
+++
+++     // Name returns the name of this endpoint.
+++     Name() string
+++
+++     // Network returns the name of the network to which this endpoint is attached.
+++     Network() string
+++
+++     // Join creates a new sandbox for the given container ID and populates the
+++     // network resources allocated for the endpoint and joins the sandbox to
+++     // the endpoint.
+++     Join(containerID string, options ...EndpointOption) error
+++
+++     // Leave removes the sandbox associated with  container ID and detaches
+++     // the network resources populated in the sandbox
+++     Leave(containerID string, options ...EndpointOption) error
+++
+++     // Return certain operational data belonging to this endpoint
+++     Info() EndpointInfo
+++
+++     // DriverInfo returns a collection of driver operational data related to this endpoint retrieved from the driver
+++     DriverInfo() (map[string]interface{}, error)
+++
+++     // ContainerInfo returns the info available at the endpoint about the attached container
+++     ContainerInfo() ContainerInfo
+++
+++     // Delete and detaches this endpoint from the network.
+++     Delete() error
+++
+++     // Retrieve the interfaces' statistics from the sandbox
+++     Statistics() (map[string]*sandbox.InterfaceStatistics, error)
+++}
+++
+++// EndpointOption is a option setter function type used to pass varios options to Network
+++// and Endpoint interfaces methods. The various setter functions of type EndpointOption are
+++// provided by libnetwork, they look like <Create|Join|Leave>Option[...](...)
+++type EndpointOption func(ep *endpoint)
+++
+++// ContainerData is a set of data returned when a container joins an endpoint.
+++type ContainerData struct {
+++     SandboxKey string
+++}
+++
+++// These are the container configs used to customize container /etc/hosts file.
+++type hostsPathConfig struct {
+++     hostName      string
+++     domainName    string
+++     hostsPath     string
+++     extraHosts    []extraHost
+++     parentUpdates []parentUpdate
+++}
+++
+++// These are the container configs used to customize container /etc/resolv.conf file.
+++type resolvConfPathConfig struct {
+++     resolvConfPath string
+++     dnsList        []string
+++     dnsSearchList  []string
+++}
+++
+++type containerConfig struct {
+++     hostsPathConfig
+++     resolvConfPathConfig
+++     generic           map[string]interface{}
+++     useDefaultSandBox bool
+++     prio              int // higher the value, more the priority
+++}
+++
+++type extraHost struct {
+++     name string
+++     IP   string
+++}
+++
+++type parentUpdate struct {
+++     eid  string
+++     name string
+++     ip   string
+++}
+++
+++type containerInfo struct {
+++     id     string
+++     config containerConfig
+++     data   ContainerData
+++     sync.Mutex
+++}
+++
+++func (ci *containerInfo) ID() string {
+++     return ci.id
+++}
+++
+++func (ci *containerInfo) Labels() map[string]interface{} {
+++     return ci.config.generic
+++}
+++
+++type endpoint struct {
+++     name          string
+++     id            types.UUID
+++     network       *network
+++     iFaces        []*endpointInterface
+++     joinInfo      *endpointJoinInfo
+++     container     *containerInfo
+++     exposedPorts  []types.TransportPort
+++     generic       map[string]interface{}
+++     joinLeaveDone chan struct{}
+++     dbIndex       uint64
+++     dbExists      bool
+++     sync.Mutex
+++}
+++
+++func (ci *containerInfo) MarshalJSON() ([]byte, error) {
+++     ci.Lock()
+++     defer ci.Unlock()
+++
+++     // We are just interested in the container ID. This can be expanded to include all of containerInfo if there is a need
+++     return json.Marshal(ci.id)
+++}
+++
+++func (ci *containerInfo) UnmarshalJSON(b []byte) (err error) {
+++     ci.Lock()
+++     defer ci.Unlock()
+++
+++     var id string
+++     if err := json.Unmarshal(b, &id); err != nil {
+++             return err
+++     }
+++     ci.id = id
+++     return nil
+++}
+++
+++func (ep *endpoint) MarshalJSON() ([]byte, error) {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     epMap := make(map[string]interface{})
+++     epMap["name"] = ep.name
+++     epMap["id"] = string(ep.id)
+++     epMap["ep_iface"] = ep.iFaces
+++     epMap["exposed_ports"] = ep.exposedPorts
+++     epMap["generic"] = ep.generic
+++     if ep.container != nil {
+++             epMap["container"] = ep.container
+++     }
+++     return json.Marshal(epMap)
+++}
+++
+++func (ep *endpoint) UnmarshalJSON(b []byte) (err error) {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     var epMap map[string]interface{}
+++     if err := json.Unmarshal(b, &epMap); err != nil {
+++             return err
+++     }
+++     ep.name = epMap["name"].(string)
+++     ep.id = types.UUID(epMap["id"].(string))
+++
+++     ib, _ := json.Marshal(epMap["ep_iface"])
+++     var ifaces []endpointInterface
+++     json.Unmarshal(ib, &ifaces)
+++     ep.iFaces = make([]*endpointInterface, 0)
+++     for _, iface := range ifaces {
+++             ep.iFaces = append(ep.iFaces, &iface)
+++     }
+++
+++     tb, _ := json.Marshal(epMap["exposed_ports"])
+++     var tPorts []types.TransportPort
+++     json.Unmarshal(tb, &tPorts)
+++     ep.exposedPorts = tPorts
+++
+++     epc, ok := epMap["container"]
+++     if ok {
+++             cb, _ := json.Marshal(epc)
+++             var cInfo containerInfo
+++             json.Unmarshal(cb, &cInfo)
+++             ep.container = &cInfo
+++     }
+++
+++     if epMap["generic"] != nil {
+++             ep.generic = epMap["generic"].(map[string]interface{})
+++     }
+++     return nil
+++}
+++
+++const defaultPrefix = "/var/lib/docker/network/files"
+++
+++func (ep *endpoint) ID() string {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     return string(ep.id)
+++}
+++
+++func (ep *endpoint) Name() string {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     return ep.name
+++}
+++
+++func (ep *endpoint) Network() string {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     return ep.network.name
+++}
+++
+++// endpoint Key structure : endpoint/network-id/endpoint-id
+++func (ep *endpoint) Key() []string {
+++     ep.Lock()
+++     n := ep.network
+++     defer ep.Unlock()
+++     return []string{datastore.EndpointKeyPrefix, string(n.id), string(ep.id)}
+++}
+++
+++func (ep *endpoint) KeyPrefix() []string {
+++     ep.Lock()
+++     n := ep.network
+++     defer ep.Unlock()
+++     return []string{datastore.EndpointKeyPrefix, string(n.id)}
+++}
+++
+++func (ep *endpoint) networkIDFromKey(key []string) (types.UUID, error) {
+++     // endpoint Key structure : endpoint/network-id/endpoint-id
+++     // it's an invalid key if the key doesn't have all the 3 key elements above
+++     if key == nil || len(key) < 3 || key[0] != datastore.EndpointKeyPrefix {
+++             return types.UUID(""), fmt.Errorf("invalid endpoint key : %v", key)
+++     }
+++
+++     // network-id is placed at index=1. pls refer to endpoint.Key() method
+++     return types.UUID(key[1]), nil
+++}
+++
+++func (ep *endpoint) Value() []byte {
+++     b, err := json.Marshal(ep)
+++     if err != nil {
+++             return nil
+++     }
+++     return b
+++}
+++
+++func (ep *endpoint) SetValue(value []byte) error {
+++     return json.Unmarshal(value, ep)
+++}
+++
+++func (ep *endpoint) Index() uint64 {
+++     ep.Lock()
+++     defer ep.Unlock()
+++     return ep.dbIndex
+++}
+++
+++func (ep *endpoint) SetIndex(index uint64) {
+++     ep.Lock()
+++     defer ep.Unlock()
+++     ep.dbIndex = index
+++     ep.dbExists = true
+++}
+++
+++func (ep *endpoint) Exists() bool {
+++     ep.Lock()
+++     defer ep.Unlock()
+++     return ep.dbExists
+++}
+++
+++func (ep *endpoint) processOptions(options ...EndpointOption) {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     for _, opt := range options {
+++             if opt != nil {
+++                     opt(ep)
+++             }
+++     }
+++}
+++
+++func createBasePath(dir string) error {
+++     err := os.MkdirAll(dir, 0644)
+++     if err != nil && !os.IsExist(err) {
+++             return err
+++     }
+++
+++     return nil
+++}
+++
+++func createFile(path string) error {
+++     var f *os.File
+++
+++     dir, _ := filepath.Split(path)
+++     err := createBasePath(dir)
+++     if err != nil {
+++             return err
+++     }
+++
+++     f, err = os.Create(path)
+++     if err == nil {
+++             f.Close()
+++     }
+++
+++     return err
+++}
+++
+++// joinLeaveStart waits to ensure there are no joins or leaves in progress and
+++// marks this join/leave in progress without race
+++func (ep *endpoint) joinLeaveStart() {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     for ep.joinLeaveDone != nil {
+++             joinLeaveDone := ep.joinLeaveDone
+++             ep.Unlock()
+++
+++             select {
+++             case <-joinLeaveDone:
+++             }
+++
+++             ep.Lock()
+++     }
+++
+++     ep.joinLeaveDone = make(chan struct{})
+++}
+++
+++// joinLeaveEnd marks the end of this join/leave operation and
+++// signals the same without race to other join and leave waiters
+++func (ep *endpoint) joinLeaveEnd() {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     if ep.joinLeaveDone != nil {
+++             close(ep.joinLeaveDone)
+++             ep.joinLeaveDone = nil
+++     }
+++}
+++
+++func (ep *endpoint) Join(containerID string, options ...EndpointOption) error {
+++     var err error
+++
+++     if containerID == "" {
+++             return InvalidContainerIDError(containerID)
+++     }
+++
+++     ep.joinLeaveStart()
+++     defer func() {
+++             ep.joinLeaveEnd()
+++     }()
+++
+++     ep.Lock()
+++     if ep.container != nil {
+++             ep.Unlock()
+++             return ErrInvalidJoin{}
+++     }
+++
+++     ep.container = &containerInfo{
+++             id: containerID,
+++             config: containerConfig{
+++                     hostsPathConfig: hostsPathConfig{
+++                             extraHosts:    []extraHost{},
+++                             parentUpdates: []parentUpdate{},
+++                     },
+++             }}
+++
+++     ep.joinInfo = &endpointJoinInfo{}
+++
+++     container := ep.container
+++     network := ep.network
+++     epid := ep.id
+++
+++     ep.Unlock()
+++     defer func() {
+++             if err != nil {
+++                     ep.Lock()
+++                     ep.container = nil
+++                     ep.Unlock()
+++             }
+++     }()
+++
+++     network.Lock()
+++     driver := network.driver
+++     nid := network.id
+++     ctrlr := network.ctrlr
+++     network.Unlock()
+++
+++     ep.processOptions(options...)
+++
+++     sboxKey := sandbox.GenerateKey(containerID)
+++     if container.config.useDefaultSandBox {
+++             sboxKey = sandbox.GenerateKey("default")
+++     }
+++
+++     err = driver.Join(nid, epid, sboxKey, ep, container.config.generic)
+++     if err != nil {
+++             return err
+++     }
+++     defer func() {
+++             if err != nil {
+++                     // Do not alter global err variable, it's needed by the previous defer
+++                     if err := driver.Leave(nid, epid); err != nil {
+++                             log.Warnf("driver leave failed while rolling back join: %v", err)
+++                     }
+++             }
+++     }()
+++
+++     err = ep.buildHostsFiles()
+++     if err != nil {
+++             return err
+++     }
+++
+++     err = ep.updateParentHosts()
+++     if err != nil {
+++             return err
+++     }
+++
+++     err = ep.setupDNS()
+++     if err != nil {
+++             return err
+++     }
+++
+++     sb, err := ctrlr.sandboxAdd(sboxKey, !container.config.useDefaultSandBox, ep)
+++     if err != nil {
+++             return fmt.Errorf("failed sandbox add: %v", err)
+++     }
+++     defer func() {
+++             if err != nil {
+++                     ctrlr.sandboxRm(sboxKey, ep)
+++             }
+++     }()
+++
+++     if err := network.ctrlr.updateEndpointToStore(ep); err != nil {
+++             return err
+++     }
+++
+++     container.data.SandboxKey = sb.Key()
+++     return nil
+++}
+++
+++func (ep *endpoint) hasInterface(iName string) bool {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     for _, iface := range ep.iFaces {
+++             if iface.srcName == iName {
+++                     return true
+++             }
+++     }
+++
+++     return false
+++}
+++
+++func (ep *endpoint) Leave(containerID string, options ...EndpointOption) error {
+++     var err error
+++
+++     ep.joinLeaveStart()
+++     defer ep.joinLeaveEnd()
+++
+++     ep.processOptions(options...)
+++
+++     ep.Lock()
+++     container := ep.container
+++     n := ep.network
+++
+++     if container == nil || container.id == "" || container.data.SandboxKey == "" ||
+++             containerID == "" || container.id != containerID {
+++             if container == nil {
+++                     err = ErrNoContainer{}
+++             } else {
+++                     err = InvalidContainerIDError(containerID)
+++             }
+++
+++             ep.Unlock()
+++             return err
+++     }
+++     ep.container = nil
+++     ep.Unlock()
+++
+++     n.Lock()
+++     driver := n.driver
+++     ctrlr := n.ctrlr
+++     n.Unlock()
+++
+++     if err := ctrlr.updateEndpointToStore(ep); err != nil {
+++             ep.Lock()
+++             ep.container = container
+++             ep.Unlock()
+++             return err
+++     }
+++
+++     err = driver.Leave(n.id, ep.id)
+++
+++     ctrlr.sandboxRm(container.data.SandboxKey, ep)
+++
+++     return err
+++}
+++
+++func (ep *endpoint) Delete() error {
+++     var err error
+++     ep.Lock()
+++     epid := ep.id
+++     name := ep.name
+++     n := ep.network
+++     if ep.container != nil {
+++             ep.Unlock()
+++             return &ActiveContainerError{name: name, id: string(epid)}
+++     }
+++     n.Lock()
+++     ctrlr := n.ctrlr
+++     n.Unlock()
+++     ep.Unlock()
+++
+++     if err = ctrlr.deleteEndpointFromStore(ep); err != nil {
+++             return err
+++     }
+++     defer func() {
+++             if err != nil {
+++                     ep.SetIndex(0)
+++                     if e := ctrlr.updateEndpointToStore(ep); e != nil {
+++                             log.Warnf("failed to recreate endpoint in store %s : %v", name, err)
+++                     }
+++             }
+++     }()
+++
+++     // Update the endpoint count in network and update it in the datastore
+++     n.DecEndpointCnt()
+++     if err = ctrlr.updateNetworkToStore(n); err != nil {
+++             return err
+++     }
+++     defer func() {
+++             if err != nil {
+++                     n.IncEndpointCnt()
+++                     if e := ctrlr.updateNetworkToStore(n); e != nil {
+++                             log.Warnf("failed to update network %s : %v", n.name, e)
+++                     }
+++             }
+++     }()
+++
+++     if err = ep.deleteEndpoint(); err != nil {
+++             return err
+++     }
+++
+++     return nil
+++}
+++
+++func (ep *endpoint) Statistics() (map[string]*sandbox.InterfaceStatistics, error) {
+++     m := make(map[string]*sandbox.InterfaceStatistics)
+++
+++     ep.Lock()
+++     n := ep.network
+++     skey := ep.container.data.SandboxKey
+++     ep.Unlock()
+++
+++     n.Lock()
+++     c := n.ctrlr
+++     n.Unlock()
+++
+++     sbox := c.sandboxGet(skey)
+++     if sbox == nil {
+++             return m, nil
+++     }
+++
+++     var err error
+++     for _, i := range sbox.Info().Interfaces() {
+++             if m[i.DstName()], err = i.Statistics(); err != nil {
+++                     return m, err
+++             }
+++     }
+++
+++     return m, nil
+++}
+++
+++func (ep *endpoint) deleteEndpoint() error {
+++     ep.Lock()
+++     n := ep.network
+++     name := ep.name
+++     epid := ep.id
+++     ep.Unlock()
+++
+++     n.Lock()
+++     _, ok := n.endpoints[epid]
+++     if !ok {
+++             n.Unlock()
+++             return nil
+++     }
+++
+++     nid := n.id
+++     driver := n.driver
+++     delete(n.endpoints, epid)
+++     n.Unlock()
+++
+++     if err := driver.DeleteEndpoint(nid, epid); err != nil {
+++             if _, ok := err.(types.ForbiddenError); ok {
+++                     n.Lock()
+++                     n.endpoints[epid] = ep
+++                     n.Unlock()
+++                     return err
+++             }
+++             log.Warnf("driver error deleting endpoint %s : %v", name, err)
+++     }
+++
+++     n.updateSvcRecord(ep, false)
+++     return nil
+++}
+++
+++func (ep *endpoint) addHostEntries(recs []etchosts.Record) {
+++     ep.Lock()
+++     container := ep.container
+++     ep.Unlock()
+++
+++     if container == nil {
+++             return
+++     }
+++
+++     if err := etchosts.Add(container.config.hostsPath, recs); err != nil {
+++             log.Warnf("Failed adding service host entries to the running container: %v", err)
+++     }
+++}
+++
+++func (ep *endpoint) deleteHostEntries(recs []etchosts.Record) {
+++     ep.Lock()
+++     container := ep.container
+++     ep.Unlock()
+++
+++     if container == nil {
+++             return
+++     }
+++
+++     if err := etchosts.Delete(container.config.hostsPath, recs); err != nil {
+++             log.Warnf("Failed deleting service host entries to the running container: %v", err)
+++     }
+++}
+++
+++func (ep *endpoint) buildHostsFiles() error {
+++     var extraContent []etchosts.Record
+++
+++     ep.Lock()
+++     container := ep.container
+++     joinInfo := ep.joinInfo
+++     ifaces := ep.iFaces
+++     n := ep.network
+++     ep.Unlock()
+++
+++     if container == nil {
+++             return ErrNoContainer{}
+++     }
+++
+++     if container.config.hostsPath == "" {
+++             container.config.hostsPath = defaultPrefix + "/" + container.id + "/hosts"
+++     }
+++
+++     dir, _ := filepath.Split(container.config.hostsPath)
+++     err := createBasePath(dir)
+++     if err != nil {
+++             return err
+++     }
+++
+++     if joinInfo != nil && joinInfo.hostsPath != "" {
+++             content, err := ioutil.ReadFile(joinInfo.hostsPath)
+++             if err != nil && !os.IsNotExist(err) {
+++                     return err
+++             }
+++
+++             if err == nil {
+++                     return ioutil.WriteFile(container.config.hostsPath, content, 0644)
+++             }
+++     }
+++
+++     for _, extraHost := range container.config.extraHosts {
+++             extraContent = append(extraContent,
+++                     etchosts.Record{Hosts: extraHost.name, IP: extraHost.IP})
+++     }
+++
+++     extraContent = append(extraContent, n.getSvcRecords()...)
+++
+++     IP := ""
+++     if len(ifaces) != 0 && ifaces[0] != nil {
+++             IP = ifaces[0].addr.IP.String()
+++     }
+++
+++     return etchosts.Build(container.config.hostsPath, IP, container.config.hostName,
+++             container.config.domainName, extraContent)
+++}
+++
+++func (ep *endpoint) updateParentHosts() error {
+++     ep.Lock()
+++     container := ep.container
+++     network := ep.network
+++     ep.Unlock()
+++
+++     if container == nil {
+++             return ErrNoContainer{}
+++     }
+++
+++     for _, update := range container.config.parentUpdates {
+++             network.Lock()
+++             pep, ok := network.endpoints[types.UUID(update.eid)]
+++             if !ok {
+++                     network.Unlock()
+++                     continue
+++             }
+++             network.Unlock()
+++
+++             pep.Lock()
+++             pContainer := pep.container
+++             pep.Unlock()
+++
+++             if pContainer != nil {
+++                     if err := etchosts.Update(pContainer.config.hostsPath, update.ip, update.name); err != nil {
+++                             return err
+++                     }
+++             }
+++     }
+++
+++     return nil
+++}
+++
+++func (ep *endpoint) updateDNS(resolvConf []byte) error {
+++     ep.Lock()
+++     container := ep.container
+++     network := ep.network
+++     ep.Unlock()
+++
+++     if container == nil {
+++             return ErrNoContainer{}
+++     }
+++
+++     oldHash := []byte{}
+++     hashFile := container.config.resolvConfPath + ".hash"
+++
+++     resolvBytes, err := ioutil.ReadFile(container.config.resolvConfPath)
+++     if err != nil {
+++             if !os.IsNotExist(err) {
+++                     return err
+++             }
+++     } else {
+++             oldHash, err = ioutil.ReadFile(hashFile)
+++             if err != nil {
+++                     if !os.IsNotExist(err) {
+++                             return err
+++                     }
+++
+++                     oldHash = []byte{}
+++             }
+++     }
+++
+++     curHash, err := ioutils.HashData(bytes.NewReader(resolvBytes))
+++     if err != nil {
+++             return err
+++     }
+++
+++     if string(oldHash) != "" && curHash != string(oldHash) {
+++             // Seems the user has changed the container resolv.conf since the last time
+++             // we checked so return without doing anything.
+++             return nil
+++     }
+++
+++     // replace any localhost/127.* and remove IPv6 nameservers if IPv6 disabled.
+++     resolvConf, _ = resolvconf.FilterResolvDNS(resolvConf, network.enableIPv6)
+++
+++     newHash, err := ioutils.HashData(bytes.NewReader(resolvConf))
+++     if err != nil {
+++             return err
+++     }
+++
+++     // for atomic updates to these files, use temporary files with os.Rename:
+++     dir := path.Dir(container.config.resolvConfPath)
+++     tmpHashFile, err := ioutil.TempFile(dir, "hash")
+++     if err != nil {
+++             return err
+++     }
+++     tmpResolvFile, err := ioutil.TempFile(dir, "resolv")
+++     if err != nil {
+++             return err
+++     }
+++
+++     // Change the perms to 0644 since ioutil.TempFile creates it by default as 0600
+++     if err := os.Chmod(tmpResolvFile.Name(), 0644); err != nil {
+++             return err
+++     }
+++
+++     // write the updates to the temp files
+++     if err = ioutil.WriteFile(tmpHashFile.Name(), []byte(newHash), 0644); err != nil {
+++             return err
+++     }
+++     if err = ioutil.WriteFile(tmpResolvFile.Name(), resolvConf, 0644); err != nil {
+++             return err
+++     }
+++
+++     // rename the temp files for atomic replace
+++     if err = os.Rename(tmpHashFile.Name(), hashFile); err != nil {
+++             return err
+++     }
+++     return os.Rename(tmpResolvFile.Name(), container.config.resolvConfPath)
+++}
+++
+++func copyFile(src, dst string) error {
+++     sBytes, err := ioutil.ReadFile(src)
+++     if err != nil {
+++             return err
+++     }
+++
+++     return ioutil.WriteFile(dst, sBytes, 0644)
+++}
+++
+++func (ep *endpoint) setupDNS() error {
+++     ep.Lock()
+++     container := ep.container
+++     joinInfo := ep.joinInfo
+++     ep.Unlock()
+++
+++     if container == nil {
+++             return ErrNoContainer{}
+++     }
+++
+++     if container.config.resolvConfPath == "" {
+++             container.config.resolvConfPath = defaultPrefix + "/" + container.id + "/resolv.conf"
+++     }
+++
+++     dir, _ := filepath.Split(container.config.resolvConfPath)
+++     err := createBasePath(dir)
+++     if err != nil {
+++             return err
+++     }
+++
+++     if joinInfo.resolvConfPath != "" {
+++             if err := copyFile(joinInfo.resolvConfPath, container.config.resolvConfPath); err != nil {
+++                     return fmt.Errorf("could not copy source resolv.conf file %s to %s: %v", joinInfo.resolvConfPath, container.config.resolvConfPath, err)
+++             }
+++
+++             return nil
+++     }
+++
+++     resolvConf, err := resolvconf.Get()
+++     if err != nil {
+++             return err
+++     }
+++
+++     if len(container.config.dnsList) > 0 ||
+++             len(container.config.dnsSearchList) > 0 {
+++             var (
+++                     dnsList       = resolvconf.GetNameservers(resolvConf)
+++                     dnsSearchList = resolvconf.GetSearchDomains(resolvConf)
+++             )
+++
+++             if len(container.config.dnsList) > 0 {
+++                     dnsList = container.config.dnsList
+++             }
+++
+++             if len(container.config.dnsSearchList) > 0 {
+++                     dnsSearchList = container.config.dnsSearchList
+++             }
+++
+++             return resolvconf.Build(container.config.resolvConfPath, dnsList, dnsSearchList)
+++     }
+++
+++     return ep.updateDNS(resolvConf)
+++}
+++
+++// EndpointOptionGeneric function returns an option setter for a Generic option defined
+++// in a Dictionary of Key-Value pair
+++func EndpointOptionGeneric(generic map[string]interface{}) EndpointOption {
+++     return func(ep *endpoint) {
+++             for k, v := range generic {
+++                     ep.generic[k] = v
+++             }
+++     }
+++}
+++
+++// JoinOptionPriority function returns an option setter for priority option to
+++// be passed to endpoint Join method.
+++func JoinOptionPriority(prio int) EndpointOption {
+++     return func(ep *endpoint) {
+++             ep.container.config.prio = prio
+++     }
+++}
+++
+++// JoinOptionHostname function returns an option setter for hostname option to
+++// be passed to endpoint Join method.
+++func JoinOptionHostname(name string) EndpointOption {
+++     return func(ep *endpoint) {
+++             ep.container.config.hostName = name
+++     }
+++}
+++
+++// JoinOptionDomainname function returns an option setter for domainname option to
+++// be passed to endpoint Join method.
+++func JoinOptionDomainname(name string) EndpointOption {
+++     return func(ep *endpoint) {
+++             ep.container.config.domainName = name
+++     }
+++}
+++
+++// JoinOptionHostsPath function returns an option setter for hostspath option to
+++// be passed to endpoint Join method.
+++func JoinOptionHostsPath(path string) EndpointOption {
+++     return func(ep *endpoint) {
+++             ep.container.config.hostsPath = path
+++     }
+++}
+++
+++// JoinOptionExtraHost function returns an option setter for extra /etc/hosts options
+++// which is a name and IP as strings.
+++func JoinOptionExtraHost(name string, IP string) EndpointOption {
+++     return func(ep *endpoint) {
+++             ep.container.config.extraHosts = append(ep.container.config.extraHosts, extraHost{name: name, IP: IP})
+++     }
+++}
+++
+++// JoinOptionParentUpdate function returns an option setter for parent container
+++// which needs to update the IP address for the linked container.
+++func JoinOptionParentUpdate(eid string, name, ip string) EndpointOption {
+++     return func(ep *endpoint) {
+++             ep.container.config.parentUpdates = append(ep.container.config.parentUpdates, parentUpdate{eid: eid, name: name, ip: ip})
+++     }
+++}
+++
+++// JoinOptionResolvConfPath function returns an option setter for resolvconfpath option to
+++// be passed to endpoint Join method.
+++func JoinOptionResolvConfPath(path string) EndpointOption {
+++     return func(ep *endpoint) {
+++             ep.container.config.resolvConfPath = path
+++     }
+++}
+++
+++// JoinOptionDNS function returns an option setter for dns entry option to
+++// be passed to endpoint Join method.
+++func JoinOptionDNS(dns string) EndpointOption {
+++     return func(ep *endpoint) {
+++             ep.container.config.dnsList = append(ep.container.config.dnsList, dns)
+++     }
+++}
+++
+++// JoinOptionDNSSearch function returns an option setter for dns search entry option to
+++// be passed to endpoint Join method.
+++func JoinOptionDNSSearch(search string) EndpointOption {
+++     return func(ep *endpoint) {
+++             ep.container.config.dnsSearchList = append(ep.container.config.dnsSearchList, search)
+++     }
+++}
+++
+++// JoinOptionUseDefaultSandbox function returns an option setter for using default sandbox to
+++// be passed to endpoint Join method.
+++func JoinOptionUseDefaultSandbox() EndpointOption {
+++     return func(ep *endpoint) {
+++             ep.container.config.useDefaultSandBox = true
+++     }
+++}
+++
+++// CreateOptionExposedPorts function returns an option setter for the container exposed
+++// ports option to be passed to network.CreateEndpoint() method.
+++func CreateOptionExposedPorts(exposedPorts []types.TransportPort) EndpointOption {
+++     return func(ep *endpoint) {
+++             // Defensive copy
+++             eps := make([]types.TransportPort, len(exposedPorts))
+++             copy(eps, exposedPorts)
+++             // Store endpoint label and in generic because driver needs it
+++             ep.exposedPorts = eps
+++             ep.generic[netlabel.ExposedPorts] = eps
+++     }
+++}
+++
+++// CreateOptionPortMapping function returns an option setter for the mapping
+++// ports option to be passed to network.CreateEndpoint() method.
+++func CreateOptionPortMapping(portBindings []types.PortBinding) EndpointOption {
+++     return func(ep *endpoint) {
+++             // Store a copy of the bindings as generic data to pass to the driver
+++             pbs := make([]types.PortBinding, len(portBindings))
+++             copy(pbs, portBindings)
+++             ep.generic[netlabel.PortMap] = pbs
+++     }
+++}
+++
+++// JoinOptionGeneric function returns an option setter for Generic configuration
+++// that is not managed by libNetwork but can be used by the Drivers during the call to
+++// endpoint join method. Container Labels are a good example.
+++func JoinOptionGeneric(generic map[string]interface{}) EndpointOption {
+++     return func(ep *endpoint) {
+++             ep.container.config.generic = generic
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..6c0e117132c91da701063b0020f69c53d0448392
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,323 @@@@
+++package libnetwork
+++
+++import (
+++     "encoding/json"
+++     "net"
+++
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++// EndpointInfo provides an interface to retrieve network resources bound to the endpoint.
+++type EndpointInfo interface {
+++     // InterfaceList returns an interface list which were assigned to the endpoint
+++     // by the driver. This can be used after the endpoint has been created.
+++     InterfaceList() []InterfaceInfo
+++
+++     // Gateway returns the IPv4 gateway assigned by the driver.
+++     // This will only return a valid value if a container has joined the endpoint.
+++     Gateway() net.IP
+++
+++     // GatewayIPv6 returns the IPv6 gateway assigned by the driver.
+++     // This will only return a valid value if a container has joined the endpoint.
+++     GatewayIPv6() net.IP
+++
+++     // SandboxKey returns the sanbox key for the container which has joined
+++     // the endpoint. If there is no container joined then this will return an
+++     // empty string.
+++     SandboxKey() string
+++}
+++
+++// InterfaceInfo provides an interface to retrieve interface addresses bound to the endpoint.
+++type InterfaceInfo interface {
+++     // MacAddress returns the MAC address assigned to the endpoint.
+++     MacAddress() net.HardwareAddr
+++
+++     // Address returns the IPv4 address assigned to the endpoint.
+++     Address() net.IPNet
+++
+++     // AddressIPv6 returns the IPv6 address assigned to the endpoint.
+++     AddressIPv6() net.IPNet
+++}
+++
+++// ContainerInfo provides an interface to retrieve the info about the container attached to the endpoint
+++type ContainerInfo interface {
+++     // ID returns the ID of the container
+++     ID() string
+++     // Labels returns the container's labels
+++     Labels() map[string]interface{}
+++}
+++
+++type endpointInterface struct {
+++     id        int
+++     mac       net.HardwareAddr
+++     addr      net.IPNet
+++     addrv6    net.IPNet
+++     srcName   string
+++     dstPrefix string
+++     routes    []*net.IPNet
+++}
+++
+++func (epi *endpointInterface) MarshalJSON() ([]byte, error) {
+++     epMap := make(map[string]interface{})
+++     epMap["id"] = epi.id
+++     epMap["mac"] = epi.mac.String()
+++     epMap["addr"] = epi.addr.String()
+++     epMap["addrv6"] = epi.addrv6.String()
+++     epMap["srcName"] = epi.srcName
+++     epMap["dstPrefix"] = epi.dstPrefix
+++     var routes []string
+++     for _, route := range epi.routes {
+++             routes = append(routes, route.String())
+++     }
+++     epMap["routes"] = routes
+++     return json.Marshal(epMap)
+++}
+++
+++func (epi *endpointInterface) UnmarshalJSON(b []byte) (err error) {
+++     var epMap map[string]interface{}
+++     if err := json.Unmarshal(b, &epMap); err != nil {
+++             return err
+++     }
+++     epi.id = int(epMap["id"].(float64))
+++
+++     mac, _ := net.ParseMAC(epMap["mac"].(string))
+++     epi.mac = mac
+++
+++     ip, ipnet, _ := net.ParseCIDR(epMap["addr"].(string))
+++     if ipnet != nil {
+++             ipnet.IP = ip
+++             epi.addr = *ipnet
+++     }
+++
+++     ip, ipnet, _ = net.ParseCIDR(epMap["addrv6"].(string))
+++     if ipnet != nil {
+++             ipnet.IP = ip
+++             epi.addrv6 = *ipnet
+++     }
+++
+++     epi.srcName = epMap["srcName"].(string)
+++     epi.dstPrefix = epMap["dstPrefix"].(string)
+++
+++     rb, _ := json.Marshal(epMap["routes"])
+++     var routes []string
+++     json.Unmarshal(rb, &routes)
+++     epi.routes = make([]*net.IPNet, 0)
+++     for _, route := range routes {
+++             ip, ipr, err := net.ParseCIDR(route)
+++             if err == nil {
+++                     ipr.IP = ip
+++                     epi.routes = append(epi.routes, ipr)
+++             }
+++     }
+++
+++     return nil
+++}
+++
+++type endpointJoinInfo struct {
+++     gw             net.IP
+++     gw6            net.IP
+++     hostsPath      string
+++     resolvConfPath string
+++     StaticRoutes   []*types.StaticRoute
+++}
+++
+++func (ep *endpoint) ContainerInfo() ContainerInfo {
+++     ep.Lock()
+++     ci := ep.container
+++     defer ep.Unlock()
+++
+++     // Need this since we return the interface
+++     if ci == nil {
+++             return nil
+++     }
+++     return ci
+++}
+++
+++func (ep *endpoint) Info() EndpointInfo {
+++     return ep
+++}
+++
+++func (ep *endpoint) DriverInfo() (map[string]interface{}, error) {
+++     ep.Lock()
+++     network := ep.network
+++     epid := ep.id
+++     ep.Unlock()
+++
+++     network.Lock()
+++     driver := network.driver
+++     nid := network.id
+++     network.Unlock()
+++
+++     return driver.EndpointOperInfo(nid, epid)
+++}
+++
+++func (ep *endpoint) InterfaceList() []InterfaceInfo {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     iList := make([]InterfaceInfo, len(ep.iFaces))
+++
+++     for i, iface := range ep.iFaces {
+++             iList[i] = iface
+++     }
+++
+++     return iList
+++}
+++
+++func (ep *endpoint) Interfaces() []driverapi.InterfaceInfo {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     iList := make([]driverapi.InterfaceInfo, len(ep.iFaces))
+++
+++     for i, iface := range ep.iFaces {
+++             iList[i] = iface
+++     }
+++
+++     return iList
+++}
+++
+++func (ep *endpoint) AddInterface(id int, mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     iface := &endpointInterface{
+++             id:     id,
+++             addr:   *types.GetIPNetCopy(&ipv4),
+++             addrv6: *types.GetIPNetCopy(&ipv6),
+++     }
+++     iface.mac = types.GetMacCopy(mac)
+++
+++     ep.iFaces = append(ep.iFaces, iface)
+++     return nil
+++}
+++
+++func (epi *endpointInterface) ID() int {
+++     return epi.id
+++}
+++
+++func (epi *endpointInterface) MacAddress() net.HardwareAddr {
+++     return types.GetMacCopy(epi.mac)
+++}
+++
+++func (epi *endpointInterface) Address() net.IPNet {
+++     return (*types.GetIPNetCopy(&epi.addr))
+++}
+++
+++func (epi *endpointInterface) AddressIPv6() net.IPNet {
+++     return (*types.GetIPNetCopy(&epi.addrv6))
+++}
+++
+++func (epi *endpointInterface) SetNames(srcName string, dstPrefix string) error {
+++     epi.srcName = srcName
+++     epi.dstPrefix = dstPrefix
+++     return nil
+++}
+++
+++func (ep *endpoint) InterfaceNames() []driverapi.InterfaceNameInfo {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     iList := make([]driverapi.InterfaceNameInfo, len(ep.iFaces))
+++
+++     for i, iface := range ep.iFaces {
+++             iList[i] = iface
+++     }
+++
+++     return iList
+++}
+++
+++func (ep *endpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP, interfaceID int) error {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     r := types.StaticRoute{Destination: destination, RouteType: routeType, NextHop: nextHop, InterfaceID: interfaceID}
+++
+++     if routeType == types.NEXTHOP {
+++             // If the route specifies a next-hop, then it's loosely routed (i.e. not bound to a particular interface).
+++             ep.joinInfo.StaticRoutes = append(ep.joinInfo.StaticRoutes, &r)
+++     } else {
+++             // If the route doesn't specify a next-hop, it must be a connected route, bound to an interface.
+++             if err := ep.addInterfaceRoute(&r); err != nil {
+++                     return err
+++             }
+++     }
+++     return nil
+++}
+++
+++func (ep *endpoint) addInterfaceRoute(route *types.StaticRoute) error {
+++     for _, iface := range ep.iFaces {
+++             if iface.id == route.InterfaceID {
+++                     iface.routes = append(iface.routes, route.Destination)
+++                     return nil
+++             }
+++     }
+++     return types.BadRequestErrorf("Interface with ID %d doesn't exist.",
+++             route.InterfaceID)
+++}
+++
+++func (ep *endpoint) SandboxKey() string {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     if ep.container == nil {
+++             return ""
+++     }
+++
+++     return ep.container.data.SandboxKey
+++}
+++
+++func (ep *endpoint) Gateway() net.IP {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     if ep.joinInfo == nil {
+++             return net.IP{}
+++     }
+++
+++     return types.GetIPCopy(ep.joinInfo.gw)
+++}
+++
+++func (ep *endpoint) GatewayIPv6() net.IP {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     if ep.joinInfo == nil {
+++             return net.IP{}
+++     }
+++
+++     return types.GetIPCopy(ep.joinInfo.gw6)
+++}
+++
+++func (ep *endpoint) SetGateway(gw net.IP) error {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     ep.joinInfo.gw = types.GetIPCopy(gw)
+++     return nil
+++}
+++
+++func (ep *endpoint) SetGatewayIPv6(gw6 net.IP) error {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     ep.joinInfo.gw6 = types.GetIPCopy(gw6)
+++     return nil
+++}
+++
+++func (ep *endpoint) SetHostsPath(path string) error {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     ep.joinInfo.hostsPath = path
+++     return nil
+++}
+++
+++func (ep *endpoint) SetResolvConfPath(path string) error {
+++     ep.Lock()
+++     defer ep.Unlock()
+++
+++     ep.joinInfo.resolvConfPath = path
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d31a50948bc6603c22a0f3ef8f66e59e306db105
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,175 @@@@
+++package libnetwork
+++
+++import (
+++     "fmt"
+++)
+++
+++// ErrNoSuchNetwork is returned when a network query finds no result
+++type ErrNoSuchNetwork string
+++
+++func (nsn ErrNoSuchNetwork) Error() string {
+++     return fmt.Sprintf("network %s not found", string(nsn))
+++}
+++
+++// NotFound denotes the type of this error
+++func (nsn ErrNoSuchNetwork) NotFound() {}
+++
+++// ErrNoSuchEndpoint is returned when a endpoint query finds no result
+++type ErrNoSuchEndpoint string
+++
+++func (nse ErrNoSuchEndpoint) Error() string {
+++     return fmt.Sprintf("endpoint %s not found", string(nse))
+++}
+++
+++// NotFound denotes the type of this error
+++func (nse ErrNoSuchEndpoint) NotFound() {}
+++
+++// ErrInvalidNetworkDriver is returned if an invalid driver
+++// name is passed.
+++type ErrInvalidNetworkDriver string
+++
+++func (ind ErrInvalidNetworkDriver) Error() string {
+++     return fmt.Sprintf("invalid driver bound to network: %s", string(ind))
+++}
+++
+++// BadRequest denotes the type of this error
+++func (ind ErrInvalidNetworkDriver) BadRequest() {}
+++
+++// ErrInvalidJoin is returned if a join is attempted on an endpoint
+++// which already has a container joined.
+++type ErrInvalidJoin struct{}
+++
+++func (ij ErrInvalidJoin) Error() string {
+++     return "a container has already joined the endpoint"
+++}
+++
+++// BadRequest denotes the type of this error
+++func (ij ErrInvalidJoin) BadRequest() {}
+++
+++// ErrNoContainer is returned when the endpoint has no container
+++// attached to it.
+++type ErrNoContainer struct{}
+++
+++func (nc ErrNoContainer) Error() string {
+++     return "no container is attached to the endpoint"
+++}
+++
+++// Maskable denotes the type of this error
+++func (nc ErrNoContainer) Maskable() {}
+++
+++// ErrInvalidID is returned when a query-by-id method is being invoked
+++// with an empty id parameter
+++type ErrInvalidID string
+++
+++func (ii ErrInvalidID) Error() string {
+++     return fmt.Sprintf("invalid id: %s", string(ii))
+++}
+++
+++// BadRequest denotes the type of this error
+++func (ii ErrInvalidID) BadRequest() {}
+++
+++// ErrInvalidName is returned when a query-by-name or resource create method is
+++// invoked with an empty name parameter
+++type ErrInvalidName string
+++
+++func (in ErrInvalidName) Error() string {
+++     return fmt.Sprintf("invalid name: %s", string(in))
+++}
+++
+++// BadRequest denotes the type of this error
+++func (in ErrInvalidName) BadRequest() {}
+++
+++// ErrInvalidConfigFile type is returned when an invalid LibNetwork config file is detected
+++type ErrInvalidConfigFile string
+++
+++func (cf ErrInvalidConfigFile) Error() string {
+++     return fmt.Sprintf("Invalid Config file %q", string(cf))
+++}
+++
+++// NetworkTypeError type is returned when the network type string is not
+++// known to libnetwork.
+++type NetworkTypeError string
+++
+++func (nt NetworkTypeError) Error() string {
+++     return fmt.Sprintf("unknown driver %q", string(nt))
+++}
+++
+++// NotFound denotes the type of this error
+++func (nt NetworkTypeError) NotFound() {}
+++
+++// NetworkNameError is returned when a network with the same name already exists.
+++type NetworkNameError string
+++
+++func (nnr NetworkNameError) Error() string {
+++     return fmt.Sprintf("network with name %s already exists", string(nnr))
+++}
+++
+++// Forbidden denotes the type of this error
+++func (nnr NetworkNameError) Forbidden() {}
+++
+++// UnknownNetworkError is returned when libnetwork could not find in it's database
+++// a network with the same name and id.
+++type UnknownNetworkError struct {
+++     name string
+++     id   string
+++}
+++
+++func (une *UnknownNetworkError) Error() string {
+++     return fmt.Sprintf("unknown network %s id %s", une.name, une.id)
+++}
+++
+++// NotFound denotes the type of this error
+++func (une *UnknownNetworkError) NotFound() {}
+++
+++// ActiveEndpointsError is returned when a network is deleted which has active
+++// endpoints in it.
+++type ActiveEndpointsError struct {
+++     name string
+++     id   string
+++}
+++
+++func (aee *ActiveEndpointsError) Error() string {
+++     return fmt.Sprintf("network with name %s id %s has active endpoints", aee.name, aee.id)
+++}
+++
+++// Forbidden denotes the type of this error
+++func (aee *ActiveEndpointsError) Forbidden() {}
+++
+++// UnknownEndpointError is returned when libnetwork could not find in it's database
+++// an endpoint with the same name and id.
+++type UnknownEndpointError struct {
+++     name string
+++     id   string
+++}
+++
+++func (uee *UnknownEndpointError) Error() string {
+++     return fmt.Sprintf("unknown endpoint %s id %s", uee.name, uee.id)
+++}
+++
+++// NotFound denotes the type of this error
+++func (uee *UnknownEndpointError) NotFound() {}
+++
+++// ActiveContainerError is returned when an endpoint is deleted which has active
+++// containers attached to it.
+++type ActiveContainerError struct {
+++     name string
+++     id   string
+++}
+++
+++func (ace *ActiveContainerError) Error() string {
+++     return fmt.Sprintf("endpoint with name %s id %s has active containers", ace.name, ace.id)
+++}
+++
+++// Forbidden denotes the type of this error
+++func (ace *ActiveContainerError) Forbidden() {}
+++
+++// InvalidContainerIDError is returned when an invalid container id is passed
+++// in Join/Leave
+++type InvalidContainerIDError string
+++
+++func (id InvalidContainerIDError) Error() string {
+++     return fmt.Sprintf("invalid container id %s", string(id))
+++}
+++
+++// BadRequest denotes the type of this error
+++func (id InvalidContainerIDError) BadRequest() {}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..29bf66868979a78d02fd355338d18f3a89b6c742
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,51 @@@@
+++package libnetwork
+++
+++import (
+++     "testing"
+++
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++func TestErrorInterfaces(t *testing.T) {
+++
+++     badRequestErrorList := []error{ErrInvalidID(""), ErrInvalidName(""), ErrInvalidJoin{}, ErrInvalidNetworkDriver(""), InvalidContainerIDError(""), ErrNoSuchNetwork(""), ErrNoSuchEndpoint("")}
+++     for _, err := range badRequestErrorList {
+++             switch u := err.(type) {
+++             case types.BadRequestError:
+++                     return
+++             default:
+++                     t.Fatalf("Failed to detect err %v is of type BadRequestError. Got type: %T", err, u)
+++             }
+++     }
+++
+++     maskableErrorList := []error{ErrNoContainer{}}
+++     for _, err := range maskableErrorList {
+++             switch u := err.(type) {
+++             case types.MaskableError:
+++                     return
+++             default:
+++                     t.Fatalf("Failed to detect err %v is of type MaskableError. Got type: %T", err, u)
+++             }
+++     }
+++
+++     notFoundErrorList := []error{NetworkTypeError(""), &UnknownNetworkError{}, &UnknownEndpointError{}}
+++     for _, err := range notFoundErrorList {
+++             switch u := err.(type) {
+++             case types.NotFoundError:
+++                     return
+++             default:
+++                     t.Fatalf("Failed to detect err %v is of type NotFoundError. Got type: %T", err, u)
+++             }
+++     }
+++
+++     forbiddenErrorList := []error{NetworkTypeError(""), &UnknownNetworkError{}, &UnknownEndpointError{}}
+++     for _, err := range forbiddenErrorList {
+++             switch u := err.(type) {
+++             case types.ForbiddenError:
+++                     return
+++             default:
+++                     t.Fatalf("Failed to detect err %v is of type ForbiddenError. Got type: %T", err, u)
+++             }
+++     }
+++
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..466143baeedd5bd7f762cc51eb830b51b44282c4
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,127 @@@@
+++package etchosts
+++
+++import (
+++     "bytes"
+++     "fmt"
+++     "io"
+++     "io/ioutil"
+++     "os"
+++     "regexp"
+++)
+++
+++// Record Structure for a single host record
+++type Record struct {
+++     Hosts string
+++     IP    string
+++}
+++
+++// WriteTo writes record to file and returns bytes written or error
+++func (r Record) WriteTo(w io.Writer) (int64, error) {
+++     n, err := fmt.Fprintf(w, "%s\t%s\n", r.IP, r.Hosts)
+++     return int64(n), err
+++}
+++
+++// Default hosts config records slice
+++var defaultContent = []Record{
+++     {Hosts: "localhost", IP: "127.0.0.1"},
+++     {Hosts: "localhost ip6-localhost ip6-loopback", IP: "::1"},
+++     {Hosts: "ip6-localnet", IP: "fe00::0"},
+++     {Hosts: "ip6-mcastprefix", IP: "ff00::0"},
+++     {Hosts: "ip6-allnodes", IP: "ff02::1"},
+++     {Hosts: "ip6-allrouters", IP: "ff02::2"},
+++}
+++
+++// Build function
+++// path is path to host file string required
+++// IP, hostname, and domainname set main record leave empty for no master record
+++// extraContent is an array of extra host records.
+++func Build(path, IP, hostname, domainname string, extraContent []Record) error {
+++     content := bytes.NewBuffer(nil)
+++     if IP != "" {
+++             //set main record
+++             var mainRec Record
+++             mainRec.IP = IP
+++             if domainname != "" {
+++                     mainRec.Hosts = fmt.Sprintf("%s.%s %s", hostname, domainname, hostname)
+++             } else {
+++                     mainRec.Hosts = hostname
+++             }
+++             if _, err := mainRec.WriteTo(content); err != nil {
+++                     return err
+++             }
+++     }
+++     // Write defaultContent slice to buffer
+++     for _, r := range defaultContent {
+++             if _, err := r.WriteTo(content); err != nil {
+++                     return err
+++             }
+++     }
+++     // Write extra content from function arguments
+++     for _, r := range extraContent {
+++             if _, err := r.WriteTo(content); err != nil {
+++                     return err
+++             }
+++     }
+++
+++     return ioutil.WriteFile(path, content.Bytes(), 0644)
+++}
+++
+++// Add adds an arbitrary number of Records to an already existing /etc/hosts file
+++func Add(path string, recs []Record) error {
+++     if len(recs) == 0 {
+++             return nil
+++     }
+++
+++     f, err := os.Open(path)
+++     if err != nil {
+++             return err
+++     }
+++
+++     content := bytes.NewBuffer(nil)
+++
+++     _, err = content.ReadFrom(f)
+++     if err != nil {
+++             return err
+++     }
+++
+++     for _, r := range recs {
+++             if _, err := r.WriteTo(content); err != nil {
+++                     return err
+++             }
+++     }
+++
+++     return ioutil.WriteFile(path, content.Bytes(), 0644)
+++}
+++
+++// Delete deletes an arbitrary number of Records already existing in /etc/hosts file
+++func Delete(path string, recs []Record) error {
+++     if len(recs) == 0 {
+++             return nil
+++     }
+++
+++     old, err := ioutil.ReadFile(path)
+++     if err != nil {
+++             return err
+++     }
+++
+++     regexpStr := fmt.Sprintf("\\S*\\t%s\\n", regexp.QuoteMeta(recs[0].Hosts))
+++     for _, r := range recs[1:] {
+++             regexpStr = regexpStr + "|" + fmt.Sprintf("\\S*\\t%s\\n", regexp.QuoteMeta(r.Hosts))
+++     }
+++
+++     var re = regexp.MustCompile(regexpStr)
+++     return ioutil.WriteFile(path, re.ReplaceAll(old, []byte("")), 0644)
+++}
+++
+++// Update all IP addresses where hostname matches.
+++// path is path to host file
+++// IP is new IP address
+++// hostname is hostname to search for to replace IP
+++func Update(path, IP, hostname string) error {
+++     old, err := ioutil.ReadFile(path)
+++     if err != nil {
+++             return err
+++     }
+++     var re = regexp.MustCompile(fmt.Sprintf("(\\S*)(\\t%s)", regexp.QuoteMeta(hostname)))
+++     return ioutil.WriteFile(path, re.ReplaceAll(old, []byte(IP+"$2")), 0644)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..6e6e8ad3a10efd967dc5b86e5cdf1f2aba259fa1
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,249 @@@@
+++package etchosts
+++
+++import (
+++     "bytes"
+++     "io/ioutil"
+++     "os"
+++     "testing"
+++
+++     _ "github.com/docker/libnetwork/netutils"
+++)
+++
+++func TestBuildDefault(t *testing.T) {
+++     file, err := ioutil.TempFile("", "")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer os.Remove(file.Name())
+++
+++     // check that /etc/hosts has consistent ordering
+++     for i := 0; i <= 5; i++ {
+++             err = Build(file.Name(), "", "", "", nil)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++
+++             content, err := ioutil.ReadFile(file.Name())
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++             expected := "127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\nfe00::0\tip6-localnet\nff00::0\tip6-mcastprefix\nff02::1\tip6-allnodes\nff02::2\tip6-allrouters\n"
+++
+++             if expected != string(content) {
+++                     t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+++             }
+++     }
+++}
+++
+++func TestBuildHostnameDomainname(t *testing.T) {
+++     file, err := ioutil.TempFile("", "")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer os.Remove(file.Name())
+++
+++     err = Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     content, err := ioutil.ReadFile(file.Name())
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
+++             t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+++     }
+++}
+++
+++func TestBuildHostname(t *testing.T) {
+++     file, err := ioutil.TempFile("", "")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer os.Remove(file.Name())
+++
+++     err = Build(file.Name(), "10.11.12.13", "testhostname", "", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     content, err := ioutil.ReadFile(file.Name())
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if expected := "10.11.12.13\ttesthostname\n"; !bytes.Contains(content, []byte(expected)) {
+++             t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+++     }
+++}
+++
+++func TestBuildNoIP(t *testing.T) {
+++     file, err := ioutil.TempFile("", "")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer os.Remove(file.Name())
+++
+++     err = Build(file.Name(), "", "testhostname", "", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     content, err := ioutil.ReadFile(file.Name())
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if expected := ""; !bytes.Contains(content, []byte(expected)) {
+++             t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+++     }
+++}
+++
+++func TestUpdate(t *testing.T) {
+++     file, err := ioutil.TempFile("", "")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer os.Remove(file.Name())
+++
+++     if err := Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     content, err := ioutil.ReadFile(file.Name())
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
+++             t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+++     }
+++
+++     if err := Update(file.Name(), "1.1.1.1", "testhostname"); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     content, err = ioutil.ReadFile(file.Name())
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if expected := "1.1.1.1\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
+++             t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+++     }
+++}
+++
+++func TestAddEmpty(t *testing.T) {
+++     file, err := ioutil.TempFile("", "")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer os.Remove(file.Name())
+++
+++     err = Build(file.Name(), "", "", "", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := Add(file.Name(), []Record{}); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestAdd(t *testing.T) {
+++     file, err := ioutil.TempFile("", "")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer os.Remove(file.Name())
+++
+++     err = Build(file.Name(), "", "", "", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := Add(file.Name(), []Record{
+++             Record{
+++                     Hosts: "testhostname",
+++                     IP:    "2.2.2.2",
+++             },
+++     }); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     content, err := ioutil.ReadFile(file.Name())
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if expected := "2.2.2.2\ttesthostname\n"; !bytes.Contains(content, []byte(expected)) {
+++             t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+++     }
+++}
+++
+++func TestDeleteEmpty(t *testing.T) {
+++     file, err := ioutil.TempFile("", "")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer os.Remove(file.Name())
+++
+++     err = Build(file.Name(), "", "", "", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := Delete(file.Name(), []Record{}); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestDelete(t *testing.T) {
+++     file, err := ioutil.TempFile("", "")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer os.Remove(file.Name())
+++
+++     err = Build(file.Name(), "", "", "", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := Add(file.Name(), []Record{
+++             Record{
+++                     Hosts: "testhostname1",
+++                     IP:    "1.1.1.1",
+++             },
+++             Record{
+++                     Hosts: "testhostname2",
+++                     IP:    "2.2.2.2",
+++             },
+++     }); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := Delete(file.Name(), []Record{
+++             Record{
+++                     Hosts: "testhostname1",
+++                     IP:    "1.1.1.1",
+++             },
+++     }); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     content, err := ioutil.ReadFile(file.Name())
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if expected := "2.2.2.2\ttesthostname2\n"; !bytes.Contains(content, []byte(expected)) {
+++             t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+++     }
+++
+++     if expected := "1.1.1.1\ttesthostname1\n"; bytes.Contains(content, []byte(expected)) {
+++             t.Fatalf("Did not expect to find '%s' got '%s'", expected, content)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..aa39baa834609afd68422e974df4026998a87848
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,154 @@@@
+++// +build libnetwork_discovery
+++
+++package hostdiscovery
+++
+++import (
+++     "errors"
+++     "fmt"
+++     "net"
+++     "sync"
+++     "time"
+++
+++     log "github.com/Sirupsen/logrus"
+++
+++     mapset "github.com/deckarep/golang-set"
+++     "github.com/docker/libnetwork/config"
+++     "github.com/docker/swarm/discovery"
+++     // Anonymous import will be removed after we upgrade to latest swarm
+++     _ "github.com/docker/swarm/discovery/file"
+++     // Anonymous import will be removed after we upgrade to latest swarm
+++     _ "github.com/docker/swarm/discovery/kv"
+++     // Anonymous import will be removed after we upgrade to latest swarm
+++     _ "github.com/docker/swarm/discovery/nodes"
+++     // Anonymous import will be removed after we upgrade to latest swarm
+++     _ "github.com/docker/swarm/discovery/token"
+++)
+++
+++const defaultHeartbeat = time.Duration(10) * time.Second
+++const TTLFactor = 3
+++
+++type hostDiscovery struct {
+++     discovery discovery.Discovery
+++     nodes     mapset.Set
+++     stopChan  chan struct{}
+++     sync.Mutex
+++}
+++
+++// NewHostDiscovery function creates a host discovery object
+++func NewHostDiscovery() HostDiscovery {
+++     return &hostDiscovery{nodes: mapset.NewSet(), stopChan: make(chan struct{})}
+++}
+++
+++func (h *hostDiscovery) StartDiscovery(cfg *config.ClusterCfg, joinCallback JoinCallback, leaveCallback LeaveCallback) error {
+++     if cfg == nil {
+++             return fmt.Errorf("discovery requires a valid configuration")
+++     }
+++
+++     hb := time.Duration(cfg.Heartbeat) * time.Second
+++     if hb == 0 {
+++             hb = defaultHeartbeat
+++     }
+++     d, err := discovery.New(cfg.Discovery, hb, TTLFactor*hb)
+++     if err != nil {
+++             return err
+++     }
+++
+++     if ip := net.ParseIP(cfg.Address); ip == nil {
+++             return errors.New("address config should be either ipv4 or ipv6 address")
+++     }
+++
+++     if err := d.Register(cfg.Address + ":0"); err != nil {
+++             return err
+++     }
+++
+++     h.Lock()
+++     h.discovery = d
+++     h.Unlock()
+++
+++     discoveryCh, errCh := d.Watch(h.stopChan)
+++     go h.monitorDiscovery(discoveryCh, errCh, joinCallback, leaveCallback)
+++     go h.sustainHeartbeat(d, hb, cfg)
+++     return nil
+++}
+++
+++func (h *hostDiscovery) monitorDiscovery(ch <-chan discovery.Entries, errCh <-chan error, joinCallback JoinCallback, leaveCallback LeaveCallback) {
+++     for {
+++             select {
+++             case entries := <-ch:
+++                     h.processCallback(entries, joinCallback, leaveCallback)
+++             case err := <-errCh:
+++                     log.Errorf("discovery error: %v", err)
+++             case <-h.stopChan:
+++                     return
+++             }
+++     }
+++}
+++
+++func (h *hostDiscovery) StopDiscovery() error {
+++     h.Lock()
+++     stopChan := h.stopChan
+++     h.discovery = nil
+++     h.Unlock()
+++
+++     close(stopChan)
+++     return nil
+++}
+++
+++func (h *hostDiscovery) sustainHeartbeat(d discovery.Discovery, hb time.Duration, config *config.ClusterCfg) {
+++     for {
+++             select {
+++             case <-h.stopChan:
+++                     return
+++             case <-time.After(hb):
+++                     if err := d.Register(config.Address + ":0"); err != nil {
+++                             log.Warn(err)
+++                     }
+++             }
+++     }
+++}
+++
+++func (h *hostDiscovery) processCallback(entries discovery.Entries, joinCallback JoinCallback, leaveCallback LeaveCallback) {
+++     updated := hosts(entries)
+++     h.Lock()
+++     existing := h.nodes
+++     added, removed := diff(existing, updated)
+++     h.nodes = updated
+++     h.Unlock()
+++
+++     if len(added) > 0 {
+++             joinCallback(added)
+++     }
+++     if len(removed) > 0 {
+++             leaveCallback(removed)
+++     }
+++}
+++
+++func diff(existing mapset.Set, updated mapset.Set) (added []net.IP, removed []net.IP) {
+++     addSlice := updated.Difference(existing).ToSlice()
+++     removeSlice := existing.Difference(updated).ToSlice()
+++     for _, ip := range addSlice {
+++             added = append(added, net.ParseIP(ip.(string)))
+++     }
+++     for _, ip := range removeSlice {
+++             removed = append(removed, net.ParseIP(ip.(string)))
+++     }
+++     return
+++}
+++
+++func (h *hostDiscovery) Fetch() ([]net.IP, error) {
+++     h.Lock()
+++     defer h.Unlock()
+++     ips := []net.IP{}
+++     for _, ipstr := range h.nodes.ToSlice() {
+++             ips = append(ips, net.ParseIP(ipstr.(string)))
+++     }
+++     return ips, nil
+++}
+++
+++func hosts(entries discovery.Entries) mapset.Set {
+++     hosts := mapset.NewSet()
+++     for _, entry := range entries {
+++             hosts.Add(entry.Host)
+++     }
+++     return hosts
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..09394e09bcb6047799a22ab3c4386b68ea6a90c4
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,23 @@@@
+++package hostdiscovery
+++
+++import (
+++     "net"
+++
+++     "github.com/docker/libnetwork/config"
+++)
+++
+++// JoinCallback provides a callback event for new node joining the cluster
+++type JoinCallback func(entries []net.IP)
+++
+++// LeaveCallback provides a callback event for node leaving the cluster
+++type LeaveCallback func(entries []net.IP)
+++
+++// HostDiscovery primary interface
+++type HostDiscovery interface {
+++     // StartDiscovery initiates the discovery process and provides appropriate callbacks
+++     StartDiscovery(*config.ClusterCfg, JoinCallback, LeaveCallback) error
+++     // StopDiscovery stops the discovery perocess
+++     StopDiscovery() error
+++     // Fetch returns a list of host IPs that are currently discovered
+++     Fetch() ([]net.IP, error)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..2dc67ccb0f4cec384d1cbfe8b97bb8b47fa4a7c4
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,28 @@@@
+++// +build !libnetwork_discovery
+++
+++package hostdiscovery
+++
+++import (
+++     "net"
+++
+++     "github.com/docker/libnetwork/config"
+++)
+++
+++type hostDiscovery struct{}
+++
+++// NewHostDiscovery function creates a host discovery object
+++func NewHostDiscovery() HostDiscovery {
+++     return &hostDiscovery{}
+++}
+++
+++func (h *hostDiscovery) StartDiscovery(cfg *config.ClusterCfg, joinCallback JoinCallback, leaveCallback LeaveCallback) error {
+++     return nil
+++}
+++
+++func (h *hostDiscovery) StopDiscovery() error {
+++     return nil
+++}
+++
+++func (h *hostDiscovery) Fetch() ([]net.IP, error) {
+++     return []net.IP{}, nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1b48d1594c07025ce65545c3264a0a83ba9e8704
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,142 @@@@
+++// +build libnetwork_discovery
+++
+++package hostdiscovery
+++
+++import (
+++     "net"
+++     "testing"
+++     "time"
+++
+++     mapset "github.com/deckarep/golang-set"
+++     _ "github.com/docker/libnetwork/netutils"
+++
+++     "github.com/docker/libnetwork/config"
+++     "github.com/docker/swarm/discovery"
+++)
+++
+++func TestDiscovery(t *testing.T) {
+++     _, err := net.DialTimeout("tcp", "discovery-stage.hub.docker.com:80", 10*time.Second)
+++     if err != nil {
+++             t.Skip("Skipping Discovery test which need connectivity to discovery-stage.hub.docker.com")
+++     }
+++
+++     hd := NewHostDiscovery()
+++     config, err := config.ParseConfig("libnetwork.toml")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = hd.StartDiscovery(&config.Cluster, func(hosts []net.IP) {}, func(hosts []net.IP) {})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     time.Sleep(time.Duration(config.Cluster.Heartbeat*2) * time.Second)
+++     hosts, err := hd.Fetch()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     found := false
+++     for _, ip := range hosts {
+++             if ip.Equal(net.ParseIP(config.Cluster.Address)) {
+++                     found = true
+++             }
+++     }
+++     if !found {
+++             t.Fatalf("Expecting hosts. But none discovered ")
+++     }
+++     err = hd.StopDiscovery()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestBadDiscovery(t *testing.T) {
+++     _, err := net.DialTimeout("tcp", "discovery-stage.hub.docker.com:80", 10*time.Second)
+++     if err != nil {
+++             t.Skip("Skipping Discovery test which need connectivity to discovery-stage.hub.docker.com")
+++     }
+++
+++     hd := NewHostDiscovery()
+++     cfg := &config.Config{}
+++     cfg.Cluster.Discovery = ""
+++     err = hd.StartDiscovery(&cfg.Cluster, func(hosts []net.IP) {}, func(hosts []net.IP) {})
+++     if err == nil {
+++             t.Fatal("Invalid discovery configuration must fail")
+++     }
+++     cfg, err = config.ParseConfig("libnetwork.toml")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     cfg.Cluster.Address = "invalid"
+++     err = hd.StartDiscovery(&cfg.Cluster, func(hosts []net.IP) {}, func(hosts []net.IP) {})
+++     if err == nil {
+++             t.Fatal("Invalid discovery address configuration must fail")
+++     }
+++}
+++
+++func TestDiff(t *testing.T) {
+++     existing := mapset.NewSetFromSlice([]interface{}{"1.1.1.1", "2.2.2.2"})
+++     addedIP := "3.3.3.3"
+++     updated := existing.Clone()
+++     updated.Add(addedIP)
+++
+++     added, removed := diff(existing, updated)
+++     if len(added) != 1 {
+++             t.Fatalf("Diff failed for an Add update. Expecting 1 element, but got %d elements", len(added))
+++     }
+++     if added[0].String() != addedIP {
+++             t.Fatalf("Expecting : %v, Got : %v", addedIP, added[0])
+++     }
+++     if len(removed) > 0 {
+++             t.Fatalf("Diff failed for remove use-case. Expecting 0 element, but got %d elements", len(removed))
+++     }
+++
+++     updated = mapset.NewSetFromSlice([]interface{}{addedIP})
+++     added, removed = diff(existing, updated)
+++     if len(removed) != 2 {
+++             t.Fatalf("Diff failed for an remove update. Expecting 2 element, but got %d elements", len(removed))
+++     }
+++     if len(added) != 1 {
+++             t.Fatalf("Diff failed for add use-case. Expecting 1 element, but got %d elements", len(added))
+++     }
+++}
+++
+++func TestAddedCallback(t *testing.T) {
+++     hd := hostDiscovery{}
+++     hd.nodes = mapset.NewSetFromSlice([]interface{}{"1.1.1.1"})
+++     update := []*discovery.Entry{&discovery.Entry{Host: "1.1.1.1", Port: "0"}, &discovery.Entry{Host: "2.2.2.2", Port: "0"}}
+++
+++     added := false
+++     removed := false
+++     hd.processCallback(update, func(hosts []net.IP) { added = true }, func(hosts []net.IP) { removed = true })
+++     if !added {
+++             t.Fatalf("Expecting a Added callback notification. But none received")
+++     }
+++}
+++
+++func TestRemovedCallback(t *testing.T) {
+++     hd := hostDiscovery{}
+++     hd.nodes = mapset.NewSetFromSlice([]interface{}{"1.1.1.1", "2.2.2.2"})
+++     update := []*discovery.Entry{&discovery.Entry{Host: "1.1.1.1", Port: "0"}}
+++
+++     added := false
+++     removed := false
+++     hd.processCallback(update, func(hosts []net.IP) { added = true }, func(hosts []net.IP) { removed = true })
+++     if !removed {
+++             t.Fatalf("Expecting a Removed callback notification. But none received")
+++     }
+++}
+++
+++func TestNoCallback(t *testing.T) {
+++     hd := hostDiscovery{}
+++     hd.nodes = mapset.NewSetFromSlice([]interface{}{"1.1.1.1", "2.2.2.2"})
+++     update := []*discovery.Entry{&discovery.Entry{Host: "1.1.1.1", Port: "0"}, &discovery.Entry{Host: "2.2.2.2", Port: "0"}}
+++
+++     added := false
+++     removed := false
+++     hd.processCallback(update, func(hosts []net.IP) { added = true }, func(hosts []net.IP) { removed = true })
+++     if added || removed {
+++             t.Fatalf("Not expecting any callback notification. But received a callback")
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b8c68541036bd99abcabedf357ad7f3c31ccf12d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,6 @@@@
+++title = "LibNetwork Configuration file"
+++
+++[cluster]
+++  discovery = "token://08469efb104bce980931ed24c8eb03a2"
+++  Address = "1.1.1.1"
+++  Heartbeat = 3
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..2321d77ea40317a3960bfefcc5f4b682ad90ebcd
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,94 @@@@
+++// Package idm manages resevation/release of numerical ids from a configured set of contiguos ids
+++package idm
+++
+++import (
+++     "fmt"
+++
+++     "github.com/docker/libnetwork/bitseq"
+++     "github.com/docker/libnetwork/datastore"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++// Idm manages the reservation/release of numerical ids from a contiguos set
+++type Idm struct {
+++     start  uint32
+++     end    uint32
+++     handle *bitseq.Handle
+++}
+++
+++// New returns an instance of id manager for a set of [start-end] numerical ids
+++func New(ds datastore.DataStore, id string, start, end uint32) (*Idm, error) {
+++     if id == "" {
+++             return nil, fmt.Errorf("Invalid id")
+++     }
+++     if end <= start {
+++             return nil, fmt.Errorf("Invalid set range: [%d, %d]", start, end)
+++     }
+++
+++     h, err := bitseq.NewHandle("idm", ds, id, uint32(1+end-start))
+++     if err != nil {
+++             return nil, fmt.Errorf("failed to initialize bit sequence handler: %s", err.Error())
+++     }
+++
+++     return &Idm{start: start, end: end, handle: h}, nil
+++}
+++
+++// GetID returns the first available id in the set
+++func (i *Idm) GetID() (uint32, error) {
+++     if i.handle == nil {
+++             return 0, fmt.Errorf("ID set is not initialized")
+++     }
+++
+++     for {
+++             bytePos, bitPos, err := i.handle.GetFirstAvailable()
+++             if err != nil {
+++                     return 0, fmt.Errorf("no available ids")
+++             }
+++             id := i.start + uint32(bitPos+bytePos*8)
+++
+++             // for sets which length is non multiple of 32 this check is needed
+++             if i.end < id {
+++                     return 0, fmt.Errorf("no available ids")
+++             }
+++
+++             if err := i.handle.PushReservation(bytePos, bitPos, false); err != nil {
+++                     if _, ok := err.(types.RetryError); !ok {
+++                             return 0, fmt.Errorf("internal failure while reserving the id: %s", err.Error())
+++                     }
+++                     continue
+++             }
+++
+++             return id, nil
+++     }
+++}
+++
+++// GetSpecificID tries to reserve the specified id
+++func (i *Idm) GetSpecificID(id uint32) error {
+++     if i.handle == nil {
+++             return fmt.Errorf("ID set is not initialized")
+++     }
+++
+++     if id < i.start || id > i.end {
+++             return fmt.Errorf("Requested id does not belong to the set")
+++     }
+++
+++     for {
+++             bytePos, bitPos, err := i.handle.CheckIfAvailable(int(id - i.start))
+++             if err != nil {
+++                     return fmt.Errorf("requested id is not available")
+++             }
+++             if err := i.handle.PushReservation(bytePos, bitPos, false); err != nil {
+++                     if _, ok := err.(types.RetryError); !ok {
+++                             return fmt.Errorf("internal failure while reserving the id: %s", err.Error())
+++                     }
+++                     continue
+++             }
+++             return nil
+++     }
+++}
+++
+++// Release releases the specified id
+++func (i *Idm) Release(id uint32) {
+++     ordinal := id - i.start
+++     i.handle.PushReservation(int(ordinal/8), int(ordinal%8), true)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1004cb8bb5e3a4293e12408f04ef3209f2049a4c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,108 @@@@
+++package idm
+++
+++import (
+++     "testing"
+++)
+++
+++func TestNew(t *testing.T) {
+++     _, err := New(nil, "", 0, 1)
+++     if err == nil {
+++             t.Fatalf("Expected failure, but succeeded")
+++     }
+++
+++     _, err = New(nil, "myset", 1<<10, 0)
+++     if err == nil {
+++             t.Fatalf("Expected failure, but succeeded")
+++     }
+++
+++     i, err := New(nil, "myset", 0, 10)
+++     if err != nil {
+++             t.Fatalf("Unexpected failure: %v", err)
+++     }
+++     if i.handle == nil {
+++             t.Fatalf("set is not initialized")
+++     }
+++     if i.start != 0 {
+++             t.Fatalf("unexpected start")
+++     }
+++     if i.end != 10 {
+++             t.Fatalf("unexpected end")
+++     }
+++}
+++
+++func TestAllocate(t *testing.T) {
+++     i, err := New(nil, "myids", 50, 52)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err = i.GetSpecificID(49); err == nil {
+++             t.Fatalf("Expected failure but succeeded")
+++     }
+++
+++     if err = i.GetSpecificID(53); err == nil {
+++             t.Fatalf("Expected failure but succeeded")
+++     }
+++
+++     o, err := i.GetID()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if o != 50 {
+++             t.Fatalf("Unexpected first id returned: %d", o)
+++     }
+++
+++     err = i.GetSpecificID(50)
+++     if err == nil {
+++             t.Fatal(err)
+++     }
+++
+++     o, err = i.GetID()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if o != 51 {
+++             t.Fatalf("Unexpected id returned: %d", o)
+++     }
+++
+++     o, err = i.GetID()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if o != 52 {
+++             t.Fatalf("Unexpected id returned: %d", o)
+++     }
+++
+++     o, err = i.GetID()
+++     if err == nil {
+++             t.Fatalf("Expected failure but succeeded: %d", o)
+++     }
+++
+++     i.Release(50)
+++
+++     o, err = i.GetID()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if o != 50 {
+++             t.Fatalf("Unexpected id returned")
+++     }
+++
+++     i.Release(52)
+++     err = i.GetSpecificID(52)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestUninitialized(t *testing.T) {
+++     i := &Idm{}
+++
+++     if _, err := i.GetID(); err == nil {
+++             t.Fatalf("Expected failure but succeeded")
+++     }
+++
+++     if err := i.GetSpecificID(44); err == nil {
+++             t.Fatalf("Expected failure but succeeded")
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..06bc051c556cca72f60f778b77f54e19c02986f4
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,175 @@@@
+++// Package ipallocator defines the default IP allocator. It will move out of libnetwork as an external IPAM plugin.
+++// This has been imported unchanged from Docker, besides additon of registration logic
+++package ipallocator
+++
+++import (
+++     "errors"
+++     "math/big"
+++     "net"
+++     "sync"
+++
+++     "github.com/Sirupsen/logrus"
+++     "github.com/docker/libnetwork/netutils"
+++)
+++
+++// allocatedMap is thread-unsafe set of allocated IP
+++type allocatedMap struct {
+++     p     map[string]struct{}
+++     last  *big.Int
+++     begin *big.Int
+++     end   *big.Int
+++}
+++
+++func newAllocatedMap(network *net.IPNet) *allocatedMap {
+++     firstIP, lastIP := netutils.NetworkRange(network)
+++     begin := big.NewInt(0).Add(ipToBigInt(firstIP), big.NewInt(1))
+++     end := big.NewInt(0).Sub(ipToBigInt(lastIP), big.NewInt(1))
+++
+++     return &allocatedMap{
+++             p:     make(map[string]struct{}),
+++             begin: begin,
+++             end:   end,
+++             last:  big.NewInt(0).Sub(begin, big.NewInt(1)), // so first allocated will be begin
+++     }
+++}
+++
+++type networkSet map[string]*allocatedMap
+++
+++var (
+++     // ErrNoAvailableIPs preformatted error
+++     ErrNoAvailableIPs = errors.New("no available ip addresses on network")
+++     // ErrIPAlreadyAllocated preformatted error
+++     ErrIPAlreadyAllocated = errors.New("ip already allocated")
+++     // ErrIPOutOfRange preformatted error
+++     ErrIPOutOfRange = errors.New("requested ip is out of range")
+++     // ErrNetworkAlreadyRegistered preformatted error
+++     ErrNetworkAlreadyRegistered = errors.New("network already registered")
+++     // ErrBadSubnet preformatted error
+++     ErrBadSubnet = errors.New("network does not contain specified subnet")
+++)
+++
+++// IPAllocator manages the ipam
+++type IPAllocator struct {
+++     allocatedIPs networkSet
+++     mutex        sync.Mutex
+++}
+++
+++// New returns a new instance of IPAllocator
+++func New() *IPAllocator {
+++     return &IPAllocator{networkSet{}, sync.Mutex{}}
+++}
+++
+++// RegisterSubnet registers network in global allocator with bounds
+++// defined by subnet. If you want to use network range you must call
+++// this method before first RequestIP, otherwise full network range will be used
+++func (a *IPAllocator) RegisterSubnet(network *net.IPNet, subnet *net.IPNet) error {
+++     a.mutex.Lock()
+++     defer a.mutex.Unlock()
+++
+++     nw := &net.IPNet{IP: network.IP.Mask(network.Mask), Mask: network.Mask}
+++     key := nw.String()
+++     if _, ok := a.allocatedIPs[key]; ok {
+++             return ErrNetworkAlreadyRegistered
+++     }
+++
+++     // Check that subnet is within network
+++     beginIP, endIP := netutils.NetworkRange(subnet)
+++     if !(network.Contains(beginIP) && network.Contains(endIP)) {
+++             return ErrBadSubnet
+++     }
+++
+++     n := newAllocatedMap(subnet)
+++     a.allocatedIPs[key] = n
+++     return nil
+++}
+++
+++// RequestIP requests an available ip from the given network.  It
+++// will return the next available ip if the ip provided is nil.  If the
+++// ip provided is not nil it will validate that the provided ip is available
+++// for use or return an error
+++func (a *IPAllocator) RequestIP(network *net.IPNet, ip net.IP) (net.IP, error) {
+++     a.mutex.Lock()
+++     defer a.mutex.Unlock()
+++
+++     nw := &net.IPNet{IP: network.IP.Mask(network.Mask), Mask: network.Mask}
+++     key := nw.String()
+++     allocated, ok := a.allocatedIPs[key]
+++     if !ok {
+++             allocated = newAllocatedMap(nw)
+++             a.allocatedIPs[key] = allocated
+++     }
+++
+++     if ip == nil {
+++             return allocated.getNextIP()
+++     }
+++     return allocated.checkIP(ip)
+++}
+++
+++// ReleaseIP adds the provided ip back into the pool of
+++// available ips to be returned for use.
+++func (a *IPAllocator) ReleaseIP(network *net.IPNet, ip net.IP) error {
+++     a.mutex.Lock()
+++     defer a.mutex.Unlock()
+++
+++     nw := &net.IPNet{IP: network.IP.Mask(network.Mask), Mask: network.Mask}
+++     if allocated, exists := a.allocatedIPs[nw.String()]; exists {
+++             delete(allocated.p, ip.String())
+++     }
+++     return nil
+++}
+++
+++func (allocated *allocatedMap) checkIP(ip net.IP) (net.IP, error) {
+++     if _, ok := allocated.p[ip.String()]; ok {
+++             return nil, ErrIPAlreadyAllocated
+++     }
+++
+++     pos := ipToBigInt(ip)
+++     // Verify that the IP address is within our network range.
+++     if pos.Cmp(allocated.begin) == -1 || pos.Cmp(allocated.end) == 1 {
+++             return nil, ErrIPOutOfRange
+++     }
+++
+++     // Register the IP.
+++     allocated.p[ip.String()] = struct{}{}
+++
+++     return ip, nil
+++}
+++
+++// return an available ip if one is currently available.  If not,
+++// return the next available ip for the network
+++func (allocated *allocatedMap) getNextIP() (net.IP, error) {
+++     pos := big.NewInt(0).Set(allocated.last)
+++     allRange := big.NewInt(0).Sub(allocated.end, allocated.begin)
+++     for i := big.NewInt(0); i.Cmp(allRange) <= 0; i.Add(i, big.NewInt(1)) {
+++             pos.Add(pos, big.NewInt(1))
+++             if pos.Cmp(allocated.end) == 1 {
+++                     pos.Set(allocated.begin)
+++             }
+++             if _, ok := allocated.p[bigIntToIP(pos).String()]; ok {
+++                     continue
+++             }
+++             allocated.p[bigIntToIP(pos).String()] = struct{}{}
+++             allocated.last.Set(pos)
+++             return bigIntToIP(pos), nil
+++     }
+++     return nil, ErrNoAvailableIPs
+++}
+++
+++// Converts a 4 bytes IP into a 128 bit integer
+++func ipToBigInt(ip net.IP) *big.Int {
+++     x := big.NewInt(0)
+++     if ip4 := ip.To4(); ip4 != nil {
+++             return x.SetBytes(ip4)
+++     }
+++     if ip6 := ip.To16(); ip6 != nil {
+++             return x.SetBytes(ip6)
+++     }
+++
+++     logrus.Errorf("ipToBigInt: Wrong IP length! %s", ip)
+++     return nil
+++}
+++
+++// Converts 128 bit integer into a 4 bytes IP address
+++func bigIntToIP(v *big.Int) net.IP {
+++     return net.IP(v.Bytes())
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..fffe6e3389c6ffe4185b5c9c8a68c8fb1ee116c6
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,690 @@@@
+++package ipallocator
+++
+++import (
+++     "fmt"
+++     "math/big"
+++     "net"
+++     "testing"
+++)
+++
+++func TestConversion(t *testing.T) {
+++     ip := net.ParseIP("127.0.0.1")
+++     i := ipToBigInt(ip)
+++     if i.Cmp(big.NewInt(0x7f000001)) != 0 {
+++             t.Fatal("incorrect conversion")
+++     }
+++     conv := bigIntToIP(i)
+++     if !ip.Equal(conv) {
+++             t.Error(conv.String())
+++     }
+++}
+++
+++func TestConversionIPv6(t *testing.T) {
+++     ip := net.ParseIP("2a00:1450::1")
+++     ip2 := net.ParseIP("2a00:1450::2")
+++     ip3 := net.ParseIP("2a00:1450::1:1")
+++     i := ipToBigInt(ip)
+++     val, success := big.NewInt(0).SetString("2a001450000000000000000000000001", 16)
+++     if !success {
+++             t.Fatal("Hex-String to BigInt conversion failed.")
+++     }
+++     if i.Cmp(val) != 0 {
+++             t.Fatal("incorrent conversion")
+++     }
+++
+++     conv := bigIntToIP(i)
+++     conv2 := bigIntToIP(big.NewInt(0).Add(i, big.NewInt(1)))
+++     conv3 := bigIntToIP(big.NewInt(0).Add(i, big.NewInt(0x10000)))
+++
+++     if !ip.Equal(conv) {
+++             t.Error("2a00:1450::1 should be equal to " + conv.String())
+++     }
+++     if !ip2.Equal(conv2) {
+++             t.Error("2a00:1450::2 should be equal to " + conv2.String())
+++     }
+++     if !ip3.Equal(conv3) {
+++             t.Error("2a00:1450::1:1 should be equal to " + conv3.String())
+++     }
+++}
+++
+++func TestRequestNewIps(t *testing.T) {
+++     a := New()
+++
+++     network := &net.IPNet{
+++             IP:   []byte{192, 168, 0, 1},
+++             Mask: []byte{255, 255, 255, 0},
+++     }
+++
+++     var ip net.IP
+++     var err error
+++
+++     for i := 1; i < 10; i++ {
+++             ip, err = a.RequestIP(network, nil)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++
+++             if expected := fmt.Sprintf("192.168.0.%d", i); ip.String() != expected {
+++                     t.Fatalf("Expected ip %s got %s", expected, ip.String())
+++             }
+++     }
+++     value := bigIntToIP(big.NewInt(0).Add(ipToBigInt(ip), big.NewInt(1))).String()
+++     if err := a.ReleaseIP(network, ip); err != nil {
+++             t.Fatal(err)
+++     }
+++     ip, err = a.RequestIP(network, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if ip.String() != value {
+++             t.Fatalf("Expected to receive the next ip %s got %s", value, ip.String())
+++     }
+++}
+++
+++func TestRequestNewIpV6(t *testing.T) {
+++     a := New()
+++
+++     network := &net.IPNet{
+++             IP:   []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+++             Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
+++     }
+++
+++     var ip net.IP
+++     var err error
+++     for i := 1; i < 10; i++ {
+++             ip, err = a.RequestIP(network, nil)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++
+++             if expected := fmt.Sprintf("2a00:1450::%d", i); ip.String() != expected {
+++                     t.Fatalf("Expected ip %s got %s", expected, ip.String())
+++             }
+++     }
+++     value := bigIntToIP(big.NewInt(0).Add(ipToBigInt(ip), big.NewInt(1))).String()
+++     if err := a.ReleaseIP(network, ip); err != nil {
+++             t.Fatal(err)
+++     }
+++     ip, err = a.RequestIP(network, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if ip.String() != value {
+++             t.Fatalf("Expected to receive the next ip %s got %s", value, ip.String())
+++     }
+++}
+++
+++func TestReleaseIp(t *testing.T) {
+++     a := New()
+++
+++     network := &net.IPNet{
+++             IP:   []byte{192, 168, 0, 1},
+++             Mask: []byte{255, 255, 255, 0},
+++     }
+++
+++     ip, err := a.RequestIP(network, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := a.ReleaseIP(network, ip); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestReleaseIpV6(t *testing.T) {
+++     a := New()
+++
+++     network := &net.IPNet{
+++             IP:   []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+++             Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
+++     }
+++
+++     ip, err := a.RequestIP(network, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := a.ReleaseIP(network, ip); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestGetReleasedIp(t *testing.T) {
+++     a := New()
+++     network := &net.IPNet{
+++             IP:   []byte{192, 168, 0, 1},
+++             Mask: []byte{255, 255, 255, 0},
+++     }
+++
+++     ip, err := a.RequestIP(network, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     value := ip.String()
+++     if err := a.ReleaseIP(network, ip); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     for i := 0; i < 253; i++ {
+++             _, err = a.RequestIP(network, nil)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++             err = a.ReleaseIP(network, ip)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++     }
+++
+++     ip, err = a.RequestIP(network, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if ip.String() != value {
+++             t.Fatalf("Expected to receive same ip %s got %s", value, ip.String())
+++     }
+++}
+++
+++func TestGetReleasedIpV6(t *testing.T) {
+++     a := New()
+++
+++     network := &net.IPNet{
+++             IP:   []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+++             Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0},
+++     }
+++
+++     ip, err := a.RequestIP(network, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     value := ip.String()
+++     if err := a.ReleaseIP(network, ip); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     for i := 0; i < 253; i++ {
+++             _, err = a.RequestIP(network, nil)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++             err = a.ReleaseIP(network, ip)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++     }
+++
+++     ip, err = a.RequestIP(network, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if ip.String() != value {
+++             t.Fatalf("Expected to receive same ip %s got %s", value, ip.String())
+++     }
+++}
+++
+++func TestRequestSpecificIp(t *testing.T) {
+++     a := New()
+++
+++     network := &net.IPNet{
+++             IP:   []byte{192, 168, 0, 1},
+++             Mask: []byte{255, 255, 255, 224},
+++     }
+++
+++     ip := net.ParseIP("192.168.0.5")
+++
+++     // Request a "good" IP.
+++     if _, err := a.RequestIP(network, ip); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     // Request the same IP again.
+++     if _, err := a.RequestIP(network, ip); err != ErrIPAlreadyAllocated {
+++             t.Fatalf("Got the same IP twice: %#v", err)
+++     }
+++
+++     // Request an out of range IP.
+++     if _, err := a.RequestIP(network, net.ParseIP("192.168.0.42")); err != ErrIPOutOfRange {
+++             t.Fatalf("Got an out of range IP: %#v", err)
+++     }
+++}
+++
+++func TestRequestSpecificIpV6(t *testing.T) {
+++     a := New()
+++
+++     network := &net.IPNet{
+++             IP:   []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+++             Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
+++     }
+++
+++     ip := net.ParseIP("2a00:1450::5")
+++
+++     // Request a "good" IP.
+++     if _, err := a.RequestIP(network, ip); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     // Request the same IP again.
+++     if _, err := a.RequestIP(network, ip); err != ErrIPAlreadyAllocated {
+++             t.Fatalf("Got the same IP twice: %#v", err)
+++     }
+++
+++     // Request an out of range IP.
+++     if _, err := a.RequestIP(network, net.ParseIP("2a00:1500::1")); err != ErrIPOutOfRange {
+++             t.Fatalf("Got an out of range IP: %#v", err)
+++     }
+++}
+++
+++func TestIPAllocator(t *testing.T) {
+++     a := New()
+++
+++     expectedIPs := []net.IP{
+++             0: net.IPv4(127, 0, 0, 1),
+++             1: net.IPv4(127, 0, 0, 2),
+++             2: net.IPv4(127, 0, 0, 3),
+++             3: net.IPv4(127, 0, 0, 4),
+++             4: net.IPv4(127, 0, 0, 5),
+++             5: net.IPv4(127, 0, 0, 6),
+++     }
+++
+++     gwIP, n, _ := net.ParseCIDR("127.0.0.1/29")
+++
+++     network := &net.IPNet{IP: gwIP, Mask: n.Mask}
+++     // Pool after initialisation (f = free, u = used)
+++     // 1(f) - 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
+++     //  â†‘
+++
+++     // Check that we get 6 IPs, from 127.0.0.1–127.0.0.6, in that
+++     // order.
+++     for i := 0; i < 6; i++ {
+++             ip, err := a.RequestIP(network, nil)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++
+++             assertIPEquals(t, expectedIPs[i], ip)
+++     }
+++     // Before loop begin
+++     // 1(f) - 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
+++     //  â†‘
+++
+++     // After i = 0
+++     // 1(u) - 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
+++     //         â†‘
+++
+++     // After i = 1
+++     // 1(u) - 2(u) - 3(f) - 4(f) - 5(f) - 6(f)
+++     //                â†‘
+++
+++     // After i = 2
+++     // 1(u) - 2(u) - 3(u) - 4(f) - 5(f) - 6(f)
+++     //                       â†‘
+++
+++     // After i = 3
+++     // 1(u) - 2(u) - 3(u) - 4(u) - 5(f) - 6(f)
+++     //                              â†‘
+++
+++     // After i = 4
+++     // 1(u) - 2(u) - 3(u) - 4(u) - 5(u) - 6(f)
+++     //                                     â†‘
+++
+++     // After i = 5
+++     // 1(u) - 2(u) - 3(u) - 4(u) - 5(u) - 6(u)
+++     //  â†‘
+++
+++     // Check that there are no more IPs
+++     ip, err := a.RequestIP(network, nil)
+++     if err == nil {
+++             t.Fatalf("There shouldn't be any IP addresses at this point, got %s\n", ip)
+++     }
+++
+++     // Release some IPs in non-sequential order
+++     if err := a.ReleaseIP(network, expectedIPs[3]); err != nil {
+++             t.Fatal(err)
+++     }
+++     // 1(u) - 2(u) - 3(u) - 4(f) - 5(u) - 6(u)
+++     //                       â†‘
+++
+++     if err := a.ReleaseIP(network, expectedIPs[2]); err != nil {
+++             t.Fatal(err)
+++     }
+++     // 1(u) - 2(u) - 3(f) - 4(f) - 5(u) - 6(u)
+++     //                â†‘
+++
+++     if err := a.ReleaseIP(network, expectedIPs[4]); err != nil {
+++             t.Fatal(err)
+++     }
+++     // 1(u) - 2(u) - 3(f) - 4(f) - 5(f) - 6(u)
+++     //                              â†‘
+++
+++     // Make sure that IPs are reused in sequential order, starting
+++     // with the first released IP
+++     newIPs := make([]net.IP, 3)
+++     for i := 0; i < 3; i++ {
+++             ip, err := a.RequestIP(network, nil)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++
+++             newIPs[i] = ip
+++     }
+++     assertIPEquals(t, expectedIPs[2], newIPs[0])
+++     assertIPEquals(t, expectedIPs[3], newIPs[1])
+++     assertIPEquals(t, expectedIPs[4], newIPs[2])
+++
+++     _, err = a.RequestIP(network, nil)
+++     if err == nil {
+++             t.Fatal("There shouldn't be any IP addresses at this point")
+++     }
+++}
+++
+++func TestAllocateFirstIP(t *testing.T) {
+++     a := New()
+++
+++     network := &net.IPNet{
+++             IP:   []byte{192, 168, 0, 0},
+++             Mask: []byte{255, 255, 255, 0},
+++     }
+++
+++     firstIP := network.IP.To4().Mask(network.Mask)
+++     first := big.NewInt(0).Add(ipToBigInt(firstIP), big.NewInt(1))
+++
+++     ip, err := a.RequestIP(network, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     allocated := ipToBigInt(ip)
+++
+++     if allocated == first {
+++             t.Fatalf("allocated ip should not equal first ip: %d == %d", first, allocated)
+++     }
+++}
+++
+++func TestAllocateAllIps(t *testing.T) {
+++     a := New()
+++
+++     network := &net.IPNet{
+++             IP:   []byte{192, 168, 0, 1},
+++             Mask: []byte{255, 255, 255, 0},
+++     }
+++
+++     var (
+++             current, first net.IP
+++             err            error
+++             isFirst        = true
+++     )
+++
+++     for err == nil {
+++             current, err = a.RequestIP(network, nil)
+++             if isFirst {
+++                     first = current
+++                     isFirst = false
+++             }
+++     }
+++
+++     if err != ErrNoAvailableIPs {
+++             t.Fatal(err)
+++     }
+++
+++     if _, err := a.RequestIP(network, nil); err != ErrNoAvailableIPs {
+++             t.Fatal(err)
+++     }
+++
+++     if err := a.ReleaseIP(network, first); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     again, err := a.RequestIP(network, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     assertIPEquals(t, first, again)
+++
+++     // ensure that alloc.last == alloc.begin won't result in dead loop
+++     if _, err := a.RequestIP(network, nil); err != ErrNoAvailableIPs {
+++             t.Fatal(err)
+++     }
+++
+++     // Test by making alloc.last the only free ip and ensure we get it back
+++     // #1. first of the range, (alloc.last == ipToInt(first) already)
+++     if err := a.ReleaseIP(network, first); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     ret, err := a.RequestIP(network, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     assertIPEquals(t, first, ret)
+++
+++     // #2. last of the range, note that current is the last one
+++     last := net.IPv4(192, 168, 0, 254)
+++     setLastTo(t, a, network, last)
+++
+++     ret, err = a.RequestIP(network, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     assertIPEquals(t, last, ret)
+++
+++     // #3. middle of the range
+++     mid := net.IPv4(192, 168, 0, 7)
+++     setLastTo(t, a, network, mid)
+++
+++     ret, err = a.RequestIP(network, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     assertIPEquals(t, mid, ret)
+++}
+++
+++// make sure the pool is full when calling setLastTo.
+++// we don't cheat here
+++func setLastTo(t *testing.T, a *IPAllocator, network *net.IPNet, ip net.IP) {
+++     if err := a.ReleaseIP(network, ip); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     ret, err := a.RequestIP(network, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     assertIPEquals(t, ip, ret)
+++
+++     if err := a.ReleaseIP(network, ip); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestAllocateDifferentSubnets(t *testing.T) {
+++     a := New()
+++     network1 := &net.IPNet{
+++             IP:   []byte{192, 168, 0, 1},
+++             Mask: []byte{255, 255, 255, 0},
+++     }
+++     network2 := &net.IPNet{
+++             IP:   []byte{127, 0, 0, 1},
+++             Mask: []byte{255, 255, 255, 0},
+++     }
+++     network3 := &net.IPNet{
+++             IP:   []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+++             Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
+++     }
+++     network4 := &net.IPNet{
+++             IP:   []byte{0x2a, 0x00, 0x16, 0x32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+++             Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
+++     }
+++     expectedIPs := []net.IP{
+++             0: net.IPv4(192, 168, 0, 1),
+++             1: net.IPv4(192, 168, 0, 2),
+++             2: net.IPv4(127, 0, 0, 1),
+++             3: net.IPv4(127, 0, 0, 2),
+++             4: net.ParseIP("2a00:1450::1"),
+++             5: net.ParseIP("2a00:1450::2"),
+++             6: net.ParseIP("2a00:1450::3"),
+++             7: net.ParseIP("2a00:1632::1"),
+++             8: net.ParseIP("2a00:1632::2"),
+++     }
+++
+++     ip11, err := a.RequestIP(network1, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     ip12, err := a.RequestIP(network1, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     ip21, err := a.RequestIP(network2, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     ip22, err := a.RequestIP(network2, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     ip31, err := a.RequestIP(network3, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     ip32, err := a.RequestIP(network3, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     ip33, err := a.RequestIP(network3, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     ip41, err := a.RequestIP(network4, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     ip42, err := a.RequestIP(network4, nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     assertIPEquals(t, expectedIPs[0], ip11)
+++     assertIPEquals(t, expectedIPs[1], ip12)
+++     assertIPEquals(t, expectedIPs[2], ip21)
+++     assertIPEquals(t, expectedIPs[3], ip22)
+++     assertIPEquals(t, expectedIPs[4], ip31)
+++     assertIPEquals(t, expectedIPs[5], ip32)
+++     assertIPEquals(t, expectedIPs[6], ip33)
+++     assertIPEquals(t, expectedIPs[7], ip41)
+++     assertIPEquals(t, expectedIPs[8], ip42)
+++}
+++
+++func TestRegisterBadTwice(t *testing.T) {
+++     a := New()
+++     network := &net.IPNet{
+++             IP:   []byte{192, 168, 1, 1},
+++             Mask: []byte{255, 255, 255, 0},
+++     }
+++     subnet := &net.IPNet{
+++             IP:   []byte{192, 168, 1, 8},
+++             Mask: []byte{255, 255, 255, 248},
+++     }
+++
+++     if err := a.RegisterSubnet(network, subnet); err != nil {
+++             t.Fatal(err)
+++     }
+++     subnet = &net.IPNet{
+++             IP:   []byte{192, 168, 1, 16},
+++             Mask: []byte{255, 255, 255, 248},
+++     }
+++     if err := a.RegisterSubnet(network, subnet); err != ErrNetworkAlreadyRegistered {
+++             t.Fatalf("Expecteded ErrNetworkAlreadyRegistered error, got %v", err)
+++     }
+++}
+++
+++func TestRegisterBadRange(t *testing.T) {
+++     a := New()
+++     network := &net.IPNet{
+++             IP:   []byte{192, 168, 1, 1},
+++             Mask: []byte{255, 255, 255, 0},
+++     }
+++     subnet := &net.IPNet{
+++             IP:   []byte{192, 168, 1, 1},
+++             Mask: []byte{255, 255, 0, 0},
+++     }
+++     if err := a.RegisterSubnet(network, subnet); err != ErrBadSubnet {
+++             t.Fatalf("Expected ErrBadSubnet error, got %v", err)
+++     }
+++}
+++
+++func TestAllocateFromRange(t *testing.T) {
+++     a := New()
+++     network := &net.IPNet{
+++             IP:   []byte{192, 168, 0, 1},
+++             Mask: []byte{255, 255, 255, 0},
+++     }
+++     // 192.168.1.9 - 192.168.1.14
+++     subnet := &net.IPNet{
+++             IP:   []byte{192, 168, 0, 8},
+++             Mask: []byte{255, 255, 255, 248},
+++     }
+++
+++     if err := a.RegisterSubnet(network, subnet); err != nil {
+++             t.Fatal(err)
+++     }
+++     expectedIPs := []net.IP{
+++             0: net.IPv4(192, 168, 0, 9),
+++             1: net.IPv4(192, 168, 0, 10),
+++             2: net.IPv4(192, 168, 0, 11),
+++             3: net.IPv4(192, 168, 0, 12),
+++             4: net.IPv4(192, 168, 0, 13),
+++             5: net.IPv4(192, 168, 0, 14),
+++     }
+++     for _, ip := range expectedIPs {
+++             rip, err := a.RequestIP(network, nil)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++             assertIPEquals(t, ip, rip)
+++     }
+++
+++     if _, err := a.RequestIP(network, nil); err != ErrNoAvailableIPs {
+++             t.Fatalf("Expected ErrNoAvailableIPs error, got %v", err)
+++     }
+++     for _, ip := range expectedIPs {
+++             a.ReleaseIP(network, ip)
+++             rip, err := a.RequestIP(network, nil)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++             assertIPEquals(t, ip, rip)
+++     }
+++}
+++
+++func assertIPEquals(t *testing.T, ip1, ip2 net.IP) {
+++     if !ip1.Equal(ip2) {
+++             t.Fatalf("Expected IP %s, got %s", ip1, ip2)
+++     }
+++}
+++
+++func BenchmarkRequestIP(b *testing.B) {
+++     network := &net.IPNet{
+++             IP:   []byte{192, 168, 0, 1},
+++             Mask: []byte{255, 255, 255, 0},
+++     }
+++     b.ResetTimer()
+++
+++     for i := 0; i < b.N; i++ {
+++             a := New()
+++
+++             for j := 0; j < 253; j++ {
+++                     _, err := a.RequestIP(network, nil)
+++                     if err != nil {
+++                             b.Fatal(err)
+++                     }
+++             }
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..5e7734c03f9911aeba1294485cc210485ae6e07b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,634 @@@@
+++package ipam
+++
+++import (
+++     "fmt"
+++     "net"
+++     "strings"
+++     "sync"
+++
+++     log "github.com/Sirupsen/logrus"
+++     "github.com/docker/libkv/store"
+++     "github.com/docker/libnetwork/bitseq"
+++     "github.com/docker/libnetwork/datastore"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++const (
+++     // The biggest configurable host subnets
+++     minNetSize   = 8
+++     minNetSizeV6 = 64
+++     // The effective network size for v6
+++     minNetSizeV6Eff = 96
+++     // The size of the host subnet used internally, it's the most granular sequence addresses
+++     defaultInternalHostSize = 16
+++     // datastore keyes for ipam obkects
+++     dsConfigKey = "ipam-config" // ipam-config/<domain>/<map of subent configs>
+++     dsDataKey   = "ipam-data"   // ipam-data/<domain>/<subnet>/<child-sudbnet>/<bitmask>
+++)
+++
+++// Allocator provides per address space ipv4/ipv6 book keeping
+++type Allocator struct {
+++     // The internal subnets host size
+++     internalHostSize int
+++     // Static subnet information
+++     subnets map[subnetKey]*SubnetInfo
+++     // Allocated addresses in each address space's internal subnet
+++     addresses map[subnetKey]*bitseq.Handle
+++     // Datastore
+++     store    datastore.DataStore
+++     App      string
+++     ID       string
+++     dbIndex  uint64
+++     dbExists bool
+++     sync.Mutex
+++}
+++
+++// NewAllocator returns an instance of libnetwork ipam
+++func NewAllocator(ds datastore.DataStore) (*Allocator, error) {
+++     a := &Allocator{}
+++     a.subnets = make(map[subnetKey]*SubnetInfo)
+++     a.addresses = make(map[subnetKey]*bitseq.Handle)
+++     a.internalHostSize = defaultInternalHostSize
+++     a.store = ds
+++     a.App = "ipam"
+++     a.ID = dsConfigKey
+++
+++     if a.store == nil {
+++             return a, nil
+++     }
+++
+++     // Register for status changes
+++     a.watchForChanges()
+++
+++     // Get the initial subnet configs status from the ds if present.
+++     kvPair, err := a.store.KVStore().Get(datastore.Key(a.Key()...))
+++     if err != nil {
+++             if err != store.ErrKeyNotFound {
+++                     return nil, fmt.Errorf("failed to retrieve the ipam subnet configs from datastore: %v", err)
+++             }
+++             return a, nil
+++     }
+++     a.subnetConfigFromStore(kvPair)
+++
+++     // Now retrieve the list of small subnets
+++     var inserterList []func() error
+++     a.Lock()
+++     for k, v := range a.subnets {
+++             inserterList = append(inserterList,
+++                     func() error {
+++                             subnetList, err := getInternalSubnets(v.Subnet, a.internalHostSize)
+++                             if err != nil {
+++                                     return fmt.Errorf("failed to load address bitmask for configured subnet %s because of %s", v.Subnet.String(), err.Error())
+++                             }
+++                             a.insertAddressMasks(k, subnetList)
+++                             return nil
+++                     })
+++     }
+++     a.Unlock()
+++
+++     // Add the bitmasks, data could come from datastore
+++     for _, f := range inserterList {
+++             if err := f(); err != nil {
+++                     return nil, err
+++             }
+++     }
+++
+++     return a, nil
+++}
+++
+++func (a *Allocator) subnetConfigFromStore(kvPair *store.KVPair) {
+++     a.Lock()
+++     if a.dbIndex < kvPair.LastIndex {
+++             a.subnets = byteArrayToSubnets(kvPair.Value)
+++             a.dbIndex = kvPair.LastIndex
+++             a.dbExists = true
+++     }
+++     a.Unlock()
+++}
+++
+++// Pointer to the configured subnets in each address space
+++type subnetKey struct {
+++     addressSpace AddressSpace
+++     subnet       string
+++     childSubnet  string
+++}
+++
+++func (s *subnetKey) String() string {
+++     k := fmt.Sprintf("%s/%s", s.addressSpace, s.subnet)
+++     if s.childSubnet != "" {
+++             k = fmt.Sprintf("%s/%s", k, s.childSubnet)
+++     }
+++     return k
+++}
+++
+++func (s *subnetKey) FromString(str string) error {
+++     if str == "" || !strings.Contains(str, "/") {
+++             return fmt.Errorf("invalid string form for subnetkey: %s", str)
+++     }
+++
+++     p := strings.Split(str, "/")
+++     if len(p) != 3 && len(p) != 5 {
+++             return fmt.Errorf("invalid string form for subnetkey: %s", str)
+++     }
+++     s.addressSpace = AddressSpace(p[0])
+++     s.subnet = fmt.Sprintf("%s/%s", p[1], p[2])
+++     if len(p) == 5 {
+++             s.childSubnet = fmt.Sprintf("%s/%s", p[1], p[2])
+++     }
+++
+++     return nil
+++}
+++
+++func (s *subnetKey) canonicalSubnet() *net.IPNet {
+++     if _, sub, err := net.ParseCIDR(s.subnet); err == nil {
+++             return sub
+++     }
+++     return nil
+++}
+++
+++func (s *subnetKey) canonicalChildSubnet() *net.IPNet {
+++     if _, sub, err := net.ParseCIDR(s.childSubnet); err == nil {
+++             return sub
+++     }
+++     return nil
+++}
+++
+++type ipVersion int
+++
+++const (
+++     v4 = 4
+++     v6 = 6
+++)
+++
+++/*******************
+++ * IPAMConf Contract
+++ ********************/
+++
+++// AddSubnet adds a subnet for the specified address space
+++func (a *Allocator) AddSubnet(addrSpace AddressSpace, subnetInfo *SubnetInfo) error {
+++     // Sanity check
+++     if addrSpace == "" {
+++             return ErrInvalidAddressSpace
+++     }
+++     if subnetInfo == nil || subnetInfo.Subnet == nil {
+++             return ErrInvalidSubnet
+++     }
+++     // Convert to smaller internal subnets (if needed)
+++     subnetList, err := getInternalSubnets(subnetInfo.Subnet, a.internalHostSize)
+++     if err != nil {
+++             return err
+++     }
+++retry:
+++     if a.contains(addrSpace, subnetInfo) {
+++             return ErrOverlapSubnet
+++     }
+++
+++     // Store the configured subnet and sync to datatstore
+++     key := subnetKey{addrSpace, subnetInfo.Subnet.String(), ""}
+++     a.Lock()
+++     a.subnets[key] = subnetInfo
+++     a.Unlock()
+++     err = a.writeToStore()
+++     if err != nil {
+++             if _, ok := err.(types.RetryError); !ok {
+++                     return types.InternalErrorf("subnet configuration failed because of %s", err.Error())
+++             }
+++             // Update to latest
+++             if erru := a.readFromStore(); erru != nil {
+++                     // Restore and bail out
+++                     a.Lock()
+++                     delete(a.addresses, key)
+++                     a.Unlock()
+++                     return fmt.Errorf("failed to get updated subnets config from datastore (%v) after (%v)", erru, err)
+++             }
+++             goto retry
+++     }
+++
+++     // Insert respective bitmasks for this subnet
+++     a.insertAddressMasks(key, subnetList)
+++
+++     return nil
+++}
+++
+++// Create and insert the internal subnet(s) addresses masks into the address database. Mask data may come from the bitseq datastore.
+++func (a *Allocator) insertAddressMasks(parentKey subnetKey, internalSubnetList []*net.IPNet) error {
+++     for _, intSub := range internalSubnetList {
+++             var err error
+++             ones, bits := intSub.Mask.Size()
+++             numAddresses := 1 << uint(bits-ones)
+++             smallKey := subnetKey{parentKey.addressSpace, parentKey.subnet, intSub.String()}
+++
+++             // Insert the new address masks. AddressMask content may come from datastore
+++             a.Lock()
+++             a.addresses[smallKey], err = bitseq.NewHandle(dsDataKey, a.store, smallKey.String(), uint32(numAddresses))
+++             a.Unlock()
+++             if err != nil {
+++                     return err
+++             }
+++     }
+++     return nil
+++}
+++
+++// Check subnets size. In case configured subnet is v6 and host size is
+++// greater than 32 bits, adjust subnet to /96.
+++func adjustAndCheckSubnetSize(subnet *net.IPNet) (*net.IPNet, error) {
+++     ones, bits := subnet.Mask.Size()
+++     if v6 == getAddressVersion(subnet.IP) {
+++             if ones < minNetSizeV6 {
+++                     return nil, ErrInvalidSubnet
+++             }
+++             if ones < minNetSizeV6Eff {
+++                     newMask := net.CIDRMask(minNetSizeV6Eff, bits)
+++                     return &net.IPNet{IP: subnet.IP, Mask: newMask}, nil
+++             }
+++     } else {
+++             if ones < minNetSize {
+++                     return nil, ErrInvalidSubnet
+++             }
+++     }
+++     return subnet, nil
+++}
+++
+++// Checks whether the passed subnet is a superset or subset of any of the subset in the db
+++func (a *Allocator) contains(space AddressSpace, subInfo *SubnetInfo) bool {
+++     a.Lock()
+++     defer a.Unlock()
+++     for k, v := range a.subnets {
+++             if space == k.addressSpace {
+++                     if subInfo.Subnet.Contains(v.Subnet.IP) ||
+++                             v.Subnet.Contains(subInfo.Subnet.IP) {
+++                             return true
+++                     }
+++             }
+++     }
+++     return false
+++}
+++
+++// Splits the passed subnet into N internal subnets with host size equal to internalHostSize.
+++// If the subnet's host size is equal to or smaller than internalHostSize, there won't be any
+++// split and the return list will contain only the passed subnet.
+++func getInternalSubnets(inSubnet *net.IPNet, internalHostSize int) ([]*net.IPNet, error) {
+++     var subnetList []*net.IPNet
+++
+++     // Sanity check and size adjustment for v6
+++     subnet, err := adjustAndCheckSubnetSize(inSubnet)
+++     if err != nil {
+++             return subnetList, err
+++     }
+++
+++     // Get network/host subnet information
+++     netBits, bits := subnet.Mask.Size()
+++     hostBits := bits - netBits
+++
+++     extraBits := hostBits - internalHostSize
+++     if extraBits <= 0 {
+++             subnetList = make([]*net.IPNet, 1)
+++             subnetList[0] = subnet
+++     } else {
+++             // Split in smaller internal subnets
+++             numIntSubs := 1 << uint(extraBits)
+++             subnetList = make([]*net.IPNet, numIntSubs)
+++
+++             // Construct one copy of the internal subnets's mask
+++             intNetBits := bits - internalHostSize
+++             intMask := net.CIDRMask(intNetBits, bits)
+++
+++             // Construct the prefix portion for each internal subnet
+++             for i := 0; i < numIntSubs; i++ {
+++                     intIP := make([]byte, len(subnet.IP))
+++                     copy(intIP, subnet.IP) // IPv6 is too big, just work on the extra portion
+++                     addIntToIP(intIP, i<<uint(internalHostSize))
+++                     subnetList[i] = &net.IPNet{IP: intIP, Mask: intMask}
+++             }
+++     }
+++     return subnetList, nil
+++}
+++
+++// RemoveSubnet removes the subnet from the specified address space
+++func (a *Allocator) RemoveSubnet(addrSpace AddressSpace, subnet *net.IPNet) error {
+++     if addrSpace == "" {
+++             return ErrInvalidAddressSpace
+++     }
+++     if subnet == nil {
+++             return ErrInvalidSubnet
+++     }
+++retry:
+++     // Look for the respective subnet configuration data
+++     // Remove it along with the internal subnets
+++     subKey := subnetKey{addrSpace, subnet.String(), ""}
+++     a.Lock()
+++     current, ok := a.subnets[subKey]
+++     a.Unlock()
+++     if !ok {
+++             return ErrSubnetNotFound
+++     }
+++
+++     // Remove config and sync to datastore
+++     a.Lock()
+++     delete(a.subnets, subKey)
+++     a.Unlock()
+++     err := a.writeToStore()
+++     if err != nil {
+++             if _, ok := err.(types.RetryError); !ok {
+++                     return types.InternalErrorf("subnet removal failed because of %s", err.Error())
+++             }
+++             // Update to latest
+++             if erru := a.readFromStore(); erru != nil {
+++                     // Restore and bail out
+++                     a.Lock()
+++                     a.subnets[subKey] = current
+++                     a.Unlock()
+++                     return fmt.Errorf("failed to get updated subnets config from datastore (%v) after (%v)", erru, err)
+++             }
+++             goto retry
+++     }
+++
+++     // Get the list of smaller internal subnets
+++     subnetList, err := getInternalSubnets(subnet, a.internalHostSize)
+++     if err != nil {
+++             return err
+++     }
+++
+++     for _, s := range subnetList {
+++             sk := subnetKey{addrSpace, subKey.subnet, s.String()}
+++             a.Lock()
+++             if bm, ok := a.addresses[sk]; ok {
+++                     bm.Destroy()
+++             }
+++             delete(a.addresses, sk)
+++             a.Unlock()
+++     }
+++
+++     return nil
+++
+++}
+++
+++// AddVendorInfo adds vendor specific data
+++func (a *Allocator) AddVendorInfo([]byte) error {
+++     // no op for us
+++     return nil
+++}
+++
+++/****************
+++ * IPAM Contract
+++ ****************/
+++
+++// Request allows requesting an IPv4 address from the specified address space
+++func (a *Allocator) Request(addrSpace AddressSpace, req *AddressRequest) (*AddressResponse, error) {
+++     return a.request(addrSpace, req, v4)
+++}
+++
+++// RequestV6 requesting an IPv6 address from the specified address space
+++func (a *Allocator) RequestV6(addrSpace AddressSpace, req *AddressRequest) (*AddressResponse, error) {
+++     return a.request(addrSpace, req, v6)
+++}
+++
+++func (a *Allocator) request(addrSpace AddressSpace, req *AddressRequest, version ipVersion) (*AddressResponse, error) {
+++     // Empty response
+++     response := &AddressResponse{}
+++
+++     // Sanity check
+++     if addrSpace == "" {
+++             return response, ErrInvalidAddressSpace
+++     }
+++
+++     // Validate request
+++     if err := req.Validate(); err != nil {
+++             return response, err
+++     }
+++
+++     // Check ip version congruence
+++     if &req.Subnet != nil && version != getAddressVersion(req.Subnet.IP) {
+++             return response, ErrInvalidRequest
+++     }
+++
+++     // Look for an address
+++     ip, _, err := a.reserveAddress(addrSpace, &req.Subnet, req.Address, version)
+++     if err == nil {
+++             // Populate response
+++             response.Address = ip
+++             a.Lock()
+++             response.Subnet = *a.subnets[subnetKey{addrSpace, req.Subnet.String(), ""}]
+++             a.Unlock()
+++     }
+++
+++     return response, err
+++}
+++
+++// Release allows releasing the address from the specified address space
+++func (a *Allocator) Release(addrSpace AddressSpace, address net.IP) {
+++     if address == nil {
+++             return
+++     }
+++     ver := getAddressVersion(address)
+++     if ver == v4 {
+++             address = address.To4()
+++     }
+++     for _, subKey := range a.getSubnetList(addrSpace, ver) {
+++             a.Lock()
+++             space := a.addresses[subKey]
+++             a.Unlock()
+++             sub := subKey.canonicalChildSubnet()
+++             if sub.Contains(address) {
+++                     // Retrieve correspondent ordinal in the subnet
+++                     ordinal := ipToInt(getHostPortionIP(address, sub))
+++                     // Release it
+++                     for {
+++                             var err error
+++                             if err = space.PushReservation(ordinal/8, ordinal%8, true); err == nil {
+++                                     break
+++                             }
+++                             if _, ok := err.(types.RetryError); ok {
+++                                     // bitmask must have changed, retry delete
+++                                     continue
+++                             }
+++                             log.Warnf("Failed to release address %s because of internal error: %s", address.String(), err.Error())
+++                             return
+++                     }
+++                     return
+++             }
+++
+++     }
+++}
+++
+++func (a *Allocator) reserveAddress(addrSpace AddressSpace, subnet *net.IPNet, prefAddress net.IP, ver ipVersion) (net.IP, *net.IPNet, error) {
+++     var keyList []subnetKey
+++
+++     // Get the list of pointers to the internal subnets
+++     if subnet != nil {
+++             // Get the list of smaller internal subnets
+++             subnetList, err := getInternalSubnets(subnet, a.internalHostSize)
+++             if err != nil {
+++                     return nil, nil, err
+++             }
+++             for _, s := range subnetList {
+++                     keyList = append(keyList, subnetKey{addrSpace, subnet.String(), s.String()})
+++             }
+++     } else {
+++             a.Lock()
+++             keyList = a.getSubnetList(addrSpace, ver)
+++             a.Unlock()
+++     }
+++     if len(keyList) == 0 {
+++             return nil, nil, ErrNoAvailableSubnet
+++     }
+++
+++     for _, key := range keyList {
+++             a.Lock()
+++             bitmask, ok := a.addresses[key]
+++             a.Unlock()
+++             if !ok {
+++                     fmt.Printf("\nDid not find a bitmask for subnet key: %s", key.String())
+++                     continue
+++             }
+++             address, err := a.getAddress(key.canonicalChildSubnet(), bitmask, prefAddress, ver)
+++             if err == nil {
+++                     return address, subnet, nil
+++             }
+++     }
+++
+++     return nil, nil, ErrNoAvailableIPs
+++}
+++
+++// Get the list of available internal subnets for the specified address space and the desired ip version
+++func (a *Allocator) getSubnetList(addrSpace AddressSpace, ver ipVersion) []subnetKey {
+++     var list [1024]subnetKey
+++     ind := 0
+++     a.Lock()
+++     for subKey := range a.addresses {
+++             s := subKey.canonicalSubnet()
+++             subVer := getAddressVersion(s.IP)
+++             if subKey.addressSpace == addrSpace && subVer == ver {
+++                     list[ind] = subKey
+++                     ind++
+++             }
+++     }
+++     a.Unlock()
+++     return list[0:ind]
+++}
+++
+++func (a *Allocator) getAddress(subnet *net.IPNet, bitmask *bitseq.Handle, prefAddress net.IP, ver ipVersion) (net.IP, error) {
+++     var (
+++             bytePos, bitPos int
+++             ordinal         int
+++             err             error
+++     )
+++
+++     // Look for free IP, skip .0 and .255, they will be automatically reserved
+++     for {
+++             if bitmask.Unselected() <= 0 {
+++                     return nil, ErrNoAvailableIPs
+++             }
+++             if prefAddress == nil {
+++                     bytePos, bitPos, err = bitmask.GetFirstAvailable()
+++             } else {
+++                     ordinal = ipToInt(getHostPortionIP(prefAddress, subnet))
+++                     bytePos, bitPos, err = bitmask.CheckIfAvailable(ordinal)
+++             }
+++             if err != nil {
+++                     return nil, ErrNoAvailableIPs
+++             }
+++
+++             // Lock it
+++             if err = bitmask.PushReservation(bytePos, bitPos, false); err != nil {
+++                     if _, ok := err.(types.RetryError); !ok {
+++                             return nil, fmt.Errorf("internal failure while reserving the address: %s", err.Error())
+++                     }
+++                     continue
+++             }
+++
+++             // Build IP ordinal
+++             ordinal = bitPos + bytePos*8
+++
+++             // For v4, let reservation of .0 and .255 happen automatically
+++             if ver == v4 && !isValidIP(ordinal) {
+++                     continue
+++             }
+++             break
+++     }
+++
+++     // Convert IP ordinal for this subnet into IP address
+++     return generateAddress(ordinal, subnet), nil
+++}
+++
+++// DumpDatabase dumps the internal info
+++func (a *Allocator) DumpDatabase() {
+++     a.Lock()
+++     defer a.Unlock()
+++     for k, config := range a.subnets {
+++             fmt.Printf("\n\n%s:", config.Subnet.String())
+++             subnetList, _ := getInternalSubnets(config.Subnet, a.internalHostSize)
+++             for _, s := range subnetList {
+++                     internKey := subnetKey{k.addressSpace, config.Subnet.String(), s.String()}
+++                     bm := a.addresses[internKey]
+++                     fmt.Printf("\n\t%s: %s\n\t%d", internKey.childSubnet, bm, bm.Unselected())
+++             }
+++     }
+++}
+++
+++// It generates the ip address in the passed subnet specified by
+++// the passed host address ordinal
+++func generateAddress(ordinal int, network *net.IPNet) net.IP {
+++     var address [16]byte
+++
+++     // Get network portion of IP
+++     if network.IP.To4() != nil {
+++             copy(address[:], network.IP.To4())
+++     } else {
+++             copy(address[:], network.IP)
+++     }
+++
+++     end := len(network.Mask)
+++     addIntToIP(address[:end], ordinal)
+++
+++     return net.IP(address[:end])
+++}
+++
+++func getAddressVersion(ip net.IP) ipVersion {
+++     if ip.To4() == nil {
+++             return v6
+++     }
+++     return v4
+++}
+++
+++// .0 and .255 will return false
+++func isValidIP(i int) bool {
+++     lastByte := i & 0xff
+++     return lastByte != 0xff && lastByte != 0
+++}
+++
+++// Adds the ordinal IP to the current array
+++// 192.168.0.0 + 53 => 192.168.53
+++func addIntToIP(array []byte, ordinal int) {
+++     for i := len(array) - 1; i >= 0; i-- {
+++             array[i] |= (byte)(ordinal & 0xff)
+++             ordinal >>= 8
+++     }
+++}
+++
+++// Convert an ordinal to the respective IP address
+++func ipToInt(ip []byte) int {
+++     value := 0
+++     for i := 0; i < len(ip); i++ {
+++             j := len(ip) - 1 - i
+++             value += int(ip[i]) << uint(j*8)
+++     }
+++     return value
+++}
+++
+++// Given an address and subnet, returns the host portion address
+++func getHostPortionIP(address net.IP, subnet *net.IPNet) net.IP {
+++     hostPortion := make([]byte, len(address))
+++     for i := 0; i < len(subnet.Mask); i++ {
+++             hostPortion[i] = address[i] &^ subnet.Mask[i]
+++     }
+++     return hostPortion
+++}
+++
+++func printLine(head *bitseq.Sequence) {
+++     fmt.Println()
+++     for head != nil {
+++             fmt.Printf("-")
+++             head = head.Next
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..cf67a753759ad1a04badb5be6be37644169d01f1
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,587 @@@@
+++package ipam
+++
+++import (
+++     "fmt"
+++     "net"
+++     "testing"
+++     "time"
+++
+++     "github.com/docker/libnetwork/bitseq"
+++)
+++
+++func getAllocator(t *testing.T, subnet *net.IPNet) *Allocator {
+++     a, err := NewAllocator(nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     a.AddSubnet("default", &SubnetInfo{Subnet: subnet})
+++     return a
+++}
+++
+++func TestInt2IP2IntConversion(t *testing.T) {
+++     for i := 0; i < 256*256*256; i++ {
+++             var array [4]byte // new array at each cycle
+++             addIntToIP(array[:], i)
+++             j := ipToInt(array[:])
+++             if j != i {
+++                     t.Fatalf("Failed to convert ordinal %d to IP % x and back to ordinal. Got %d", i, array, j)
+++             }
+++     }
+++}
+++
+++func TestIsValid(t *testing.T) {
+++     list := []int{0, 255, 256, 511, 512, 767, 768}
+++     for _, i := range list {
+++             if isValidIP(i) {
+++                     t.Fatalf("Failed to detect invalid IPv4 ordinal: %d", i)
+++             }
+++     }
+++
+++     list = []int{1, 254, 257, 258, 510, 513, 769, 770}
+++     for _, i := range list {
+++             if !isValidIP(i) {
+++                     t.Fatalf("Marked valid ipv4 as invalid: %d", i)
+++             }
+++     }
+++}
+++
+++func TestGetAddressVersion(t *testing.T) {
+++     if v4 != getAddressVersion(net.ParseIP("172.28.30.112")) {
+++             t.Fatalf("Failed to detect IPv4 version")
+++     }
+++     if v4 != getAddressVersion(net.ParseIP("0.0.0.1")) {
+++             t.Fatalf("Failed to detect IPv4 version")
+++     }
+++     if v6 != getAddressVersion(net.ParseIP("ff01::1")) {
+++             t.Fatalf("Failed to detect IPv6 version")
+++     }
+++     if v6 != getAddressVersion(net.ParseIP("2001:56::76:51")) {
+++             t.Fatalf("Failed to detect IPv6 version")
+++     }
+++}
+++
+++func TestKeyString(t *testing.T) {
+++
+++     k := &subnetKey{addressSpace: "default", subnet: "172.27.0.0/16"}
+++     expected := "default/172.27.0.0/16"
+++     if expected != k.String() {
+++             t.Fatalf("Unexpected key string: %s", k.String())
+++     }
+++
+++     k2 := &subnetKey{}
+++     err := k2.FromString(expected)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if k2.addressSpace != k.addressSpace || k2.subnet != k.subnet {
+++             t.Fatalf("subnetKey.FromString() failed. Expected %v. Got %v", k, k2)
+++     }
+++
+++     expected = fmt.Sprintf("%s/%s", expected, "172.27.3.0/24")
+++     k.childSubnet = "172.27.3.0/24"
+++     if expected != k.String() {
+++             t.Fatalf("Unexpected key string: %s", k.String())
+++     }
+++
+++     err = k2.FromString(expected)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if k2.addressSpace != k.addressSpace || k2.subnet != k.subnet {
+++             t.Fatalf("subnetKey.FromString() failed. Expected %v. Got %v", k, k2)
+++     }
+++}
+++
+++func TestAddSubnets(t *testing.T) {
+++     a, err := NewAllocator(nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     _, sub0, _ := net.ParseCIDR("10.0.0.0/8")
+++     err = a.AddSubnet("default", &SubnetInfo{Subnet: sub0})
+++     if err != nil {
+++             t.Fatalf("Unexpected failure in adding subent")
+++     }
+++
+++     err = a.AddSubnet("abc", &SubnetInfo{Subnet: sub0})
+++     if err != nil {
+++             t.Fatalf("Unexpected failure in adding overlapping subents to different address spaces")
+++     }
+++
+++     err = a.AddSubnet("abc", &SubnetInfo{Subnet: sub0})
+++     if err == nil {
+++             t.Fatalf("Failed to detect overlapping subnets: %s and %s", sub0, sub0)
+++     }
+++
+++     _, sub1, _ := net.ParseCIDR("10.20.2.0/24")
+++     err = a.AddSubnet("default", &SubnetInfo{Subnet: sub1})
+++     if err == nil {
+++             t.Fatalf("Failed to detect overlapping subnets: %s and %s", sub0, sub1)
+++     }
+++
+++     _, sub2, _ := net.ParseCIDR("10.128.0.0/9")
+++     err = a.AddSubnet("default", &SubnetInfo{Subnet: sub2})
+++     if err == nil {
+++             t.Fatalf("Failed to detect overlapping subnets: %s and %s", sub1, sub2)
+++     }
+++
+++     _, sub6, err := net.ParseCIDR("1003:1:2:3:4:5:6::/112")
+++     if err != nil {
+++             t.Fatalf("Wrong input, Can't proceed: %s", err.Error())
+++     }
+++     err = a.AddSubnet("default", &SubnetInfo{Subnet: sub6})
+++     if err != nil {
+++             t.Fatalf("Failed to add v6 subnet: %s", err.Error())
+++     }
+++
+++     _, sub6, err = net.ParseCIDR("1003:1:2:3::/64")
+++     if err != nil {
+++             t.Fatalf("Wrong input, Can't proceed: %s", err.Error())
+++     }
+++     err = a.AddSubnet("default", &SubnetInfo{Subnet: sub6})
+++     if err == nil {
+++             t.Fatalf("Failed to detect overlapping v6 subnet")
+++     }
+++}
+++
+++func TestAdjustAndCheckSubnet(t *testing.T) {
+++     _, sub6, _ := net.ParseCIDR("1003:1:2:300::/63")
+++     _, err := adjustAndCheckSubnetSize(sub6)
+++     if err == nil {
+++             t.Fatalf("Failed detect too big v6 subnet")
+++     }
+++
+++     _, sub, _ := net.ParseCIDR("192.0.0.0/7")
+++     _, err = adjustAndCheckSubnetSize(sub)
+++     if err == nil {
+++             t.Fatalf("Failed detect too big v4 subnet")
+++     }
+++
+++     subnet := "1004:1:2:6::/64"
+++     _, sub6, _ = net.ParseCIDR(subnet)
+++     subnetToSplit, err := adjustAndCheckSubnetSize(sub6)
+++     if err != nil {
+++             t.Fatalf("Unexpected error returned by adjustAndCheckSubnetSize()")
+++     }
+++     ones, _ := subnetToSplit.Mask.Size()
+++     if ones < minNetSizeV6Eff {
+++             t.Fatalf("Wrong effective network size for %s. Expected: %d. Got: %d", subnet, minNetSizeV6Eff, ones)
+++     }
+++}
+++
+++func TestRemoveSubnet(t *testing.T) {
+++     a, err := NewAllocator(nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     input := []struct {
+++             addrSpace AddressSpace
+++             subnet    string
+++     }{
+++             {"default", "192.168.0.0/16"},
+++             {"default", "172.17.0.0/16"},
+++             {"default", "10.0.0.0/8"},
+++             {"default", "2002:1:2:3:4:5:ffff::/112"},
+++             {"splane", "172.17.0.0/16"},
+++             {"splane", "10.0.0.0/8"},
+++             {"splane", "2002:1:2:3:4:5:6::/112"},
+++             {"splane", "2002:1:2:3:4:5:ffff::/112"},
+++     }
+++
+++     for _, i := range input {
+++             _, sub, err := net.ParseCIDR(i.subnet)
+++             if err != nil {
+++                     t.Fatalf("Wrong input, Can't proceed: %s", err.Error())
+++             }
+++             err = a.AddSubnet(i.addrSpace, &SubnetInfo{Subnet: sub})
+++             if err != nil {
+++                     t.Fatalf("Failed to apply input. Can't proceed: %s", err.Error())
+++             }
+++     }
+++
+++     _, sub, _ := net.ParseCIDR("172.17.0.0/16")
+++     a.RemoveSubnet("default", sub)
+++     if len(a.subnets) != 7 {
+++             t.Fatalf("Failed to remove subnet info")
+++     }
+++     list := a.getSubnetList("default", v4)
+++     if len(list) != 257 {
+++             t.Fatalf("Failed to effectively remove subnet address space")
+++     }
+++
+++     _, sub, _ = net.ParseCIDR("2002:1:2:3:4:5:ffff::/112")
+++     a.RemoveSubnet("default", sub)
+++     if len(a.subnets) != 6 {
+++             t.Fatalf("Failed to remove subnet info")
+++     }
+++     list = a.getSubnetList("default", v6)
+++     if len(list) != 0 {
+++             t.Fatalf("Failed to effectively remove subnet address space")
+++     }
+++
+++     _, sub, _ = net.ParseCIDR("2002:1:2:3:4:5:6::/112")
+++     a.RemoveSubnet("splane", sub)
+++     if len(a.subnets) != 5 {
+++             t.Fatalf("Failed to remove subnet info")
+++     }
+++     list = a.getSubnetList("splane", v6)
+++     if len(list) != 1 {
+++             t.Fatalf("Failed to effectively remove subnet address space")
+++     }
+++}
+++
+++func TestGetInternalSubnets(t *testing.T) {
+++     // This function tests the splitting of a parent subnet in small host subnets.
+++     // The splitting is controlled by the max host size, which is the first parameter
+++     // passed to the function. It basically says if the parent subnet host size is
+++     // greater than the max host size, split the parent subnet into N internal small
+++     // subnets with host size = max host size to cover the same address space.
+++
+++     input := []struct {
+++             internalHostSize int
+++             parentSubnet     string
+++             firstIntSubnet   string
+++             lastIntSubnet    string
+++     }{
+++             // Test 8 bits prefix network
+++             {24, "10.0.0.0/8", "10.0.0.0/8", "10.0.0.0/8"},
+++             {16, "10.0.0.0/8", "10.0.0.0/16", "10.255.0.0/16"},
+++             {8, "10.0.0.0/8", "10.0.0.0/24", "10.255.255.0/24"},
+++             // Test 16 bits prefix network
+++             {16, "192.168.0.0/16", "192.168.0.0/16", "192.168.0.0/16"},
+++             {8, "192.168.0.0/16", "192.168.0.0/24", "192.168.255.0/24"},
+++             // Test 24 bits prefix network
+++             {16, "192.168.57.0/24", "192.168.57.0/24", "192.168.57.0/24"},
+++             {8, "192.168.57.0/24", "192.168.57.0/24", "192.168.57.0/24"},
+++             // Test non byte multiple host size
+++             {24, "10.0.0.0/8", "10.0.0.0/8", "10.0.0.0/8"},
+++             {20, "10.0.0.0/12", "10.0.0.0/12", "10.0.0.0/12"},
+++             {20, "10.128.0.0/12", "10.128.0.0/12", "10.128.0.0/12"},
+++             {12, "10.16.0.0/16", "10.16.0.0/20", "10.16.240.0/20"},
+++             {13, "10.0.0.0/8", "10.0.0.0/19", "10.255.224.0/19"},
+++             {15, "10.0.0.0/8", "10.0.0.0/17", "10.255.128.0/17"},
+++             // Test v6 network
+++             {16, "2002:1:2:3:4:5:6000::/110", "2002:1:2:3:4:5:6000:0/112", "2002:1:2:3:4:5:6003:0/112"},
+++             {16, "2002:1:2:3:4:5:ff00::/104", "2002:1:2:3:4:5:ff00:0/112", "2002:1:2:3:4:5:ffff:0/112"},
+++             {12, "2002:1:2:3:4:5:ffff::/112", "2002:1:2:3:4:5:ffff:0/116", "2002:1:2:3:4:5:ffff:f000/116"},
+++             {11, "2002:1:2:3:4:5:ffff::/112", "2002:1:2:3:4:5:ffff:0/117", "2002:1:2:3:4:5:ffff:f800/117"},
+++     }
+++
+++     for _, d := range input {
+++             assertInternalSubnet(t, d.internalHostSize, d.parentSubnet, d.firstIntSubnet, d.lastIntSubnet)
+++     }
+++
+++}
+++
+++func TestGetAddress(t *testing.T) {
+++     input := []string{
+++             /*"10.0.0.0/8", "10.0.0.0/9", */ "10.0.0.0/10", "10.0.0.0/11", "10.0.0.0/12", "10.0.0.0/13", "10.0.0.0/14",
+++             "10.0.0.0/15", "10.0.0.0/16", "10.0.0.0/17", "10.0.0.0/18", "10.0.0.0/19", "10.0.0.0/20", "10.0.0.0/21",
+++             "10.0.0.0/22", "10.0.0.0/23", "10.0.0.0/24", "10.0.0.0/25", "10.0.0.0/26", "10.0.0.0/27", "10.0.0.0/28",
+++             "10.0.0.0/29", "10.0.0.0/30", "10.0.0.0/31"}
+++
+++     for _, subnet := range input {
+++             assertGetAddress(t, subnet)
+++     }
+++}
+++
+++func TestGetSubnetList(t *testing.T) {
+++     a, err := NewAllocator(nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     input := []struct {
+++             addrSpace AddressSpace
+++             subnet    string
+++     }{
+++             {"default", "192.168.0.0/16"},
+++             {"default", "172.17.0.0/16"},
+++             {"default", "10.0.0.0/8"},
+++             {"default", "2002:1:2:3:4:5:6::/112"},
+++             {"default", "2002:1:2:3:4:5:ffff::/112"},
+++             {"splane", "172.17.0.0/16"},
+++             {"splane", "10.0.0.0/8"},
+++             {"splane", "2002:1:2:3:4:5:ff00::/104"},
+++     }
+++
+++     for _, i := range input {
+++             _, sub, err := net.ParseCIDR(i.subnet)
+++             if err != nil {
+++                     t.Fatalf("Wrong input, Can't proceed: %s", err.Error())
+++             }
+++             err = a.AddSubnet(i.addrSpace, &SubnetInfo{Subnet: sub})
+++             if err != nil {
+++                     t.Fatalf("Failed to apply input. Can't proceed: %s", err.Error())
+++             }
+++     }
+++
+++     list := a.getSubnetList("default", v4)
+++     if len(list) != 258 {
+++             t.Fatalf("Incorrect number of internal subnets for ipv4 version. Expected 258. Got %d.", len(list))
+++     }
+++     list = a.getSubnetList("splane", v4)
+++     if len(list) != 257 {
+++             t.Fatalf("Incorrect number of internal subnets for ipv4 version. Expected 257. Got %d.", len(list))
+++     }
+++
+++     list = a.getSubnetList("default", v6)
+++     if len(list) != 2 {
+++             t.Fatalf("Incorrect number of internal subnets for ipv6 version. Expected 2. Got %d.", len(list))
+++     }
+++     list = a.getSubnetList("splane", v6)
+++     if len(list) != 256 {
+++             t.Fatalf("Incorrect number of internal subnets for ipv6 version. Expected 256. Got %d.", len(list))
+++     }
+++
+++}
+++
+++func TestRequestSyntaxCheck(t *testing.T) {
+++     var (
+++             subnet   = "192.168.0.0/16"
+++             addSpace = AddressSpace("green")
+++     )
+++
+++     a, err := NewAllocator(nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     // Add subnet and create base request
+++     _, sub, _ := net.ParseCIDR(subnet)
+++     a.AddSubnet(addSpace, &SubnetInfo{Subnet: sub})
+++     req := &AddressRequest{Subnet: *sub}
+++
+++     // Empty address space request
+++     _, err = a.Request("", req)
+++     if err == nil {
+++             t.Fatalf("Failed to detect wrong request: empty address space")
+++     }
+++
+++     // Preferred address from different subnet in request
+++     req.Address = net.ParseIP("172.17.0.23")
+++     _, err = a.Request(addSpace, req)
+++     if err == nil {
+++             t.Fatalf("Failed to detect wrong request: preferred IP from different subnet")
+++     }
+++
+++     // Preferred address specified and nil subnet
+++     req = &AddressRequest{Address: net.ParseIP("172.17.0.23")}
+++     _, err = a.Request(addSpace, req)
+++     if err == nil {
+++             t.Fatalf("Failed to detect wrong request: subnet not specified but preferred address specified")
+++     }
+++}
+++
+++func TestRequest(t *testing.T) {
+++     // Request N addresses from different size subnets, verifying last request
+++     // returns expected address. Internal subnet host size is Allocator's default, 16
+++     input := []struct {
+++             subnet string
+++             numReq int
+++             lastIP string
+++     }{
+++             {"192.168.59.0/24", 254, "192.168.59.254"},
+++             {"192.168.240.0/20", 254, "192.168.240.254"},
+++             {"192.168.0.0/16", 254, "192.168.0.254"},
+++             {"10.16.0.0/16", 254, "10.16.0.254"},
+++             {"10.128.0.0/12", 254, "10.128.0.254"},
+++             {"10.0.0.0/8", 254, "10.0.0.254"},
+++             {"192.168.0.0/16", 256, "192.168.1.2"},
+++             {"10.0.0.0/8", 256, "10.0.1.2"},
+++
+++             {"192.168.128.0/18", 4 * 254, "192.168.131.254"},
+++             {"192.168.240.0/20", 16 * 254, "192.168.255.254"},
+++
+++             {"192.168.0.0/16", 256 * 254, "192.168.255.254"},
+++             {"10.0.0.0/8", 2 * 254, "10.0.1.254"},
+++             {"10.0.0.0/8", 5 * 254, "10.0.4.254"},
+++             //{"10.0.0.0/8", 100 * 256 * 254, "10.99.255.254"},
+++     }
+++
+++     for _, d := range input {
+++             assertNRequests(t, d.subnet, d.numReq, d.lastIP)
+++     }
+++}
+++
+++func TestRelease(t *testing.T) {
+++     var (
+++             err    error
+++             req    *AddressRequest
+++             subnet = "192.168.0.0/16"
+++     )
+++
+++     _, sub, _ := net.ParseCIDR(subnet)
+++     a := getAllocator(t, sub)
+++     req = &AddressRequest{Subnet: *sub}
+++     bm := a.addresses[subnetKey{"default", subnet, subnet}]
+++
+++     // Allocate all addresses
+++     for err != ErrNoAvailableIPs {
+++             _, err = a.Request("default", req)
+++     }
+++
+++     toRelease := []struct {
+++             address string
+++     }{
+++             {"192.168.0.1"},
+++             {"192.168.0.2"},
+++             {"192.168.0.3"},
+++             {"192.168.0.4"},
+++             {"192.168.0.5"},
+++             {"192.168.0.6"},
+++             {"192.168.0.7"},
+++             {"192.168.0.8"},
+++             {"192.168.0.9"},
+++             {"192.168.0.10"},
+++             {"192.168.0.30"},
+++             {"192.168.0.31"},
+++             {"192.168.1.32"},
+++
+++             {"192.168.0.254"},
+++             {"192.168.1.1"},
+++             {"192.168.1.2"},
+++
+++             {"192.168.1.3"},
+++
+++             {"192.168.255.253"},
+++             {"192.168.255.254"},
+++     }
+++
+++     // One by one, relase the address and request again. We should get the same IP
+++     req = &AddressRequest{Subnet: *sub}
+++     for i, inp := range toRelease {
+++             address := net.ParseIP(inp.address)
+++             a.Release("default", address)
+++             if bm.Unselected() != 1 {
+++                     t.Fatalf("Failed to update free address count after release. Expected %d, Found: %d", i+1, bm.Unselected())
+++             }
+++
+++             rsp, err := a.Request("default", req)
+++             if err != nil {
+++                     t.Fatalf("Failed to obtain the address: %s", err.Error())
+++             }
+++             if !address.Equal(rsp.Address) {
+++                     t.Fatalf("Failed to obtain the same address. Expected: %s, Got: %s", address, rsp.Address)
+++             }
+++     }
+++}
+++
+++func assertInternalSubnet(t *testing.T, hostSize int, bigSubnet, firstSmall, lastSmall string) {
+++     _, subnet, _ := net.ParseCIDR(bigSubnet)
+++     list, _ := getInternalSubnets(subnet, hostSize)
+++     count := 1
+++     ones, bits := subnet.Mask.Size()
+++     diff := bits - ones - hostSize
+++     if diff > 0 {
+++             count <<= uint(diff)
+++     }
+++
+++     if len(list) != count {
+++             t.Fatalf("Wrong small subnets number. Expected: %d, Got: %d", count, len(list))
+++     }
+++     if firstSmall != list[0].String() {
+++             t.Fatalf("Wrong first small subent. Expected: %v, Got: %v", firstSmall, list[0])
+++     }
+++     if lastSmall != list[count-1].String() {
+++             t.Fatalf("Wrong last small subent. Expected: %v, Got: %v", lastSmall, list[count-1])
+++     }
+++}
+++
+++func assertGetAddress(t *testing.T, subnet string) {
+++     var (
+++             err       error
+++             printTime = false
+++             a         = &Allocator{}
+++     )
+++
+++     _, sub, _ := net.ParseCIDR(subnet)
+++     ones, bits := sub.Mask.Size()
+++     zeroes := bits - ones
+++     numAddresses := 1 << uint(zeroes)
+++
+++     bm, err := bitseq.NewHandle("ipam_test", nil, "default/192.168.0.0/24", uint32(numAddresses))
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     start := time.Now()
+++     run := 0
+++     for err != ErrNoAvailableIPs {
+++             _, err = a.getAddress(sub, bm, nil, v4)
+++             run++
+++     }
+++     if printTime {
+++             fmt.Printf("\nTaken %v, to allocate all addresses on %s. (nemAddresses: %d. Runs: %d)", time.Since(start), subnet, numAddresses, run)
+++     }
+++     if bm.Unselected() != 0 {
+++             t.Fatalf("Unexpected free count after reserving all addresses: %d", bm.Unselected())
+++     }
+++     /*
+++             if bm.Head.Block != expectedMax || bm.Head.Count != numBlocks {
+++                     t.Fatalf("Failed to effectively reserve all addresses on %s. Expected (0x%x, %d) as first sequence. Found (0x%x,%d)",
+++                             subnet, expectedMax, numBlocks, bm.Head.Block, bm.Head.Count)
+++             }
+++     */
+++}
+++
+++func assertNRequests(t *testing.T, subnet string, numReq int, lastExpectedIP string) {
+++     var (
+++             err       error
+++             req       *AddressRequest
+++             rsp       *AddressResponse
+++             printTime = false
+++     )
+++
+++     _, sub, _ := net.ParseCIDR(subnet)
+++     lastIP := net.ParseIP(lastExpectedIP)
+++
+++     a := getAllocator(t, sub)
+++     req = &AddressRequest{Subnet: *sub}
+++
+++     i := 0
+++     start := time.Now()
+++     for ; i < numReq; i++ {
+++             rsp, err = a.Request("default", req)
+++     }
+++     if printTime {
+++             fmt.Printf("\nTaken %v, to allocate %d addresses on %s\n", time.Since(start), numReq, subnet)
+++     }
+++
+++     if !lastIP.Equal(rsp.Address) {
+++             t.Fatalf("Wrong last IP. Expected %s. Got: %s (err: %v, ind: %d)", lastExpectedIP, rsp.Address.String(), err, i)
+++     }
+++}
+++
+++func benchmarkRequest(subnet *net.IPNet) {
+++     var err error
+++
+++     a, _ := NewAllocator(nil)
+++     a.internalHostSize = 20
+++     a.AddSubnet("default", &SubnetInfo{Subnet: subnet})
+++
+++     req := &AddressRequest{Subnet: *subnet}
+++     for err != ErrNoAvailableIPs {
+++             _, err = a.Request("default", req)
+++
+++     }
+++}
+++
+++func benchMarkRequest(subnet *net.IPNet, b *testing.B) {
+++     for n := 0; n < b.N; n++ {
+++             benchmarkRequest(subnet)
+++     }
+++}
+++
+++func BenchmarkRequest_24(b *testing.B) {
+++     benchmarkRequest(&net.IPNet{IP: []byte{10, 0, 0, 0}, Mask: []byte{255, 255, 255, 0}})
+++}
+++
+++func BenchmarkRequest_16(b *testing.B) {
+++     benchmarkRequest(&net.IPNet{IP: []byte{10, 0, 0, 0}, Mask: []byte{255, 255, 0, 0}})
+++}
+++
+++func BenchmarkRequest_8(b *testing.B) {
+++     benchmarkRequest(&net.IPNet{IP: []byte{10, 0, 0, 0}, Mask: []byte{255, 0xfc, 0, 0}})
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..618ada2c52385fb11f8c8c0f0d588601750dc9ac
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,100 @@@@
+++// Package ipam that specifies the contract the IPAM plugin need to satisfy,
+++// decoupling IPAM interface and implementation.
+++package ipam
+++
+++import (
+++     "errors"
+++     "net"
+++)
+++
+++/**************
+++ * IPAM Errors
+++ **************/
+++
+++// ErrIpamNotAvailable is returned when the plugin prviding the IPAM service is not available
+++var (
+++     ErrInvalidIpamService       = errors.New("Invalid IPAM Service")
+++     ErrInvalidIpamConfigService = errors.New("Invalid IPAM Config Service")
+++     ErrIpamNotAvailable         = errors.New("IPAM Service not available")
+++     ErrIpamInternalError        = errors.New("IPAM Internal Error")
+++     ErrInvalidAddressSpace      = errors.New("Invalid Address Space")
+++     ErrInvalidSubnet            = errors.New("Invalid Subnet")
+++     ErrInvalidRequest           = errors.New("Invalid Request")
+++     ErrSubnetNotFound           = errors.New("Subnet not found")
+++     ErrOverlapSubnet            = errors.New("Subnet overlaps with existing subnet on this address space")
+++     ErrNoAvailableSubnet        = errors.New("No available subnet")
+++     ErrNoAvailableIPs           = errors.New("No available addresses on subnet")
+++     ErrIPAlreadyAllocated       = errors.New("Address already in use")
+++     ErrIPOutOfRange             = errors.New("Requested address is out of range")
+++     ErrSubnetAlreadyRegistered  = errors.New("Subnet already registered on this address space")
+++     ErrBadSubnet                = errors.New("Address space does not contain specified subnet")
+++)
+++
+++// AddressSpace identifies a unique pool of network addresses
+++type AddressSpace string
+++
+++/*******************************
+++ * IPAM Configuration Interface
+++ *******************************/
+++
+++// Config represents the interface the IPAM service plugins must implement
+++// in order to allow injection/modification of IPAM database.
+++// Common key is a addressspace
+++type Config interface {
+++     // AddSubnet adds a subnet to the specified address space
+++     AddSubnet(AddressSpace, *SubnetInfo) error
+++     // RemoveSubnet removes a subnet from the specified address space
+++     RemoveSubnet(AddressSpace, *net.IPNet) error
+++     // AddVendorInfo adds Vendor specific data
+++     AddVendorInfo([]byte) error
+++}
+++
+++// SubnetInfo contains the information subnet hosts need in order to communicate
+++type SubnetInfo struct {
+++     Subnet     *net.IPNet
+++     Gateway    net.IP
+++     OpaqueData []byte // Vendor specific
+++}
+++
+++/*************************
+++ * IPAM Service Interface
+++ *************************/
+++
+++// IPAM defines the interface that needs to be implemented by IPAM service plugin
+++// Common key is a unique address space identifier
+++type IPAM interface {
+++     // Request address from the specified address space
+++     Request(AddressSpace, *AddressRequest) (*AddressResponse, error)
+++     // Separate API for IPv6
+++     RequestV6(AddressSpace, *AddressRequest) (*AddressResponse, error)
+++     // Release the address from the specified address space
+++     Release(AddressSpace, net.IP)
+++}
+++
+++// AddressRequest encloses the information a client
+++// needs to pass to IPAM when requesting an address
+++type AddressRequest struct {
+++     Subnet     net.IPNet // Preferred subnet pool (Optional)
+++     Address    net.IP    // Preferred address (Optional)
+++     Endpoint   string    // For static IP mapping (Optional)
+++     OpaqueData []byte    // Vendor specific request data
+++}
+++
+++// Validate runs syntactic validation on this AddressRequest object
+++func (req *AddressRequest) Validate() error {
+++     var byteArray []byte = req.Address
+++
+++     // Check preferred address
+++     if byteArray != nil && (&req.Subnet == nil || !req.Subnet.Contains(req.Address)) {
+++             return ErrInvalidRequest
+++     }
+++
+++     return nil
+++}
+++
+++// AddressResponse represents the IPAM service's
+++// response to an address request
+++type AddressResponse struct {
+++     Address net.IP
+++     Subnet  SubnetInfo
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1e8d740609e635455a3987d951fa0da8269485bd
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,178 @@@@
+++package ipam
+++
+++import (
+++     "encoding/json"
+++     "net"
+++
+++     log "github.com/Sirupsen/logrus"
+++     "github.com/docker/libnetwork/datastore"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++// Key provides the Key to be used in KV Store
+++func (a *Allocator) Key() []string {
+++     a.Lock()
+++     defer a.Unlock()
+++     return []string{a.App, a.ID}
+++}
+++
+++// KeyPrefix returns the immediate parent key that can be used for tree walk
+++func (a *Allocator) KeyPrefix() []string {
+++     a.Lock()
+++     defer a.Unlock()
+++     return []string{a.App}
+++}
+++
+++// Value marshals the data to be stored in the KV store
+++func (a *Allocator) Value() []byte {
+++     a.Lock()
+++     defer a.Unlock()
+++
+++     if a.subnets == nil {
+++             return []byte{}
+++     }
+++
+++     b, err := subnetsToByteArray(a.subnets)
+++     if err != nil {
+++             return nil
+++     }
+++     return b
+++}
+++
+++// SetValue unmarshalls the data from the KV store.
+++func (a *Allocator) SetValue(value []byte) error {
+++     a.subnets = byteArrayToSubnets(value)
+++     return nil
+++}
+++
+++func subnetsToByteArray(m map[subnetKey]*SubnetInfo) ([]byte, error) {
+++     if m == nil {
+++             return nil, nil
+++     }
+++
+++     mm := make(map[string]string, len(m))
+++     for k, v := range m {
+++             mm[k.String()] = v.Subnet.String()
+++     }
+++
+++     return json.Marshal(mm)
+++}
+++
+++func byteArrayToSubnets(ba []byte) map[subnetKey]*SubnetInfo {
+++     m := map[subnetKey]*SubnetInfo{}
+++
+++     if ba == nil || len(ba) == 0 {
+++             return m
+++     }
+++
+++     var mm map[string]string
+++     err := json.Unmarshal(ba, &mm)
+++     if err != nil {
+++             log.Warnf("Failed to decode subnets byte array: %v", err)
+++             return m
+++     }
+++     for ks, vs := range mm {
+++             sk := subnetKey{}
+++             if err := sk.FromString(ks); err != nil {
+++                     log.Warnf("Failed to decode subnets map entry: (%s, %s)", ks, vs)
+++                     continue
+++             }
+++             si := &SubnetInfo{}
+++             _, nw, err := net.ParseCIDR(vs)
+++             if err != nil {
+++                     log.Warnf("Failed to decode subnets map entry value: (%s, %s)", ks, vs)
+++                     continue
+++             }
+++             si.Subnet = nw
+++             m[sk] = si
+++     }
+++     return m
+++}
+++
+++// Index returns the latest DB Index as seen by this object
+++func (a *Allocator) Index() uint64 {
+++     a.Lock()
+++     defer a.Unlock()
+++     return a.dbIndex
+++}
+++
+++// SetIndex method allows the datastore to store the latest DB Index into this object
+++func (a *Allocator) SetIndex(index uint64) {
+++     a.Lock()
+++     a.dbIndex = index
+++     a.dbExists = true
+++     a.Unlock()
+++}
+++
+++// Exists method is true if this object has been stored in the DB.
+++func (a *Allocator) Exists() bool {
+++     a.Lock()
+++     defer a.Unlock()
+++     return a.dbExists
+++}
+++
+++func (a *Allocator) watchForChanges() error {
+++     if a.store == nil {
+++             return nil
+++     }
+++
+++     kvpChan, err := a.store.KVStore().Watch(datastore.Key(a.Key()...), nil)
+++     if err != nil {
+++             return err
+++     }
+++     go func() {
+++             for {
+++                     select {
+++                     case kvPair := <-kvpChan:
+++                             if kvPair != nil {
+++                                     log.Debugf("Got notification for key %v: %v", kvPair.Key, kvPair.Value)
+++                                     a.subnetConfigFromStore(kvPair)
+++                             }
+++                     }
+++             }
+++     }()
+++     return nil
+++}
+++
+++func (a *Allocator) readFromStore() error {
+++     a.Lock()
+++     store := a.store
+++     a.Unlock()
+++
+++     if store == nil {
+++             return nil
+++     }
+++
+++     kvPair, err := a.store.KVStore().Get(datastore.Key(a.Key()...))
+++     if err != nil {
+++             return err
+++     }
+++
+++     a.subnetConfigFromStore(kvPair)
+++
+++     return nil
+++}
+++
+++func (a *Allocator) writeToStore() error {
+++     a.Lock()
+++     store := a.store
+++     a.Unlock()
+++     if store == nil {
+++             return nil
+++     }
+++     err := store.PutObjectAtomic(a)
+++     if err == datastore.ErrKeyModified {
+++             return types.RetryErrorf("failed to perform atomic write (%v). retry might fix the error", err)
+++     }
+++     return err
+++}
+++
+++func (a *Allocator) deleteFromStore() error {
+++     a.Lock()
+++     store := a.store
+++     a.Unlock()
+++     if store == nil {
+++             return nil
+++     }
+++     return store.DeleteObjectAtomic(a)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1227647b74a57cf4d56d4ee344e980086fe61e3e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,164 @@@@
+++package iptables
+++
+++import (
+++     "fmt"
+++     "strings"
+++
+++     "github.com/Sirupsen/logrus"
+++     "github.com/godbus/dbus"
+++)
+++
+++// IPV defines the table string
+++type IPV string
+++
+++const (
+++     // Iptables point ipv4 table
+++     Iptables IPV = "ipv4"
+++     // IP6Tables point to ipv6 table
+++     IP6Tables IPV = "ipv6"
+++     // Ebtables point to bridge table
+++     Ebtables IPV = "eb"
+++)
+++const (
+++     dbusInterface = "org.fedoraproject.FirewallD1"
+++     dbusPath      = "/org/fedoraproject/FirewallD1"
+++)
+++
+++// Conn is a connection to firewalld dbus endpoint.
+++type Conn struct {
+++     sysconn *dbus.Conn
+++     sysobj  *dbus.Object
+++     signal  chan *dbus.Signal
+++}
+++
+++var (
+++     connection       *Conn
+++     firewalldRunning bool      // is Firewalld service running
+++     onReloaded       []*func() // callbacks when Firewalld has been reloaded
+++)
+++
+++// FirewalldInit initializes firewalld management code.
+++func FirewalldInit() error {
+++     var err error
+++
+++     if connection, err = newConnection(); err != nil {
+++             return fmt.Errorf("Failed to connect to D-Bus system bus: %v", err)
+++     }
+++     if connection != nil {
+++             go signalHandler()
+++     }
+++
+++     firewalldRunning = checkRunning()
+++     return nil
+++}
+++
+++// New() establishes a connection to the system bus.
+++func newConnection() (*Conn, error) {
+++     c := new(Conn)
+++     if err := c.initConnection(); err != nil {
+++             return nil, err
+++     }
+++
+++     return c, nil
+++}
+++
+++// Innitialize D-Bus connection.
+++func (c *Conn) initConnection() error {
+++     var err error
+++
+++     c.sysconn, err = dbus.SystemBus()
+++     if err != nil {
+++             return err
+++     }
+++
+++     // This never fails, even if the service is not running atm.
+++     c.sysobj = c.sysconn.Object(dbusInterface, dbus.ObjectPath(dbusPath))
+++
+++     rule := fmt.Sprintf("type='signal',path='%s',interface='%s',sender='%s',member='Reloaded'",
+++             dbusPath, dbusInterface, dbusInterface)
+++     c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule)
+++
+++     rule = fmt.Sprintf("type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus',sender='org.freedesktop.DBus',arg0='%s'",
+++             dbusInterface)
+++     c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule)
+++
+++     c.signal = make(chan *dbus.Signal, 10)
+++     c.sysconn.Signal(c.signal)
+++
+++     return nil
+++}
+++
+++func signalHandler() {
+++     for signal := range connection.signal {
+++             if strings.Contains(signal.Name, "NameOwnerChanged") {
+++                     firewalldRunning = checkRunning()
+++                     dbusConnectionChanged(signal.Body)
+++             } else if strings.Contains(signal.Name, "Reloaded") {
+++                     reloaded()
+++             }
+++     }
+++}
+++
+++func dbusConnectionChanged(args []interface{}) {
+++     name := args[0].(string)
+++     oldOwner := args[1].(string)
+++     newOwner := args[2].(string)
+++
+++     if name != dbusInterface {
+++             return
+++     }
+++
+++     if len(newOwner) > 0 {
+++             connectionEstablished()
+++     } else if len(oldOwner) > 0 {
+++             connectionLost()
+++     }
+++}
+++
+++func connectionEstablished() {
+++     reloaded()
+++}
+++
+++func connectionLost() {
+++     // Doesn't do anything for now. Libvirt also doesn't react to this.
+++}
+++
+++// call all callbacks
+++func reloaded() {
+++     for _, pf := range onReloaded {
+++             (*pf)()
+++     }
+++}
+++
+++// OnReloaded add callback
+++func OnReloaded(callback func()) {
+++     for _, pf := range onReloaded {
+++             if pf == &callback {
+++                     return
+++             }
+++     }
+++     onReloaded = append(onReloaded, &callback)
+++}
+++
+++// Call some remote method to see whether the service is actually running.
+++func checkRunning() bool {
+++     var zone string
+++     var err error
+++
+++     if connection != nil {
+++             err = connection.sysobj.Call(dbusInterface+".getDefaultZone", 0).Store(&zone)
+++             logrus.Infof("Firewalld running: %t", err == nil)
+++             return err == nil
+++     }
+++     return false
+++}
+++
+++// Passthrough method simply passes args through to iptables/ip6tables
+++func Passthrough(ipv IPV, args ...string) ([]byte, error) {
+++     var output string
+++     logrus.Debugf("Firewalld passthrough: %s, %s", ipv, args)
+++     if err := connection.sysobj.Call(dbusInterface+".direct.passthrough", 0, ipv, args).Store(&output); err != nil {
+++             return nil, err
+++     }
+++     return []byte(output), nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..547ba7e68355642b4d8c1286f03335b77d778f27
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,83 @@@@
+++package iptables
+++
+++import (
+++     "net"
+++     "strconv"
+++     "testing"
+++)
+++
+++func TestFirewalldInit(t *testing.T) {
+++     if !checkRunning() {
+++             t.Skip("firewalld is not running")
+++     }
+++     if err := FirewalldInit(); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestReloaded(t *testing.T) {
+++     var err error
+++     var fwdChain *Chain
+++
+++     fwdChain, err = NewChain("FWD", "lo", Filter, false)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer fwdChain.Remove()
+++
+++     // copy-pasted from iptables_test:TestLink
+++     ip1 := net.ParseIP("192.168.1.1")
+++     ip2 := net.ParseIP("192.168.1.2")
+++     port := 1234
+++     proto := "tcp"
+++
+++     err = fwdChain.Link(Append, ip1, ip2, port, proto)
+++     if err != nil {
+++             t.Fatal(err)
+++     } else {
+++             // to be re-called again later
+++             OnReloaded(func() { fwdChain.Link(Append, ip1, ip2, port, proto) })
+++     }
+++
+++     rule1 := []string{
+++             "-i", fwdChain.Bridge,
+++             "-o", fwdChain.Bridge,
+++             "-p", proto,
+++             "-s", ip1.String(),
+++             "-d", ip2.String(),
+++             "--dport", strconv.Itoa(port),
+++             "-j", "ACCEPT"}
+++
+++     if !Exists(fwdChain.Table, fwdChain.Name, rule1...) {
+++             t.Fatalf("rule1 does not exist")
+++     }
+++
+++     // flush all rules
+++     fwdChain.Remove()
+++
+++     reloaded()
+++
+++     // make sure the rules have been recreated
+++     if !Exists(fwdChain.Table, fwdChain.Name, rule1...) {
+++             t.Fatalf("rule1 hasn't been recreated")
+++     }
+++}
+++
+++func TestPassthrough(t *testing.T) {
+++     rule1 := []string{
+++             "-i", "lo",
+++             "-p", "udp",
+++             "--dport", "123",
+++             "-j", "ACCEPT"}
+++
+++     if firewalldRunning {
+++             _, err := Passthrough(Iptables, append([]string{"-A"}, rule1...)...)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++             if !Exists(Filter, "INPUT", rule1...) {
+++                     t.Fatalf("rule1 does not exist")
+++             }
+++     }
+++
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..707ddb7e59a794a62d532adcb560b3e9c897e790
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,328 @@@@
+++package iptables
+++
+++import (
+++     "errors"
+++     "fmt"
+++     "net"
+++     "os/exec"
+++     "strconv"
+++     "strings"
+++     "sync"
+++
+++     "github.com/Sirupsen/logrus"
+++)
+++
+++// Action signifies the iptable action.
+++type Action string
+++
+++// Table refers to Nat, Filter or Mangle.
+++type Table string
+++
+++const (
+++     // Append appends the rule at the end of the chain.
+++     Append Action = "-A"
+++     // Delete deletes the rule from the chain.
+++     Delete Action = "-D"
+++     // Insert inserts the rule at the top of the chain.
+++     Insert Action = "-I"
+++     // Nat table is used for nat translation rules.
+++     Nat Table = "nat"
+++     // Filter table is used for filter rules.
+++     Filter Table = "filter"
+++     // Mangle table is used for mangling the packet.
+++     Mangle Table = "mangle"
+++)
+++
+++var (
+++     iptablesPath  string
+++     supportsXlock = false
+++     // used to lock iptables commands if xtables lock is not supported
+++     bestEffortLock sync.Mutex
+++     // ErrIptablesNotFound is returned when the rule is not found.
+++     ErrIptablesNotFound = errors.New("Iptables not found")
+++)
+++
+++// Chain defines the iptables chain.
+++type Chain struct {
+++     Name        string
+++     Bridge      string
+++     Table       Table
+++     HairpinMode bool
+++}
+++
+++// ChainError is returned to represent errors during ip table operation.
+++type ChainError struct {
+++     Chain  string
+++     Output []byte
+++}
+++
+++func (e ChainError) Error() string {
+++     return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output))
+++}
+++
+++func initCheck() error {
+++
+++     if iptablesPath == "" {
+++             path, err := exec.LookPath("iptables")
+++             if err != nil {
+++                     return ErrIptablesNotFound
+++             }
+++             iptablesPath = path
+++             supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil
+++     }
+++     return nil
+++}
+++
+++// NewChain adds a new chain to ip table.
+++func NewChain(name, bridge string, table Table, hairpinMode bool) (*Chain, error) {
+++     c := &Chain{
+++             Name:        name,
+++             Bridge:      bridge,
+++             Table:       table,
+++             HairpinMode: hairpinMode,
+++     }
+++
+++     if string(c.Table) == "" {
+++             c.Table = Filter
+++     }
+++
+++     // Add chain if it doesn't exist
+++     if _, err := Raw("-t", string(c.Table), "-n", "-L", c.Name); err != nil {
+++             if output, err := Raw("-t", string(c.Table), "-N", c.Name); err != nil {
+++                     return nil, err
+++             } else if len(output) != 0 {
+++                     return nil, fmt.Errorf("Could not create %s/%s chain: %s", c.Table, c.Name, output)
+++             }
+++     }
+++
+++     switch table {
+++     case Nat:
+++             preroute := []string{
+++                     "-m", "addrtype",
+++                     "--dst-type", "LOCAL",
+++                     "-j", c.Name}
+++             if !Exists(Nat, "PREROUTING", preroute...) {
+++                     if err := c.Prerouting(Append, preroute...); err != nil {
+++                             return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
+++                     }
+++             }
+++             output := []string{
+++                     "-m", "addrtype",
+++                     "--dst-type", "LOCAL",
+++                     "-j", c.Name}
+++             if !hairpinMode {
+++                     output = append(output, "!", "--dst", "127.0.0.0/8")
+++             }
+++             if !Exists(Nat, "OUTPUT", output...) {
+++                     if err := c.Output(Append, output...); err != nil {
+++                             return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
+++                     }
+++             }
+++     case Filter:
+++             link := []string{
+++                     "-o", c.Bridge,
+++                     "-j", c.Name}
+++             if !Exists(Filter, "FORWARD", link...) {
+++                     insert := append([]string{string(Insert), "FORWARD"}, link...)
+++                     if output, err := Raw(insert...); err != nil {
+++                             return nil, err
+++                     } else if len(output) != 0 {
+++                             return nil, fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output)
+++                     }
+++             }
+++     }
+++     return c, nil
+++}
+++
+++// RemoveExistingChain removes existing chain from the table.
+++func RemoveExistingChain(name string, table Table) error {
+++     c := &Chain{
+++             Name:  name,
+++             Table: table,
+++     }
+++     if string(c.Table) == "" {
+++             c.Table = Filter
+++     }
+++     return c.Remove()
+++}
+++
+++// Forward adds forwarding rule to 'filter' table and corresponding nat rule to 'nat' table.
+++func (c *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr string, destPort int) error {
+++     daddr := ip.String()
+++     if ip.IsUnspecified() {
+++             // iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we
+++             // want "0.0.0.0/0". "0/0" is correctly interpreted as "any
+++             // value" by both iptables and ip6tables.
+++             daddr = "0/0"
+++     }
+++     args := []string{"-t", string(Nat), string(action), c.Name,
+++             "-p", proto,
+++             "-d", daddr,
+++             "--dport", strconv.Itoa(port),
+++             "-j", "DNAT",
+++             "--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))}
+++     if !c.HairpinMode {
+++             args = append(args, "!", "-i", c.Bridge)
+++     }
+++     if output, err := Raw(args...); err != nil {
+++             return err
+++     } else if len(output) != 0 {
+++             return ChainError{Chain: "FORWARD", Output: output}
+++     }
+++
+++     if output, err := Raw("-t", string(Filter), string(action), c.Name,
+++             "!", "-i", c.Bridge,
+++             "-o", c.Bridge,
+++             "-p", proto,
+++             "-d", destAddr,
+++             "--dport", strconv.Itoa(destPort),
+++             "-j", "ACCEPT"); err != nil {
+++             return err
+++     } else if len(output) != 0 {
+++             return ChainError{Chain: "FORWARD", Output: output}
+++     }
+++
+++     if output, err := Raw("-t", string(Nat), string(action), "POSTROUTING",
+++             "-p", proto,
+++             "-s", destAddr,
+++             "-d", destAddr,
+++             "--dport", strconv.Itoa(destPort),
+++             "-j", "MASQUERADE"); err != nil {
+++             return err
+++     } else if len(output) != 0 {
+++             return ChainError{Chain: "FORWARD", Output: output}
+++     }
+++
+++     return nil
+++}
+++
+++// Link adds reciprocal ACCEPT rule for two supplied IP addresses.
+++// Traffic is allowed from ip1 to ip2 and vice-versa
+++func (c *Chain) Link(action Action, ip1, ip2 net.IP, port int, proto string) error {
+++     if output, err := Raw("-t", string(Filter), string(action), c.Name,
+++             "-i", c.Bridge, "-o", c.Bridge,
+++             "-p", proto,
+++             "-s", ip1.String(),
+++             "-d", ip2.String(),
+++             "--dport", strconv.Itoa(port),
+++             "-j", "ACCEPT"); err != nil {
+++             return err
+++     } else if len(output) != 0 {
+++             return fmt.Errorf("Error iptables forward: %s", output)
+++     }
+++     if output, err := Raw("-t", string(Filter), string(action), c.Name,
+++             "-i", c.Bridge, "-o", c.Bridge,
+++             "-p", proto,
+++             "-s", ip2.String(),
+++             "-d", ip1.String(),
+++             "--sport", strconv.Itoa(port),
+++             "-j", "ACCEPT"); err != nil {
+++             return err
+++     } else if len(output) != 0 {
+++             return fmt.Errorf("Error iptables forward: %s", output)
+++     }
+++     return nil
+++}
+++
+++// Prerouting adds linking rule to nat/PREROUTING chain.
+++func (c *Chain) Prerouting(action Action, args ...string) error {
+++     a := []string{"-t", string(Nat), string(action), "PREROUTING"}
+++     if len(args) > 0 {
+++             a = append(a, args...)
+++     }
+++     if output, err := Raw(a...); err != nil {
+++             return err
+++     } else if len(output) != 0 {
+++             return ChainError{Chain: "PREROUTING", Output: output}
+++     }
+++     return nil
+++}
+++
+++// Output adds linking rule to an OUTPUT chain.
+++func (c *Chain) Output(action Action, args ...string) error {
+++     a := []string{"-t", string(c.Table), string(action), "OUTPUT"}
+++     if len(args) > 0 {
+++             a = append(a, args...)
+++     }
+++     if output, err := Raw(a...); err != nil {
+++             return err
+++     } else if len(output) != 0 {
+++             return ChainError{Chain: "OUTPUT", Output: output}
+++     }
+++     return nil
+++}
+++
+++// Remove removes the chain.
+++func (c *Chain) Remove() error {
+++     // Ignore errors - This could mean the chains were never set up
+++     if c.Table == Nat {
+++             c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name)
+++             c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", c.Name)
+++             c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name) // Created in versions <= 0.1.6
+++
+++             c.Prerouting(Delete)
+++             c.Output(Delete)
+++     }
+++     Raw("-t", string(c.Table), "-F", c.Name)
+++     Raw("-t", string(c.Table), "-X", c.Name)
+++     return nil
+++}
+++
+++// Exists checks if a rule exists
+++func Exists(table Table, chain string, rule ...string) bool {
+++     if string(table) == "" {
+++             table = Filter
+++     }
+++
+++     // iptables -C, --check option was added in v.1.4.11
+++     // http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt
+++
+++     // try -C
+++     // if exit status is 0 then return true, the rule exists
+++     if _, err := Raw(append([]string{
+++             "-t", string(table), "-C", chain}, rule...)...); err == nil {
+++             return true
+++     }
+++
+++     // parse "iptables -S" for the rule (this checks rules in a specific chain
+++     // in a specific table)
+++     ruleString := strings.Join(rule, " ")
+++     existingRules, _ := exec.Command(iptablesPath, "-t", string(table), "-S", chain).Output()
+++
+++     return strings.Contains(string(existingRules), ruleString)
+++}
+++
+++// Raw calls 'iptables' system command, passing supplied arguments.
+++func Raw(args ...string) ([]byte, error) {
+++     if firewalldRunning {
+++             output, err := Passthrough(Iptables, args...)
+++             if err == nil || !strings.Contains(err.Error(), "was not provided by any .service files") {
+++                     return output, err
+++             }
+++
+++     }
+++
+++     if err := initCheck(); err != nil {
+++             return nil, err
+++     }
+++     if supportsXlock {
+++             args = append([]string{"--wait"}, args...)
+++     } else {
+++             bestEffortLock.Lock()
+++             defer bestEffortLock.Unlock()
+++     }
+++
+++     logrus.Debugf("%s, %v", iptablesPath, args)
+++
+++     output, err := exec.Command(iptablesPath, args...).CombinedOutput()
+++     if err != nil {
+++             return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err)
+++     }
+++
+++     // ignore iptables' message about xtables lock
+++     if strings.Contains(string(output), "waiting for it to exit") {
+++             output = []byte("")
+++     }
+++
+++     return output, err
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..63d931c8ab51488760515c604e316ed64d286b08
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,230 @@@@
+++package iptables
+++
+++import (
+++     "net"
+++     "os/exec"
+++     "strconv"
+++     "strings"
+++     "sync"
+++     "testing"
+++
+++     _ "github.com/docker/libnetwork/netutils"
+++)
+++
+++const chainName = "DOCKEREST"
+++
+++var natChain *Chain
+++var filterChain *Chain
+++
+++func TestNewChain(t *testing.T) {
+++     var err error
+++
+++     natChain, err = NewChain(chainName, "lo", Nat, false)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     filterChain, err = NewChain(chainName, "lo", Filter, false)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestForward(t *testing.T) {
+++     ip := net.ParseIP("192.168.1.1")
+++     port := 1234
+++     dstAddr := "172.17.0.1"
+++     dstPort := 4321
+++     proto := "tcp"
+++
+++     err := natChain.Forward(Insert, ip, port, proto, dstAddr, dstPort)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     dnatRule := []string{
+++             "-d", ip.String(),
+++             "-p", proto,
+++             "--dport", strconv.Itoa(port),
+++             "-j", "DNAT",
+++             "--to-destination", dstAddr + ":" + strconv.Itoa(dstPort),
+++             "!", "-i", natChain.Bridge,
+++     }
+++
+++     if !Exists(natChain.Table, natChain.Name, dnatRule...) {
+++             t.Fatalf("DNAT rule does not exist")
+++     }
+++
+++     filterRule := []string{
+++             "!", "-i", filterChain.Bridge,
+++             "-o", filterChain.Bridge,
+++             "-d", dstAddr,
+++             "-p", proto,
+++             "--dport", strconv.Itoa(dstPort),
+++             "-j", "ACCEPT",
+++     }
+++
+++     if !Exists(filterChain.Table, filterChain.Name, filterRule...) {
+++             t.Fatalf("filter rule does not exist")
+++     }
+++
+++     masqRule := []string{
+++             "-d", dstAddr,
+++             "-s", dstAddr,
+++             "-p", proto,
+++             "--dport", strconv.Itoa(dstPort),
+++             "-j", "MASQUERADE",
+++     }
+++
+++     if !Exists(natChain.Table, "POSTROUTING", masqRule...) {
+++             t.Fatalf("MASQUERADE rule does not exist")
+++     }
+++}
+++
+++func TestLink(t *testing.T) {
+++     var err error
+++
+++     ip1 := net.ParseIP("192.168.1.1")
+++     ip2 := net.ParseIP("192.168.1.2")
+++     port := 1234
+++     proto := "tcp"
+++
+++     err = filterChain.Link(Append, ip1, ip2, port, proto)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     rule1 := []string{
+++             "-i", filterChain.Bridge,
+++             "-o", filterChain.Bridge,
+++             "-p", proto,
+++             "-s", ip1.String(),
+++             "-d", ip2.String(),
+++             "--dport", strconv.Itoa(port),
+++             "-j", "ACCEPT"}
+++
+++     if !Exists(filterChain.Table, filterChain.Name, rule1...) {
+++             t.Fatalf("rule1 does not exist")
+++     }
+++
+++     rule2 := []string{
+++             "-i", filterChain.Bridge,
+++             "-o", filterChain.Bridge,
+++             "-p", proto,
+++             "-s", ip2.String(),
+++             "-d", ip1.String(),
+++             "--sport", strconv.Itoa(port),
+++             "-j", "ACCEPT"}
+++
+++     if !Exists(filterChain.Table, filterChain.Name, rule2...) {
+++             t.Fatalf("rule2 does not exist")
+++     }
+++}
+++
+++func TestPrerouting(t *testing.T) {
+++     args := []string{
+++             "-i", "lo",
+++             "-d", "192.168.1.1"}
+++
+++     err := natChain.Prerouting(Insert, args...)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if !Exists(natChain.Table, "PREROUTING", args...) {
+++             t.Fatalf("rule does not exist")
+++     }
+++
+++     delRule := append([]string{"-D", "PREROUTING", "-t", string(Nat)}, args...)
+++     if _, err = Raw(delRule...); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestOutput(t *testing.T) {
+++     args := []string{
+++             "-o", "lo",
+++             "-d", "192.168.1.1"}
+++
+++     err := natChain.Output(Insert, args...)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if !Exists(natChain.Table, "OUTPUT", args...) {
+++             t.Fatalf("rule does not exist")
+++     }
+++
+++     delRule := append([]string{"-D", "OUTPUT", "-t",
+++             string(natChain.Table)}, args...)
+++     if _, err = Raw(delRule...); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestConcurrencyWithWait(t *testing.T) {
+++     RunConcurrencyTest(t, true)
+++}
+++
+++func TestConcurrencyNoWait(t *testing.T) {
+++     RunConcurrencyTest(t, false)
+++}
+++
+++// Runs 10 concurrent rule additions. This will fail if iptables
+++// is actually invoked simultaneously without --wait.
+++// Note that if iptables does not support the xtable lock on this
+++// system, then allowXlock has no effect -- it will always be off.
+++func RunConcurrencyTest(t *testing.T, allowXlock bool) {
+++     var wg sync.WaitGroup
+++
+++     if !allowXlock && supportsXlock {
+++             supportsXlock = false
+++             defer func() { supportsXlock = true }()
+++     }
+++
+++     ip := net.ParseIP("192.168.1.1")
+++     port := 1234
+++     dstAddr := "172.17.0.1"
+++     dstPort := 4321
+++     proto := "tcp"
+++
+++     for i := 0; i < 10; i++ {
+++             wg.Add(1)
+++             go func() {
+++                     defer wg.Done()
+++                     err := natChain.Forward(Append, ip, port, proto, dstAddr, dstPort)
+++                     if err != nil {
+++                             t.Fatal(err)
+++                     }
+++             }()
+++     }
+++     wg.Wait()
+++}
+++
+++func TestCleanup(t *testing.T) {
+++     var err error
+++     var rules []byte
+++
+++     // Cleanup filter/FORWARD first otherwise output of iptables-save is dirty
+++     link := []string{"-t", string(filterChain.Table),
+++             string(Delete), "FORWARD",
+++             "-o", filterChain.Bridge,
+++             "-j", filterChain.Name}
+++     if _, err = Raw(link...); err != nil {
+++             t.Fatal(err)
+++     }
+++     filterChain.Remove()
+++
+++     err = RemoveExistingChain(chainName, Nat)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     rules, err = exec.Command("iptables-save").Output()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if strings.Contains(string(rules), chainName) {
+++             t.Fatalf("Removing chain failed. %s found in iptables-save", chainName)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..317afbb7f322ebe0d50b34a782680accacfc1e43
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,32 @@@@
+++package libnetwork
+++
+++import (
+++     "testing"
+++
+++     "github.com/docker/libnetwork/datastore"
+++     "github.com/docker/libnetwork/driverapi"
+++)
+++
+++func TestDriverRegistration(t *testing.T) {
+++     bridgeNetType := "bridge"
+++     c, err := New()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     err = c.(*controller).RegisterDriver(bridgeNetType, nil, driverapi.Capability{})
+++     if err == nil {
+++             t.Fatalf("Expecting the RegisterDriver to fail for %s", bridgeNetType)
+++     }
+++     if _, ok := err.(driverapi.ErrActiveRegistration); !ok {
+++             t.Fatalf("Failed for unexpected reason: %v", err)
+++     }
+++     err = c.(*controller).RegisterDriver("test-dummy", nil, driverapi.Capability{})
+++     if err != nil {
+++             t.Fatalf("Test failed with an error %v", err)
+++     }
+++}
+++
+++func SetTestDataStore(c NetworkController, custom datastore.DataStore) {
+++     con := c.(*controller)
+++     con.store = custom
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ec3d194664314f76fb78452e083e97e02a0b142b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,2115 @@@@
+++package libnetwork_test
+++
+++import (
+++     "bytes"
+++     "flag"
+++     "fmt"
+++     "io/ioutil"
+++     "net"
+++     "net/http"
+++     "net/http/httptest"
+++     "os"
+++     "runtime"
+++     "strconv"
+++     "sync"
+++     "testing"
+++
+++     log "github.com/Sirupsen/logrus"
+++     "github.com/docker/docker/pkg/plugins"
+++     "github.com/docker/docker/pkg/reexec"
+++     "github.com/docker/libnetwork"
+++     "github.com/docker/libnetwork/datastore"
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/netlabel"
+++     "github.com/docker/libnetwork/netutils"
+++     "github.com/docker/libnetwork/options"
+++     "github.com/docker/libnetwork/sandbox"
+++     "github.com/docker/libnetwork/types"
+++     "github.com/vishvananda/netlink"
+++     "github.com/vishvananda/netns"
+++)
+++
+++const (
+++     bridgeNetType = "bridge"
+++)
+++
+++var controller libnetwork.NetworkController
+++
+++func TestMain(m *testing.M) {
+++     if reexec.Init() {
+++             return
+++     }
+++
+++     if err := createController(); err != nil {
+++             os.Exit(1)
+++     }
+++     option := options.Generic{
+++             "EnableIPForwarding": true,
+++     }
+++
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = option
+++
+++     err := controller.ConfigureNetworkDriver(bridgeNetType, genericOption)
+++     if err != nil {
+++             //m.Fatal(err)
+++             os.Exit(1)
+++     }
+++
+++     libnetwork.SetTestDataStore(controller, datastore.NewCustomDataStore(datastore.NewMockStore()))
+++
+++     os.Exit(m.Run())
+++}
+++
+++func createController() error {
+++     var err error
+++
+++     controller, err = libnetwork.New()
+++     if err != nil {
+++             return err
+++     }
+++
+++     return nil
+++}
+++
+++func createTestNetwork(networkType, networkName string, netOption options.Generic) (libnetwork.Network, error) {
+++     network, err := controller.NewNetwork(networkType, networkName,
+++             libnetwork.NetworkOptionGeneric(netOption))
+++     if err != nil {
+++             return nil, err
+++     }
+++
+++     return network, nil
+++}
+++
+++func getEmptyGenericOption() map[string]interface{} {
+++     genericOption := make(map[string]interface{})
+++     genericOption[netlabel.GenericData] = options.Generic{}
+++     return genericOption
+++}
+++
+++func getPortMapping() []types.PortBinding {
+++     return []types.PortBinding{
+++             types.PortBinding{Proto: types.TCP, Port: uint16(230), HostPort: uint16(23000)},
+++             types.PortBinding{Proto: types.UDP, Port: uint16(200), HostPort: uint16(22000)},
+++             types.PortBinding{Proto: types.TCP, Port: uint16(120), HostPort: uint16(12000)},
+++     }
+++}
+++
+++func TestNull(t *testing.T) {
+++     network, err := createTestNetwork("null", "testnull", options.Generic{})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     ep, err := network.CreateEndpoint("testep")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = ep.Join("null_container",
+++             libnetwork.JoinOptionHostname("test"),
+++             libnetwork.JoinOptionDomainname("docker.io"),
+++             libnetwork.JoinOptionExtraHost("web", "192.168.0.1"))
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = ep.Leave("null_container")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := ep.Delete(); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     // host type is special network. Cannot be removed.
+++     err = network.Delete()
+++     if err == nil {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(types.ForbiddenError); !ok {
+++             t.Fatalf("Unexpected error type")
+++     }
+++}
+++
+++func TestHost(t *testing.T) {
+++     network, err := createTestNetwork("host", "testhost", options.Generic{})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     ep1, err := network.CreateEndpoint("testep1")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = ep1.Join("host_container1",
+++             libnetwork.JoinOptionHostname("test1"),
+++             libnetwork.JoinOptionDomainname("docker.io"),
+++             libnetwork.JoinOptionExtraHost("web", "192.168.0.1"),
+++             libnetwork.JoinOptionUseDefaultSandbox())
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     ep2, err := network.CreateEndpoint("testep2")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = ep2.Join("host_container2",
+++             libnetwork.JoinOptionHostname("test2"),
+++             libnetwork.JoinOptionDomainname("docker.io"),
+++             libnetwork.JoinOptionExtraHost("web", "192.168.0.1"),
+++             libnetwork.JoinOptionUseDefaultSandbox())
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = ep1.Leave("host_container1")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = ep2.Leave("host_container2")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := ep1.Delete(); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := ep2.Delete(); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     // Try to create another host endpoint and join/leave that.
+++     ep3, err := network.CreateEndpoint("testep3")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = ep3.Join("host_container3",
+++             libnetwork.JoinOptionHostname("test3"),
+++             libnetwork.JoinOptionDomainname("docker.io"),
+++             libnetwork.JoinOptionExtraHost("web", "192.168.0.1"),
+++             libnetwork.JoinOptionUseDefaultSandbox())
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = ep3.Leave("host_container3")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := ep3.Delete(); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     // host type is special network. Cannot be removed.
+++     err = network.Delete()
+++     if err == nil {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(types.ForbiddenError); !ok {
+++             t.Fatalf("Unexpected error type")
+++     }
+++}
+++
+++func TestBridge(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     ip, subnet, err := net.ParseCIDR("192.168.100.1/24")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     subnet.IP = ip
+++
+++     ip, cidr, err := net.ParseCIDR("192.168.100.2/28")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     cidr.IP = ip
+++
+++     ip, cidrv6, err := net.ParseCIDR("fe90::1/96")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     cidrv6.IP = ip
+++
+++     log.Debug("Adding a bridge")
+++
+++     netOption := options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "testnetwork",
+++                     "AddressIPv4":           subnet,
+++                     "FixedCIDR":             cidr,
+++                     "FixedCIDRv6":           cidrv6,
+++                     "EnableIPv6":            true,
+++                     "EnableIPTables":        true,
+++                     "EnableIPMasquerade":    true,
+++                     "EnableICC":             true,
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++
+++     network, err := createTestNetwork(bridgeNetType, "testnetwork", netOption)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     ep, err := network.CreateEndpoint("testep", libnetwork.CreateOptionPortMapping(getPortMapping()))
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     epInfo, err := ep.DriverInfo()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     pmd, ok := epInfo[netlabel.PortMap]
+++     if !ok {
+++             t.Fatalf("Could not find expected info in endpoint data")
+++     }
+++     pm, ok := pmd.([]types.PortBinding)
+++     if !ok {
+++             t.Fatalf("Unexpected format for port mapping in endpoint operational data")
+++     }
+++     if len(pm) != 3 {
+++             t.Fatalf("Incomplete data for port mapping in endpoint operational data: %d", len(pm))
+++     }
+++
+++     if err := ep.Delete(); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := network.Delete(); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestUnknownDriver(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     _, err := createTestNetwork("unknowndriver", "testnetwork", options.Generic{})
+++     if err == nil {
+++             t.Fatal("Expected to fail. But instead succeeded")
+++     }
+++
+++     if _, ok := err.(types.NotFoundError); !ok {
+++             t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+++     }
+++}
+++
+++func TestNilRemoteDriver(t *testing.T) {
+++     _, err := controller.NewNetwork("framerelay", "dummy",
+++             libnetwork.NetworkOptionGeneric(getEmptyGenericOption()))
+++     if err == nil {
+++             t.Fatal("Expected to fail. But instead succeeded")
+++     }
+++
+++     if _, ok := err.(types.NotFoundError); !ok {
+++             t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+++     }
+++}
+++
+++func TestDuplicateNetwork(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     // Creating a default bridge name network (can't be removed)
+++     _, err := controller.NewNetwork(bridgeNetType, "testdup")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     _, err = controller.NewNetwork(bridgeNetType, "testdup")
+++     if err == nil {
+++             t.Fatal("Expected to fail. But instead succeeded")
+++     }
+++
+++     if _, ok := err.(libnetwork.NetworkNameError); !ok {
+++             t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+++     }
+++}
+++
+++func TestNetworkName(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     netOption := options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "testnetwork",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++
+++     _, err := createTestNetwork(bridgeNetType, "", netOption)
+++     if err == nil {
+++             t.Fatal("Expected to fail. But instead succeeded")
+++     }
+++
+++     if _, ok := err.(libnetwork.ErrInvalidName); !ok {
+++             t.Fatalf("Expected to fail with ErrInvalidName error. Got %v", err)
+++     }
+++
+++     networkName := "testnetwork"
+++     n, err := createTestNetwork(bridgeNetType, networkName, netOption)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := n.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     if n.Name() != networkName {
+++             t.Fatalf("Expected network name %s, got %s", networkName, n.Name())
+++     }
+++}
+++
+++func TestNetworkType(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     netOption := options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "testnetwork",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++
+++     n, err := createTestNetwork(bridgeNetType, "testnetwork", netOption)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := n.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     if n.Type() != bridgeNetType {
+++             t.Fatalf("Expected network type %s, got %s", bridgeNetType, n.Type())
+++     }
+++}
+++
+++func TestNetworkID(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     netOption := options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "testnetwork",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++
+++     n, err := createTestNetwork(bridgeNetType, "testnetwork", netOption)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := n.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     if n.ID() == "" {
+++             t.Fatal("Expected non-empty network id")
+++     }
+++}
+++
+++func TestDeleteNetworkWithActiveEndpoints(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     netOption := options.Generic{
+++             "BridgeName":            "testnetwork",
+++             "AllowNonDefaultBridge": true}
+++     option := options.Generic{
+++             netlabel.GenericData: netOption,
+++     }
+++
+++     network, err := createTestNetwork(bridgeNetType, "testnetwork", option)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     ep, err := network.CreateEndpoint("testep")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = network.Delete()
+++     if err == nil {
+++             t.Fatal("Expected to fail. But instead succeeded")
+++     }
+++
+++     if _, ok := err.(*libnetwork.ActiveEndpointsError); !ok {
+++             t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+++     }
+++
+++     // Done testing. Now cleanup.
+++     if err := ep.Delete(); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := network.Delete(); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestUnknownNetwork(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     netOption := options.Generic{
+++             "BridgeName":            "testnetwork",
+++             "AllowNonDefaultBridge": true}
+++     option := options.Generic{
+++             netlabel.GenericData: netOption,
+++     }
+++
+++     network, err := createTestNetwork(bridgeNetType, "testnetwork", option)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = network.Delete()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = network.Delete()
+++     if err == nil {
+++             t.Fatal("Expected to fail. But instead succeeded")
+++     }
+++
+++     if _, ok := err.(*libnetwork.UnknownNetworkError); !ok {
+++             t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+++     }
+++}
+++
+++func TestUnknownEndpoint(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     ip, subnet, err := net.ParseCIDR("192.168.100.1/24")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     subnet.IP = ip
+++
+++     netOption := options.Generic{
+++             "BridgeName":            "testnetwork",
+++             "AddressIPv4":           subnet,
+++             "AllowNonDefaultBridge": true}
+++     option := options.Generic{
+++             netlabel.GenericData: netOption,
+++     }
+++
+++     network, err := createTestNetwork(bridgeNetType, "testnetwork", option)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     _, err = network.CreateEndpoint("")
+++     if err == nil {
+++             t.Fatal("Expected to fail. But instead succeeded")
+++     }
+++     if _, ok := err.(libnetwork.ErrInvalidName); !ok {
+++             t.Fatalf("Expected to fail with ErrInvalidName error. Actual error: %v", err)
+++     }
+++
+++     ep, err := network.CreateEndpoint("testep")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = ep.Delete()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     // Done testing. Now cleanup
+++     if err := network.Delete(); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestNetworkEndpointsWalkers(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     // Create network 1 and add 2 endpoint: ep11, ep12
+++     netOption := options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "network1",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++
+++     net1, err := createTestNetwork(bridgeNetType, "network1", netOption)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := net1.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep11, err := net1.CreateEndpoint("ep11")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep11.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep12, err := net1.CreateEndpoint("ep12")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep12.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     // Test list methods on net1
+++     epList1 := net1.Endpoints()
+++     if len(epList1) != 2 {
+++             t.Fatalf("Endpoints() returned wrong number of elements: %d instead of 2", len(epList1))
+++     }
+++     // endpoint order is not guaranteed
+++     for _, e := range epList1 {
+++             if e != ep11 && e != ep12 {
+++                     t.Fatal("Endpoints() did not return all the expected elements")
+++             }
+++     }
+++
+++     // Test Endpoint Walk method
+++     var epName string
+++     var epWanted libnetwork.Endpoint
+++     wlk := func(ep libnetwork.Endpoint) bool {
+++             if ep.Name() == epName {
+++                     epWanted = ep
+++                     return true
+++             }
+++             return false
+++     }
+++
+++     // Look for ep1 on network1
+++     epName = "ep11"
+++     net1.WalkEndpoints(wlk)
+++     if epWanted == nil {
+++             t.Fatal(err)
+++     }
+++     if ep11 != epWanted {
+++             t.Fatal(err)
+++     }
+++
+++     current := len(controller.Networks())
+++
+++     // Create network 2
+++     netOption = options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "network2",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++
+++     net2, err := createTestNetwork(bridgeNetType, "network2", netOption)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := net2.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     // Test Networks method
+++     if len(controller.Networks()) != current+1 {
+++             t.Fatalf("Did not find the expected number of networks")
+++     }
+++
+++     // Test Network Walk method
+++     var netName string
+++     var netWanted libnetwork.Network
+++     nwWlk := func(nw libnetwork.Network) bool {
+++             if nw.Name() == netName {
+++                     netWanted = nw
+++                     return true
+++             }
+++             return false
+++     }
+++
+++     // Look for network named "network1" and "network2"
+++     netName = "network1"
+++     controller.WalkNetworks(nwWlk)
+++     if netWanted == nil {
+++             t.Fatal(err)
+++     }
+++     if net1 != netWanted {
+++             t.Fatal(err)
+++     }
+++
+++     netName = "network2"
+++     controller.WalkNetworks(nwWlk)
+++     if netWanted == nil {
+++             t.Fatal(err)
+++     }
+++     if net2 != netWanted {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestDuplicateEndpoint(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     netOption := options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "testnetwork",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++     n, err := createTestNetwork(bridgeNetType, "testnetwork", netOption)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := n.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep, err := n.CreateEndpoint("ep1")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep2, err := n.CreateEndpoint("ep1")
+++     defer func() {
+++             // Cleanup ep2 as well, else network cleanup might fail for failure cases
+++             if ep2 != nil {
+++                     if err := ep2.Delete(); err != nil {
+++                             t.Fatal(err)
+++                     }
+++             }
+++     }()
+++
+++     if err == nil {
+++             t.Fatal("Expected to fail. But instead succeeded")
+++     }
+++
+++     if _, ok := err.(types.ForbiddenError); !ok {
+++             t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+++     }
+++}
+++
+++func TestControllerQuery(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     // Create network 1
+++     netOption := options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "network1",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++     net1, err := createTestNetwork(bridgeNetType, "network1", netOption)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := net1.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     // Create network 2
+++     netOption = options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "network2",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++     net2, err := createTestNetwork(bridgeNetType, "network2", netOption)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := net2.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     _, err = controller.NetworkByName("")
+++     if err == nil {
+++             t.Fatalf("NetworkByName() succeeded with invalid target name")
+++     }
+++     if _, ok := err.(libnetwork.ErrInvalidName); !ok {
+++             t.Fatalf("Expected NetworkByName() to fail with ErrInvalidName error. Got: %v", err)
+++     }
+++
+++     _, err = controller.NetworkByID("")
+++     if err == nil {
+++             t.Fatalf("NetworkByID() succeeded with invalid target id")
+++     }
+++     if _, ok := err.(libnetwork.ErrInvalidID); !ok {
+++             t.Fatalf("NetworkByID() failed with unexpected error: %v", err)
+++     }
+++
+++     g, err := controller.NetworkByID("network1")
+++     if err == nil {
+++             t.Fatalf("Unexpected success for NetworkByID(): %v", g)
+++     }
+++     if _, ok := err.(libnetwork.ErrNoSuchNetwork); !ok {
+++             t.Fatalf("NetworkByID() failed with unexpected error: %v", err)
+++     }
+++
+++     g, err = controller.NetworkByName("network1")
+++     if err != nil {
+++             t.Fatalf("Unexpected failure for NetworkByName(): %v", err)
+++     }
+++     if g == nil {
+++             t.Fatalf("NetworkByName() did not find the network")
+++     }
+++
+++     if g != net1 {
+++             t.Fatalf("NetworkByName() returned the wrong network")
+++     }
+++
+++     g, err = controller.NetworkByID(net1.ID())
+++     if err != nil {
+++             t.Fatalf("Unexpected failure for NetworkByID(): %v", err)
+++     }
+++     if net1 != g {
+++             t.Fatalf("NetworkByID() returned unexpected element: %v", g)
+++     }
+++
+++     g, err = controller.NetworkByName("network2")
+++     if err != nil {
+++             t.Fatalf("Unexpected failure for NetworkByName(): %v", err)
+++     }
+++     if g == nil {
+++             t.Fatalf("NetworkByName() did not find the network")
+++     }
+++
+++     if g != net2 {
+++             t.Fatalf("NetworkByName() returned the wrong network")
+++     }
+++
+++     g, err = controller.NetworkByID(net2.ID())
+++     if err != nil {
+++             t.Fatalf("Unexpected failure for NetworkByID(): %v", err)
+++     }
+++     if net2 != g {
+++             t.Fatalf("NetworkByID() returned unexpected element: %v", g)
+++     }
+++}
+++
+++func TestNetworkQuery(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     // Create network 1 and add 2 endpoint: ep11, ep12
+++     netOption := options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "network1",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++     net1, err := createTestNetwork(bridgeNetType, "network1", netOption)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := net1.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep11, err := net1.CreateEndpoint("ep11")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep11.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep12, err := net1.CreateEndpoint("ep12")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep12.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     e, err := net1.EndpointByName("ep11")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if ep11 != e {
+++             t.Fatalf("EndpointByName() returned %v instead of %v", e, ep11)
+++     }
+++
+++     e, err = net1.EndpointByName("")
+++     if err == nil {
+++             t.Fatalf("EndpointByName() succeeded with invalid target name")
+++     }
+++     if _, ok := err.(libnetwork.ErrInvalidName); !ok {
+++             t.Fatalf("Expected EndpointByName() to fail with ErrInvalidName error. Got: %v", err)
+++     }
+++
+++     e, err = net1.EndpointByName("IamNotAnEndpoint")
+++     if err == nil {
+++             t.Fatalf("EndpointByName() succeeded with unknown target name")
+++     }
+++     if _, ok := err.(libnetwork.ErrNoSuchEndpoint); !ok {
+++             t.Fatal(err)
+++     }
+++     if e != nil {
+++             t.Fatalf("EndpointByName(): expected nil, got %v", e)
+++     }
+++
+++     e, err = net1.EndpointByID(ep12.ID())
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if ep12 != e {
+++             t.Fatalf("EndpointByID() returned %v instead of %v", e, ep12)
+++     }
+++
+++     e, err = net1.EndpointByID("")
+++     if err == nil {
+++             t.Fatalf("EndpointByID() succeeded with invalid target id")
+++     }
+++     if _, ok := err.(libnetwork.ErrInvalidID); !ok {
+++             t.Fatalf("EndpointByID() failed with unexpected error: %v", err)
+++     }
+++}
+++
+++const containerID = "valid_container"
+++
+++func checkSandbox(t *testing.T, info libnetwork.EndpointInfo) {
+++     origns, err := netns.Get()
+++     if err != nil {
+++             t.Fatalf("Could not get the current netns: %v", err)
+++     }
+++     defer origns.Close()
+++
+++     key := info.SandboxKey()
+++     f, err := os.OpenFile(key, os.O_RDONLY, 0)
+++     if err != nil {
+++             t.Fatalf("Failed to open network namespace path %q: %v", key, err)
+++     }
+++     defer f.Close()
+++
+++     runtime.LockOSThread()
+++     defer runtime.UnlockOSThread()
+++
+++     nsFD := f.Fd()
+++     if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
+++             t.Fatalf("Setting to the namespace pointed to by the sandbox %s failed: %v", key, err)
+++     }
+++     defer netns.Set(origns)
+++
+++     _, err = netlink.LinkByName("eth0")
+++     if err != nil {
+++             t.Fatalf("Could not find the interface eth0 inside the sandbox: %v", err)
+++     }
+++
+++     _, err = netlink.LinkByName("eth1")
+++     if err != nil {
+++             t.Fatalf("Could not find the interface eth1 inside the sandbox: %v", err)
+++     }
+++}
+++
+++func TestEndpointJoin(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     // Create network 1 and add 2 endpoint: ep11, ep12
+++     n1, err := createTestNetwork(bridgeNetType, "testnetwork1", options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "testnetwork1",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     })
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := n1.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep1, err := n1.CreateEndpoint("ep1")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep1.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     // Validate if ep.Info() only gives me IP address info and not names and gateway during CreateEndpoint()
+++     info := ep1.Info()
+++
+++     for _, iface := range info.InterfaceList() {
+++             if iface.Address().IP.To4() == nil {
+++                     t.Fatalf("Invalid IP address returned: %v", iface.Address())
+++             }
+++     }
+++
+++     if info.Gateway().To4() != nil {
+++             t.Fatalf("Expected empty gateway for an empty endpoint. Instead found a gateway: %v", info.Gateway())
+++     }
+++
+++     if info.SandboxKey() != "" {
+++             t.Fatalf("Expected an empty sandbox key for an empty endpoint. Instead found a non-empty sandbox key: %s", info.SandboxKey())
+++     }
+++
+++     defer controller.LeaveAll(containerID)
+++
+++     err = ep1.Join(containerID,
+++             libnetwork.JoinOptionHostname("test"),
+++             libnetwork.JoinOptionDomainname("docker.io"),
+++             libnetwork.JoinOptionExtraHost("web", "192.168.0.1"))
+++     runtime.LockOSThread()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             err = ep1.Leave(containerID)
+++             runtime.LockOSThread()
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     // Validate if ep.Info() only gives valid gateway and sandbox key after has container has joined.
+++     info = ep1.Info()
+++     if info.Gateway().To4() == nil {
+++             t.Fatalf("Expected a valid gateway for a joined endpoint. Instead found an invalid gateway: %v", info.Gateway())
+++     }
+++
+++     if info.SandboxKey() == "" {
+++             t.Fatalf("Expected an non-empty sandbox key for a joined endpoint. Instead found a empty sandbox key")
+++     }
+++
+++     // Check endpoint provided container information
+++     if ep1.ContainerInfo().ID() != containerID {
+++             t.Fatalf("Endpoint ContainerInfo returned unexpected id: %s", ep1.ContainerInfo().ID())
+++     }
+++
+++     // Attempt retrieval of endpoint interfaces statistics
+++     stats, err := ep1.Statistics()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if _, ok := stats["eth0"]; !ok {
+++             t.Fatalf("Did not find eth0 statistics")
+++     }
+++
+++     // Now test the container joining another network
+++     n2, err := createTestNetwork(bridgeNetType, "testnetwork2",
+++             options.Generic{
+++                     netlabel.GenericData: options.Generic{
+++                             "BridgeName":            "testnetwork2",
+++                             "AllowNonDefaultBridge": true,
+++                     },
+++             })
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := n2.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep2, err := n2.CreateEndpoint("ep2")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep2.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     err = ep2.Join(containerID)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     runtime.LockOSThread()
+++     defer func() {
+++             err = ep2.Leave(containerID)
+++             runtime.LockOSThread()
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     if ep1.ContainerInfo().ID() != ep2.ContainerInfo().ID() {
+++             t.Fatalf("ep1 and ep2 returned different container info")
+++     }
+++
+++     checkSandbox(t, info)
+++
+++}
+++
+++func TestEndpointJoinInvalidContainerId(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "testnetwork",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     })
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := n.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep, err := n.CreateEndpoint("ep1")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     err = ep.Join("")
+++     if err == nil {
+++             t.Fatal("Expected to fail join with empty container id string")
+++     }
+++
+++     if _, ok := err.(libnetwork.InvalidContainerIDError); !ok {
+++             t.Fatalf("Failed for unexpected reason: %v", err)
+++     }
+++}
+++
+++func TestEndpointDeleteWithActiveContainer(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "testnetwork",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     })
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := n.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep, err := n.CreateEndpoint("ep1")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             err = ep.Delete()
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     defer controller.LeaveAll(containerID)
+++
+++     err = ep.Join(containerID,
+++             libnetwork.JoinOptionHostname("test"),
+++             libnetwork.JoinOptionDomainname("docker.io"),
+++             libnetwork.JoinOptionExtraHost("web", "192.168.0.1"))
+++     runtime.LockOSThread()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             err = ep.Leave(containerID)
+++             runtime.LockOSThread()
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     err = ep.Delete()
+++     if err == nil {
+++             t.Fatal("Expected to fail. But instead succeeded")
+++     }
+++
+++     if _, ok := err.(*libnetwork.ActiveContainerError); !ok {
+++             t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+++     }
+++}
+++
+++func TestEndpointMultipleJoins(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "testnetwork",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     })
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := n.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep, err := n.CreateEndpoint("ep1")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     defer controller.LeaveAll(containerID)
+++
+++     err = ep.Join(containerID,
+++             libnetwork.JoinOptionHostname("test"),
+++             libnetwork.JoinOptionDomainname("docker.io"),
+++             libnetwork.JoinOptionExtraHost("web", "192.168.0.1"))
+++     runtime.LockOSThread()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             err = ep.Leave(containerID)
+++             runtime.LockOSThread()
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     err = ep.Join("container2")
+++     if err == nil {
+++             t.Fatal("Expected to fail multiple joins for the same endpoint")
+++     }
+++
+++     if _, ok := err.(libnetwork.ErrInvalidJoin); !ok {
+++             t.Fatalf("Failed for unexpected reason: %v", err)
+++     }
+++}
+++
+++func TestLeaveAll(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "testnetwork",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     })
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := n.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep1, err := n.CreateEndpoint("ep1")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep1.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep2, err := n.CreateEndpoint("ep2")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep2.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     err = ep1.Join("leaveall")
+++     if err != nil {
+++             t.Fatalf("Failed to join ep1: %v", err)
+++     }
+++     runtime.LockOSThread()
+++
+++     err = ep2.Join("leaveall")
+++     if err != nil {
+++             t.Fatalf("Failed to join ep2: %v", err)
+++     }
+++     runtime.LockOSThread()
+++
+++     err = ep1.Leave("leaveall")
+++     if err != nil {
+++             t.Fatalf("Failed to leave ep1: %v", err)
+++     }
+++     runtime.LockOSThread()
+++
+++     err = controller.LeaveAll("leaveall")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     runtime.LockOSThread()
+++}
+++
+++func TestEndpointInvalidLeave(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "testnetwork",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     })
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := n.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep, err := n.CreateEndpoint("ep1")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     err = ep.Leave(containerID)
+++     if err == nil {
+++             t.Fatal("Expected to fail leave from an endpoint which has no active join")
+++     }
+++
+++     if _, ok := err.(libnetwork.InvalidContainerIDError); !ok {
+++             if _, ok := err.(libnetwork.ErrNoContainer); !ok {
+++                     t.Fatalf("Failed for unexpected reason: %v", err)
+++             }
+++     }
+++
+++     defer controller.LeaveAll(containerID)
+++
+++     err = ep.Join(containerID,
+++             libnetwork.JoinOptionHostname("test"),
+++             libnetwork.JoinOptionDomainname("docker.io"),
+++             libnetwork.JoinOptionExtraHost("web", "192.168.0.1"))
+++     runtime.LockOSThread()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             err = ep.Leave(containerID)
+++             runtime.LockOSThread()
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     err = ep.Leave("")
+++     if err == nil {
+++             t.Fatal("Expected to fail leave with empty container id")
+++     }
+++
+++     if _, ok := err.(libnetwork.InvalidContainerIDError); !ok {
+++             t.Fatalf("Failed for unexpected reason: %v", err)
+++     }
+++
+++     err = ep.Leave("container2")
+++     if err == nil {
+++             t.Fatal("Expected to fail leave with wrong container id")
+++     }
+++
+++     if _, ok := err.(libnetwork.InvalidContainerIDError); !ok {
+++             t.Fatalf("Failed for unexpected reason: %v", err)
+++     }
+++}
+++
+++func TestEndpointUpdateParent(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     n, err := createTestNetwork("bridge", "testnetwork", options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "testnetwork",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     })
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := n.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep1, err := n.CreateEndpoint("ep1")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep1.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     defer controller.LeaveAll(containerID)
+++     err = ep1.Join(containerID,
+++             libnetwork.JoinOptionHostname("test1"),
+++             libnetwork.JoinOptionDomainname("docker.io"),
+++             libnetwork.JoinOptionExtraHost("web", "192.168.0.1"))
+++     runtime.LockOSThread()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             err = ep1.Leave(containerID)
+++             runtime.LockOSThread()
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep2, err := n.CreateEndpoint("ep2")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep2.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     defer controller.LeaveAll("container2")
+++     err = ep2.Join("container2",
+++             libnetwork.JoinOptionHostname("test2"),
+++             libnetwork.JoinOptionDomainname("docker.io"),
+++             libnetwork.JoinOptionHostsPath("/var/lib/docker/test_network/container2/hosts"),
+++             libnetwork.JoinOptionParentUpdate(ep1.ID(), "web", "192.168.0.2"))
+++     runtime.LockOSThread()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = ep2.Leave("container2")
+++     runtime.LockOSThread()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++}
+++
+++func TestEnableIPv6(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     tmpResolvConf := []byte("search pommesfrites.fr\nnameserver 12.34.56.78\nnameserver 2001:4860:4860::8888")
+++     //take a copy of resolv.conf for restoring after test completes
+++     resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     //cleanup
+++     defer func() {
+++             if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ip, cidrv6, err := net.ParseCIDR("fe80::1/64")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     cidrv6.IP = ip
+++
+++     netOption := options.Generic{
+++             netlabel.EnableIPv6: true,
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "testnetwork",
+++                     "FixedCIDRv6":           cidrv6,
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++
+++     n, err := createTestNetwork("bridge", "testnetwork", netOption)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := n.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep1, err := n.CreateEndpoint("ep1")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep1.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     resolvConfPath := "/tmp/libnetwork_test/resolv.conf"
+++     defer os.Remove(resolvConfPath)
+++
+++     defer controller.LeaveAll(containerID)
+++     err = ep1.Join(containerID,
+++             libnetwork.JoinOptionResolvConfPath(resolvConfPath))
+++     runtime.LockOSThread()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             err = ep1.Leave(containerID)
+++             runtime.LockOSThread()
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     content, err := ioutil.ReadFile(resolvConfPath)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if !bytes.Equal(content, tmpResolvConf) {
+++             t.Fatalf("Expected %s, Got %s", string(tmpResolvConf), string(content))
+++     }
+++
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestResolvConfHost(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     tmpResolvConf := []byte("search localhost.net\nnameserver 127.0.0.1\nnameserver 2001:4860:4860::8888")
+++
+++     //take a copy of resolv.conf for restoring after test completes
+++     resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     //cleanup
+++     defer func() {
+++             if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     n, err := controller.NetworkByName("testhost")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     ep1, err := n.CreateEndpoint("ep1", nil)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     resolvConfPath := "/tmp/libnetwork_test/resolv.conf"
+++     defer os.Remove(resolvConfPath)
+++
+++     err = ep1.Join(containerID,
+++             libnetwork.JoinOptionResolvConfPath(resolvConfPath))
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             err = ep1.Leave(containerID)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     finfo, err := os.Stat(resolvConfPath)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     fmode := (os.FileMode)(0644)
+++     if finfo.Mode() != fmode {
+++             t.Fatalf("Expected file mode %s, got %s", fmode.String(), finfo.Mode().String())
+++     }
+++
+++     content, err := ioutil.ReadFile(resolvConfPath)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if !bytes.Equal(content, tmpResolvConf) {
+++             t.Fatalf("Expected %s, Got %s", string(tmpResolvConf), string(content))
+++     }
+++}
+++
+++func TestResolvConf(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             defer sandbox.SetupTestOSContext(t)()
+++     }
+++
+++     tmpResolvConf1 := []byte("search pommesfrites.fr\nnameserver 12.34.56.78\nnameserver 2001:4860:4860::8888")
+++     expectedResolvConf1 := []byte("search pommesfrites.fr\nnameserver 12.34.56.78\n")
+++     tmpResolvConf2 := []byte("search pommesfrites.fr\nnameserver 112.34.56.78\nnameserver 2001:4860:4860::8888")
+++     expectedResolvConf2 := []byte("search pommesfrites.fr\nnameserver 112.34.56.78\n")
+++     tmpResolvConf3 := []byte("search pommesfrites.fr\nnameserver 113.34.56.78\n")
+++
+++     //take a copy of resolv.conf for restoring after test completes
+++     resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     //cleanup
+++     defer func() {
+++             if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     netOption := options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "testnetwork",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++     n, err := createTestNetwork("bridge", "testnetwork", netOption)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := n.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     ep1, err := n.CreateEndpoint("ep1")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := ep1.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf1, 0644); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     resolvConfPath := "/tmp/libnetwork_test/resolv.conf"
+++     defer os.Remove(resolvConfPath)
+++
+++     defer controller.LeaveAll(containerID)
+++     err = ep1.Join(containerID,
+++             libnetwork.JoinOptionResolvConfPath(resolvConfPath))
+++     runtime.LockOSThread()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             err = ep1.Leave(containerID)
+++             runtime.LockOSThread()
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     finfo, err := os.Stat(resolvConfPath)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     fmode := (os.FileMode)(0644)
+++     if finfo.Mode() != fmode {
+++             t.Fatalf("Expected file mode %s, got %s", fmode.String(), finfo.Mode().String())
+++     }
+++
+++     content, err := ioutil.ReadFile(resolvConfPath)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if !bytes.Equal(content, expectedResolvConf1) {
+++             t.Fatalf("Expected %s, Got %s", string(expectedResolvConf1), string(content))
+++     }
+++
+++     err = ep1.Leave(containerID)
+++     runtime.LockOSThread()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf2, 0644); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = ep1.Join(containerID,
+++             libnetwork.JoinOptionResolvConfPath(resolvConfPath))
+++     runtime.LockOSThread()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     content, err = ioutil.ReadFile(resolvConfPath)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if !bytes.Equal(content, expectedResolvConf2) {
+++             t.Fatalf("Expected %s, Got %s", string(expectedResolvConf2), string(content))
+++     }
+++
+++     if err := ioutil.WriteFile(resolvConfPath, tmpResolvConf3, 0644); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = ep1.Leave(containerID)
+++     runtime.LockOSThread()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     err = ep1.Join(containerID,
+++             libnetwork.JoinOptionResolvConfPath(resolvConfPath))
+++     runtime.LockOSThread()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     content, err = ioutil.ReadFile(resolvConfPath)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if !bytes.Equal(content, tmpResolvConf3) {
+++             t.Fatalf("Expected %s, Got %s", string(tmpResolvConf3), string(content))
+++     }
+++}
+++
+++func TestInvalidRemoteDriver(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             t.Skip("Skipping test when not running inside a Container")
+++     }
+++
+++     mux := http.NewServeMux()
+++     server := httptest.NewServer(mux)
+++     if server == nil {
+++             t.Fatal("Failed to start a HTTP Server")
+++     }
+++     defer server.Close()
+++
+++     type pluginRequest struct {
+++             name string
+++     }
+++
+++     mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
+++             w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+++             fmt.Fprintln(w, `{"Implements": ["InvalidDriver"]}`)
+++     })
+++
+++     if err := os.MkdirAll("/usr/share/docker/plugins", 0755); err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := os.RemoveAll("/usr/share/docker/plugins"); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     if err := ioutil.WriteFile("/usr/share/docker/plugins/invalid-network-driver.spec", []byte(server.URL), 0644); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     controller, err := libnetwork.New()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     _, err = controller.NewNetwork("invalid-network-driver", "dummy",
+++             libnetwork.NetworkOptionGeneric(getEmptyGenericOption()))
+++     if err == nil {
+++             t.Fatal("Expected to fail. But instead succeeded")
+++     }
+++
+++     if err != plugins.ErrNotImplements {
+++             t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+++     }
+++}
+++
+++func TestValidRemoteDriver(t *testing.T) {
+++     if !netutils.IsRunningInContainer() {
+++             t.Skip("Skipping test when not running inside a Container")
+++     }
+++
+++     mux := http.NewServeMux()
+++     server := httptest.NewServer(mux)
+++     if server == nil {
+++             t.Fatal("Failed to start a HTTP Server")
+++     }
+++     defer server.Close()
+++
+++     type pluginRequest struct {
+++             name string
+++     }
+++
+++     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.CreateNetwork", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
+++             w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
+++             fmt.Fprintf(w, "null")
+++     })
+++
+++     if err := os.MkdirAll("/usr/share/docker/plugins", 0755); err != nil {
+++             t.Fatal(err)
+++     }
+++     defer func() {
+++             if err := os.RemoveAll("/usr/share/docker/plugins"); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++
+++     if err := ioutil.WriteFile("/usr/share/docker/plugins/valid-network-driver.spec", []byte(server.URL), 0644); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     n, err := controller.NewNetwork("valid-network-driver", "dummy",
+++             libnetwork.NetworkOptionGeneric(getEmptyGenericOption()))
+++     if err != nil {
+++             // Only fail if we could not find the plugin driver
+++             if _, ok := err.(types.NotFoundError); ok {
+++                     t.Fatal(err)
+++             }
+++             return
+++     }
+++     defer func() {
+++             if err := n.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }()
+++}
+++
+++var (
+++     once   sync.Once
+++     start  = make(chan struct{})
+++     done   = make(chan chan struct{}, numThreads-1)
+++     origns = netns.None()
+++     testns = netns.None()
+++)
+++
+++const (
+++     iterCnt    = 25
+++     numThreads = 3
+++     first      = 1
+++     last       = numThreads
+++     debug      = false
+++)
+++
+++func createGlobalInstance(t *testing.T) {
+++     var err error
+++     defer close(start)
+++
+++     origns, err = netns.Get()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if netutils.IsRunningInContainer() {
+++             testns = origns
+++     } else {
+++             testns, err = netns.New()
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++     }
+++
+++     netOption := options.Generic{
+++             netlabel.GenericData: options.Generic{
+++                     "BridgeName":            "network",
+++                     "AllowNonDefaultBridge": true,
+++             },
+++     }
+++
+++     net1, err := controller.NetworkByName("testhost")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     net2, err := createTestNetwork("bridge", "network2", netOption)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     _, err = net1.CreateEndpoint("pep1")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     _, err = net2.CreateEndpoint("pep2")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     _, err = net2.CreateEndpoint("pep3")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func debugf(format string, a ...interface{}) (int, error) {
+++     if debug {
+++             return fmt.Printf(format, a...)
+++     }
+++
+++     return 0, nil
+++}
+++
+++func parallelJoin(t *testing.T, ep libnetwork.Endpoint, thrNumber int) {
+++     debugf("J%d.", thrNumber)
+++     var err error
+++     if thrNumber == first {
+++             err = ep.Join(fmt.Sprintf("%drace", thrNumber), libnetwork.JoinOptionUseDefaultSandbox())
+++     } else {
+++             err = ep.Join(fmt.Sprintf("%drace", thrNumber))
+++     }
+++
+++     runtime.LockOSThread()
+++     if err != nil {
+++             if _, ok := err.(libnetwork.ErrNoContainer); !ok {
+++                     if _, ok := err.(libnetwork.ErrInvalidJoin); !ok {
+++                             t.Fatalf("thread %d: %v", thrNumber, err)
+++                     }
+++             }
+++             debugf("JE%d(%v).", thrNumber, err)
+++     }
+++     debugf("JD%d.", thrNumber)
+++}
+++
+++func parallelLeave(t *testing.T, ep libnetwork.Endpoint, thrNumber int) {
+++     debugf("L%d.", thrNumber)
+++     var err error
+++     if thrNumber == first {
+++             err = ep.Leave(fmt.Sprintf("%drace", thrNumber))
+++     } else {
+++             err = controller.LeaveAll(fmt.Sprintf("%drace", thrNumber))
+++     }
+++
+++     runtime.LockOSThread()
+++     if err != nil {
+++             if _, ok := err.(libnetwork.ErrNoContainer); !ok {
+++                     if _, ok := err.(libnetwork.ErrInvalidJoin); !ok {
+++                             t.Fatalf("thread %d: %v", thrNumber, err)
+++                     }
+++             }
+++             debugf("LE%d(%v).", thrNumber, err)
+++     }
+++     debugf("LD%d.", thrNumber)
+++}
+++
+++func runParallelTests(t *testing.T, thrNumber int) {
+++     var err error
+++
+++     t.Parallel()
+++
+++     pTest := flag.Lookup("test.parallel")
+++     if pTest == nil {
+++             t.Skip("Skipped because test.parallel flag not set;")
+++     }
+++     numParallel, err := strconv.Atoi(pTest.Value.String())
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if numParallel < numThreads {
+++             t.Skip("Skipped because t.parallel was less than ", numThreads)
+++     }
+++
+++     runtime.LockOSThread()
+++     defer runtime.UnlockOSThread()
+++
+++     if thrNumber == first {
+++             createGlobalInstance(t)
+++     }
+++
+++     if thrNumber != first {
+++             select {
+++             case <-start:
+++             }
+++
+++             thrdone := make(chan struct{})
+++             done <- thrdone
+++             defer close(thrdone)
+++
+++             if thrNumber == last {
+++                     defer close(done)
+++             }
+++
+++             err = netns.Set(testns)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++     }
+++     defer netns.Set(origns)
+++
+++     net1, err := controller.NetworkByName("testhost")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if net1 == nil {
+++             t.Fatal("Could not find network1")
+++     }
+++
+++     net2, err := controller.NetworkByName("network2")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if net2 == nil {
+++             t.Fatal("Could not find network2")
+++     }
+++
+++     epName := fmt.Sprintf("pep%d", thrNumber)
+++
+++     //var err error
+++     var ep libnetwork.Endpoint
+++
+++     if thrNumber == first {
+++             ep, err = net1.EndpointByName(epName)
+++     } else {
+++             ep, err = net2.EndpointByName(epName)
+++     }
+++
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if ep == nil {
+++             t.Fatal("Got nil ep with no error")
+++     }
+++
+++     for i := 0; i < iterCnt; i++ {
+++             parallelJoin(t, ep, thrNumber)
+++             parallelLeave(t, ep, thrNumber)
+++     }
+++
+++     debugf("\n")
+++
+++     err = ep.Delete()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if thrNumber == first {
+++             for thrdone := range done {
+++                     select {
+++                     case <-thrdone:
+++                     }
+++             }
+++
+++             testns.Close()
+++
+++             if err := net2.Delete(); err != nil {
+++                     t.Fatal(err)
+++             }
+++     }
+++}
+++
+++func TestParallel1(t *testing.T) {
+++     runParallelTests(t, 1)
+++}
+++
+++func TestParallel2(t *testing.T) {
+++     runParallelTests(t, 2)
+++}
+++
+++func TestParallel3(t *testing.T) {
+++     runParallelTests(t, 3)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..42779be8a4df3584bdcefd1d05a30ae245aae77b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,52 @@@@
+++package netlabel
+++
+++import "strings"
+++
+++const (
+++     // Prefix constant marks the reserved label space for libnetwork
+++     Prefix = "com.docker.network"
+++
+++     // DriverPrefix constant marks the reserved label space for libnetwork drivers
+++     DriverPrefix = Prefix + ".driver"
+++
+++     // GenericData constant that helps to identify an option as a Generic constant
+++     GenericData = Prefix + ".generic"
+++
+++     // PortMap constant represents Port Mapping
+++     PortMap = Prefix + ".portmap"
+++
+++     // MacAddress constant represents Mac Address config of a Container
+++     MacAddress = Prefix + ".endpoint.macaddress"
+++
+++     // ExposedPorts constant represents exposedports of a Container
+++     ExposedPorts = Prefix + ".endpoint.exposedports"
+++
+++     //EnableIPv6 constant represents enabling IPV6 at network level
+++     EnableIPv6 = Prefix + ".enable_ipv6"
+++
+++     // KVProvider constant represents the KV provider backend
+++     KVProvider = DriverPrefix + ".kv_provider"
+++
+++     // KVProviderURL constant represents the KV provider URL
+++     KVProviderURL = DriverPrefix + ".kv_provider_url"
+++
+++     // OverlayBindInterface constant represents overlay driver bind interface
+++     OverlayBindInterface = DriverPrefix + ".overlay.bind_interface"
+++
+++     // OverlayNeighborIP constant represents overlay driver neighbor IP
+++     OverlayNeighborIP = DriverPrefix + ".overlay.neighbor_ip"
+++)
+++
+++// Key extracts the key portion of the label
+++func Key(label string) string {
+++     kv := strings.SplitN(label, "=", 2)
+++
+++     return kv[0]
+++}
+++
+++// Value extracts the value portion of the label
+++func Value(label string) string {
+++     kv := strings.SplitN(label, "=", 2)
+++
+++     return kv[1]
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..7ebc16e364d0d48c23680a538dd2e7ef866f647c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,10 @@@@
+++package netutils
+++
+++import "flag"
+++
+++var runningInContainer = flag.Bool("incontainer", false, "Indicates if the test is running in a container")
+++
+++// IsRunningInContainer returns whether the test is running inside a container.
+++func IsRunningInContainer() bool {
+++     return (*runningInContainer)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..cb430eb03f185e48a8cc3ec95effb640d38d2c38
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,222 @@@@
+++// Network utility functions.
+++
+++package netutils
+++
+++import (
+++     "crypto/rand"
+++     "encoding/hex"
+++     "errors"
+++     "fmt"
+++     "io"
+++     "net"
+++     "strings"
+++
+++     "github.com/docker/libnetwork/types"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++var (
+++     // ErrNetworkOverlapsWithNameservers preformatted error
+++     ErrNetworkOverlapsWithNameservers = errors.New("requested network overlaps with nameserver")
+++     // ErrNetworkOverlaps preformatted error
+++     ErrNetworkOverlaps = errors.New("requested network overlaps with existing network")
+++     // ErrNoDefaultRoute preformatted error
+++     ErrNoDefaultRoute = errors.New("no default route")
+++
+++     networkGetRoutesFct = netlink.RouteList
+++)
+++
+++// CheckNameserverOverlaps checks whether the passed network overlaps with any of the nameservers
+++func CheckNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error {
+++     if len(nameservers) > 0 {
+++             for _, ns := range nameservers {
+++                     _, nsNetwork, err := net.ParseCIDR(ns)
+++                     if err != nil {
+++                             return err
+++                     }
+++                     if NetworkOverlaps(toCheck, nsNetwork) {
+++                             return ErrNetworkOverlapsWithNameservers
+++                     }
+++             }
+++     }
+++     return nil
+++}
+++
+++// CheckRouteOverlaps checks whether the passed network overlaps with any existing routes
+++func CheckRouteOverlaps(toCheck *net.IPNet) error {
+++     networks, err := networkGetRoutesFct(nil, netlink.FAMILY_V4)
+++     if err != nil {
+++             return err
+++     }
+++
+++     for _, network := range networks {
+++             if network.Dst != nil && NetworkOverlaps(toCheck, network.Dst) {
+++                     return ErrNetworkOverlaps
+++             }
+++     }
+++     return nil
+++}
+++
+++// NetworkOverlaps detects overlap between one IPNet and another
+++func NetworkOverlaps(netX *net.IPNet, netY *net.IPNet) bool {
+++     // Check if both netX and netY are ipv4 or ipv6
+++     if (netX.IP.To4() != nil && netY.IP.To4() != nil) ||
+++             (netX.IP.To4() == nil && netY.IP.To4() == nil) {
+++             if firstIP, _ := NetworkRange(netX); netY.Contains(firstIP) {
+++                     return true
+++             }
+++             if firstIP, _ := NetworkRange(netY); netX.Contains(firstIP) {
+++                     return true
+++             }
+++     }
+++     return false
+++}
+++
+++// NetworkRange calculates the first and last IP addresses in an IPNet
+++func NetworkRange(network *net.IPNet) (net.IP, net.IP) {
+++     if network == nil {
+++             return nil, nil
+++     }
+++
+++     firstIP := network.IP.Mask(network.Mask)
+++     lastIP := types.GetIPCopy(firstIP)
+++     for i := 0; i < len(firstIP); i++ {
+++             lastIP[i] = firstIP[i] | ^network.Mask[i]
+++     }
+++
+++     if network.IP.To4() != nil {
+++             firstIP = firstIP.To4()
+++             lastIP = lastIP.To4()
+++     }
+++
+++     return firstIP, lastIP
+++}
+++
+++// GetIfaceAddr returns the first IPv4 address and slice of IPv6 addresses for the specified network interface
+++func GetIfaceAddr(name string) (net.Addr, []net.Addr, error) {
+++     iface, err := net.InterfaceByName(name)
+++     if err != nil {
+++             return nil, nil, err
+++     }
+++     addrs, err := iface.Addrs()
+++     if err != nil {
+++             return nil, nil, err
+++     }
+++     var addrs4 []net.Addr
+++     var addrs6 []net.Addr
+++     for _, addr := range addrs {
+++             ip := (addr.(*net.IPNet)).IP
+++             if ip4 := ip.To4(); ip4 != nil {
+++                     addrs4 = append(addrs4, addr)
+++             } else if ip6 := ip.To16(); len(ip6) == net.IPv6len {
+++                     addrs6 = append(addrs6, addr)
+++             }
+++     }
+++     switch {
+++     case len(addrs4) == 0:
+++             return nil, nil, fmt.Errorf("Interface %v has no IPv4 addresses", name)
+++     case len(addrs4) > 1:
+++             fmt.Printf("Interface %v has more than 1 IPv4 address. Defaulting to using %v\n",
+++                     name, (addrs4[0].(*net.IPNet)).IP)
+++     }
+++     return addrs4[0], addrs6, nil
+++}
+++
+++// GenerateRandomMAC returns a new 6-byte(48-bit) hardware address (MAC)
+++func GenerateRandomMAC() net.HardwareAddr {
+++     hw := make(net.HardwareAddr, 6)
+++     // The first byte of the MAC address has to comply with these rules:
+++     // 1. Unicast: Set the least-significant bit to 0.
+++     // 2. Address is locally administered: Set the second-least-significant bit (U/L) to 1.
+++     // 3. As "small" as possible: The veth address has to be "smaller" than the bridge address.
+++     hw[0] = 0x02
+++     // The first 24 bits of the MAC represent the Organizationally Unique Identifier (OUI).
+++     // Since this address is locally administered, we can do whatever we want as long as
+++     // it doesn't conflict with other addresses.
+++     hw[1] = 0x42
+++     // Randomly generate the remaining 4 bytes (2^32)
+++     _, err := rand.Read(hw[2:])
+++     if err != nil {
+++             return nil
+++     }
+++     return hw
+++}
+++
+++// GenerateRandomName returns a new name joined with a prefix.  This size
+++// specified is used to truncate the randomly generated value
+++func GenerateRandomName(prefix string, size int) (string, error) {
+++     id := make([]byte, 32)
+++     if _, err := io.ReadFull(rand.Reader, id); err != nil {
+++             return "", err
+++     }
+++     return prefix + hex.EncodeToString(id)[:size], nil
+++}
+++
+++// GenerateIfaceName returns an interface name using the passed in
+++// prefix and the length of random bytes. The api ensures that the
+++// there are is no interface which exists with that name.
+++func GenerateIfaceName(prefix string, len int) (string, error) {
+++     for i := 0; i < 3; i++ {
+++             name, err := GenerateRandomName(prefix, len)
+++             if err != nil {
+++                     continue
+++             }
+++             if _, err := net.InterfaceByName(name); err != nil {
+++                     if strings.Contains(err.Error(), "no such") {
+++                             return name, nil
+++                     }
+++                     return "", err
+++             }
+++     }
+++     return "", types.InternalErrorf("could not generate interface name")
+++}
+++
+++func byteArrayToInt(array []byte, numBytes int) uint64 {
+++     if numBytes <= 0 || numBytes > 8 {
+++             panic("Invalid argument")
+++     }
+++     num := 0
+++     for i := 0; i <= len(array)-1; i++ {
+++             num += int(array[len(array)-1-i]) << uint(i*8)
+++     }
+++     return uint64(num)
+++}
+++
+++// ATo64 converts a byte array into a uint32
+++func ATo64(array []byte) uint64 {
+++     return byteArrayToInt(array, 8)
+++}
+++
+++// ATo32 converts a byte array into a uint32
+++func ATo32(array []byte) uint32 {
+++     return uint32(byteArrayToInt(array, 4))
+++}
+++
+++// ATo16 converts a byte array into a uint16
+++func ATo16(array []byte) uint16 {
+++     return uint16(byteArrayToInt(array, 2))
+++}
+++
+++func intToByteArray(val uint64, numBytes int) []byte {
+++     array := make([]byte, numBytes)
+++     for i := numBytes - 1; i >= 0; i-- {
+++             array[i] = byte(val & 0xff)
+++             val = val >> 8
+++     }
+++     return array
+++}
+++
+++// U64ToA converts a uint64 to a byte array
+++func U64ToA(val uint64) []byte {
+++     return intToByteArray(uint64(val), 8)
+++}
+++
+++// U32ToA converts a uint64 to a byte array
+++func U32ToA(val uint32) []byte {
+++     return intToByteArray(uint64(val), 4)
+++}
+++
+++// U16ToA converts a uint64 to a byte array
+++func U16ToA(val uint16) []byte {
+++     return intToByteArray(uint64(val), 2)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..78de626e7fb19ebd3aa7287cbd3657d2aba6d910
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,211 @@@@
+++package netutils
+++
+++import (
+++     "bytes"
+++     "net"
+++     "testing"
+++
+++     "github.com/vishvananda/netlink"
+++)
+++
+++func TestNonOverlapingNameservers(t *testing.T) {
+++     network := &net.IPNet{
+++             IP:   []byte{192, 168, 0, 1},
+++             Mask: []byte{255, 255, 255, 0},
+++     }
+++     nameservers := []string{
+++             "127.0.0.1/32",
+++     }
+++
+++     if err := CheckNameserverOverlaps(nameservers, network); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestOverlapingNameservers(t *testing.T) {
+++     network := &net.IPNet{
+++             IP:   []byte{192, 168, 0, 1},
+++             Mask: []byte{255, 255, 255, 0},
+++     }
+++     nameservers := []string{
+++             "192.168.0.1/32",
+++     }
+++
+++     if err := CheckNameserverOverlaps(nameservers, network); err == nil {
+++             t.Fatalf("Expected error %s got %s", ErrNetworkOverlapsWithNameservers, err)
+++     }
+++}
+++
+++func TestCheckRouteOverlaps(t *testing.T) {
+++     orig := networkGetRoutesFct
+++     defer func() {
+++             networkGetRoutesFct = orig
+++     }()
+++     networkGetRoutesFct = func(netlink.Link, int) ([]netlink.Route, error) {
+++             routesData := []string{"10.0.2.0/32", "10.0.3.0/24", "10.0.42.0/24", "172.16.42.0/24", "192.168.142.0/24"}
+++
+++             routes := []netlink.Route{}
+++             for _, addr := range routesData {
+++                     _, netX, _ := net.ParseCIDR(addr)
+++                     routes = append(routes, netlink.Route{Dst: netX})
+++             }
+++             return routes, nil
+++     }
+++
+++     _, netX, _ := net.ParseCIDR("172.16.0.1/24")
+++     if err := CheckRouteOverlaps(netX); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     _, netX, _ = net.ParseCIDR("10.0.2.0/24")
+++     if err := CheckRouteOverlaps(netX); err == nil {
+++             t.Fatalf("10.0.2.0/24 and 10.0.2.0 should overlap but it doesn't")
+++     }
+++}
+++
+++func TestCheckNameserverOverlaps(t *testing.T) {
+++     nameservers := []string{"10.0.2.3/32", "192.168.102.1/32"}
+++
+++     _, netX, _ := net.ParseCIDR("10.0.2.3/32")
+++
+++     if err := CheckNameserverOverlaps(nameservers, netX); err == nil {
+++             t.Fatalf("%s should overlap 10.0.2.3/32 but doesn't", netX)
+++     }
+++
+++     _, netX, _ = net.ParseCIDR("192.168.102.2/32")
+++
+++     if err := CheckNameserverOverlaps(nameservers, netX); err != nil {
+++             t.Fatalf("%s should not overlap %v but it does", netX, nameservers)
+++     }
+++}
+++
+++func AssertOverlap(CIDRx string, CIDRy string, t *testing.T) {
+++     _, netX, _ := net.ParseCIDR(CIDRx)
+++     _, netY, _ := net.ParseCIDR(CIDRy)
+++     if !NetworkOverlaps(netX, netY) {
+++             t.Errorf("%v and %v should overlap", netX, netY)
+++     }
+++}
+++
+++func AssertNoOverlap(CIDRx string, CIDRy string, t *testing.T) {
+++     _, netX, _ := net.ParseCIDR(CIDRx)
+++     _, netY, _ := net.ParseCIDR(CIDRy)
+++     if NetworkOverlaps(netX, netY) {
+++             t.Errorf("%v and %v should not overlap", netX, netY)
+++     }
+++}
+++
+++func TestNetworkOverlaps(t *testing.T) {
+++     //netY starts at same IP and ends within netX
+++     AssertOverlap("172.16.0.1/24", "172.16.0.1/25", t)
+++     //netY starts within netX and ends at same IP
+++     AssertOverlap("172.16.0.1/24", "172.16.0.128/25", t)
+++     //netY starts and ends within netX
+++     AssertOverlap("172.16.0.1/24", "172.16.0.64/25", t)
+++     //netY starts at same IP and ends outside of netX
+++     AssertOverlap("172.16.0.1/24", "172.16.0.1/23", t)
+++     //netY starts before and ends at same IP of netX
+++     AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t)
+++     //netY starts before and ends outside of netX
+++     AssertOverlap("172.16.1.1/24", "172.16.0.1/22", t)
+++     //netY starts and ends before netX
+++     AssertNoOverlap("172.16.1.1/25", "172.16.0.1/24", t)
+++     //netX starts and ends before netY
+++     AssertNoOverlap("172.16.1.1/25", "172.16.2.1/24", t)
+++}
+++
+++func TestNetworkRange(t *testing.T) {
+++     // Simple class C test
+++     _, network, _ := net.ParseCIDR("192.168.0.1/24")
+++     first, last := NetworkRange(network)
+++     if !first.Equal(net.ParseIP("192.168.0.0")) {
+++             t.Error(first.String())
+++     }
+++     if !last.Equal(net.ParseIP("192.168.0.255")) {
+++             t.Error(last.String())
+++     }
+++
+++     // Class A test
+++     _, network, _ = net.ParseCIDR("10.0.0.1/8")
+++     first, last = NetworkRange(network)
+++     if !first.Equal(net.ParseIP("10.0.0.0")) {
+++             t.Error(first.String())
+++     }
+++     if !last.Equal(net.ParseIP("10.255.255.255")) {
+++             t.Error(last.String())
+++     }
+++
+++     // Class A, random IP address
+++     _, network, _ = net.ParseCIDR("10.1.2.3/8")
+++     first, last = NetworkRange(network)
+++     if !first.Equal(net.ParseIP("10.0.0.0")) {
+++             t.Error(first.String())
+++     }
+++     if !last.Equal(net.ParseIP("10.255.255.255")) {
+++             t.Error(last.String())
+++     }
+++
+++     // 32bit mask
+++     _, network, _ = net.ParseCIDR("10.1.2.3/32")
+++     first, last = NetworkRange(network)
+++     if !first.Equal(net.ParseIP("10.1.2.3")) {
+++             t.Error(first.String())
+++     }
+++     if !last.Equal(net.ParseIP("10.1.2.3")) {
+++             t.Error(last.String())
+++     }
+++
+++     // 31bit mask
+++     _, network, _ = net.ParseCIDR("10.1.2.3/31")
+++     first, last = NetworkRange(network)
+++     if !first.Equal(net.ParseIP("10.1.2.2")) {
+++             t.Error(first.String())
+++     }
+++     if !last.Equal(net.ParseIP("10.1.2.3")) {
+++             t.Error(last.String())
+++     }
+++
+++     // 26bit mask
+++     _, network, _ = net.ParseCIDR("10.1.2.3/26")
+++     first, last = NetworkRange(network)
+++     if !first.Equal(net.ParseIP("10.1.2.0")) {
+++             t.Error(first.String())
+++     }
+++     if !last.Equal(net.ParseIP("10.1.2.63")) {
+++             t.Error(last.String())
+++     }
+++}
+++
+++// Test veth name generation "veth"+rand (e.g.veth0f60e2c)
+++func TestGenerateRandomName(t *testing.T) {
+++     name1, err := GenerateRandomName("veth", 7)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     // veth plus generated append equals a len of 11
+++     if len(name1) != 11 {
+++             t.Fatalf("Expected 11 characters, instead received %d characters", len(name1))
+++     }
+++     name2, err := GenerateRandomName("veth", 7)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     // Fail if the random generated names equal one another
+++     if name1 == name2 {
+++             t.Fatalf("Expected differing values but received %s and %s", name1, name2)
+++     }
+++}
+++
+++// Test mac generation.
+++func TestUtilGenerateRandomMAC(t *testing.T) {
+++     mac1 := GenerateRandomMAC()
+++     mac2 := GenerateRandomMAC()
+++     // ensure bytes are unique
+++     if bytes.Equal(mac1, mac2) {
+++             t.Fatalf("mac1 %s should not equal mac2 %s", mac1, mac2)
+++     }
+++     // existing tests check string functionality so keeping the pattern
+++     if mac1.String() == mac2.String() {
+++             t.Fatalf("mac1 %s should not equal mac2 %s", mac1, mac2)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9ad4381114b950a80d4485b836828867bbdfd974
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,471 @@@@
+++package libnetwork
+++
+++import (
+++     "encoding/json"
+++     "net"
+++     "sync"
+++
+++     log "github.com/Sirupsen/logrus"
+++     "github.com/docker/docker/pkg/stringid"
+++     "github.com/docker/libnetwork/config"
+++     "github.com/docker/libnetwork/datastore"
+++     "github.com/docker/libnetwork/driverapi"
+++     "github.com/docker/libnetwork/etchosts"
+++     "github.com/docker/libnetwork/netlabel"
+++     "github.com/docker/libnetwork/options"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++// A Network represents a logical connectivity zone that containers may
+++// join using the Link method. A Network is managed by a specific driver.
+++type Network interface {
+++     // A user chosen name for this network.
+++     Name() string
+++
+++     // A system generated id for this network.
+++     ID() string
+++
+++     // The type of network, which corresponds to its managing driver.
+++     Type() string
+++
+++     // Create a new endpoint to this network symbolically identified by the
+++     // specified unique name. The options parameter carry driver specific options.
+++     // Labels support will be added in the near future.
+++     CreateEndpoint(name string, options ...EndpointOption) (Endpoint, error)
+++
+++     // Delete the network.
+++     Delete() error
+++
+++     // Endpoints returns the list of Endpoint(s) in this network.
+++     Endpoints() []Endpoint
+++
+++     // WalkEndpoints uses the provided function to walk the Endpoints
+++     WalkEndpoints(walker EndpointWalker)
+++
+++     // EndpointByName returns the Endpoint which has the passed name. If not found, the error ErrNoSuchEndpoint is returned.
+++     EndpointByName(name string) (Endpoint, error)
+++
+++     // EndpointByID returns the Endpoint which has the passed id. If not found, the error ErrNoSuchEndpoint is returned.
+++     EndpointByID(id string) (Endpoint, error)
+++}
+++
+++// EndpointWalker is a client provided function which will be used to walk the Endpoints.
+++// When the function returns true, the walk will stop.
+++type EndpointWalker func(ep Endpoint) bool
+++
+++type svcMap map[string]net.IP
+++
+++type network struct {
+++     ctrlr       *controller
+++     name        string
+++     networkType string
+++     id          types.UUID
+++     driver      driverapi.Driver
+++     enableIPv6  bool
+++     endpointCnt uint64
+++     endpoints   endpointTable
+++     generic     options.Generic
+++     dbIndex     uint64
+++     svcRecords  svcMap
+++     dbExists    bool
+++     stopWatchCh chan struct{}
+++     sync.Mutex
+++}
+++
+++func (n *network) Name() string {
+++     n.Lock()
+++     defer n.Unlock()
+++
+++     return n.name
+++}
+++
+++func (n *network) ID() string {
+++     n.Lock()
+++     defer n.Unlock()
+++
+++     return string(n.id)
+++}
+++
+++func (n *network) Type() string {
+++     n.Lock()
+++     defer n.Unlock()
+++
+++     if n.driver == nil {
+++             return ""
+++     }
+++
+++     return n.driver.Type()
+++}
+++
+++func (n *network) Key() []string {
+++     n.Lock()
+++     defer n.Unlock()
+++     return []string{datastore.NetworkKeyPrefix, string(n.id)}
+++}
+++
+++func (n *network) KeyPrefix() []string {
+++     return []string{datastore.NetworkKeyPrefix}
+++}
+++
+++func (n *network) Value() []byte {
+++     n.Lock()
+++     defer n.Unlock()
+++     b, err := json.Marshal(n)
+++     if err != nil {
+++             return nil
+++     }
+++     return b
+++}
+++
+++func (n *network) SetValue(value []byte) error {
+++     return json.Unmarshal(value, n)
+++}
+++
+++func (n *network) Index() uint64 {
+++     n.Lock()
+++     defer n.Unlock()
+++     return n.dbIndex
+++}
+++
+++func (n *network) SetIndex(index uint64) {
+++     n.Lock()
+++     n.dbIndex = index
+++     n.dbExists = true
+++     n.Unlock()
+++}
+++
+++func (n *network) Exists() bool {
+++     n.Lock()
+++     defer n.Unlock()
+++     return n.dbExists
+++}
+++
+++func (n *network) EndpointCnt() uint64 {
+++     n.Lock()
+++     defer n.Unlock()
+++     return n.endpointCnt
+++}
+++
+++func (n *network) IncEndpointCnt() {
+++     n.Lock()
+++     n.endpointCnt++
+++     n.Unlock()
+++}
+++
+++func (n *network) DecEndpointCnt() {
+++     n.Lock()
+++     n.endpointCnt--
+++     n.Unlock()
+++}
+++
+++// TODO : Can be made much more generic with the help of reflection (but has some golang limitations)
+++func (n *network) MarshalJSON() ([]byte, error) {
+++     netMap := make(map[string]interface{})
+++     netMap["name"] = n.name
+++     netMap["id"] = string(n.id)
+++     netMap["networkType"] = n.networkType
+++     netMap["endpointCnt"] = n.endpointCnt
+++     netMap["enableIPv6"] = n.enableIPv6
+++     netMap["generic"] = n.generic
+++     return json.Marshal(netMap)
+++}
+++
+++// TODO : Can be made much more generic with the help of reflection (but has some golang limitations)
+++func (n *network) UnmarshalJSON(b []byte) (err error) {
+++     var netMap map[string]interface{}
+++     if err := json.Unmarshal(b, &netMap); err != nil {
+++             return err
+++     }
+++     n.name = netMap["name"].(string)
+++     n.id = types.UUID(netMap["id"].(string))
+++     n.networkType = netMap["networkType"].(string)
+++     n.endpointCnt = uint64(netMap["endpointCnt"].(float64))
+++     n.enableIPv6 = netMap["enableIPv6"].(bool)
+++     if netMap["generic"] != nil {
+++             n.generic = netMap["generic"].(map[string]interface{})
+++     }
+++     return nil
+++}
+++
+++// NetworkOption is a option setter function type used to pass varios options to
+++// NewNetwork method. The various setter functions of type NetworkOption are
+++// provided by libnetwork, they look like NetworkOptionXXXX(...)
+++type NetworkOption func(n *network)
+++
+++// NetworkOptionGeneric function returns an option setter for a Generic option defined
+++// in a Dictionary of Key-Value pair
+++func NetworkOptionGeneric(generic map[string]interface{}) NetworkOption {
+++     return func(n *network) {
+++             n.generic = generic
+++             if _, ok := generic[netlabel.EnableIPv6]; ok {
+++                     n.enableIPv6 = generic[netlabel.EnableIPv6].(bool)
+++             }
+++     }
+++}
+++
+++func (n *network) processOptions(options ...NetworkOption) {
+++     for _, opt := range options {
+++             if opt != nil {
+++                     opt(n)
+++             }
+++     }
+++}
+++
+++func (n *network) Delete() error {
+++     var err error
+++
+++     n.Lock()
+++     ctrlr := n.ctrlr
+++     n.Unlock()
+++
+++     ctrlr.Lock()
+++     _, ok := ctrlr.networks[n.id]
+++     ctrlr.Unlock()
+++
+++     if !ok {
+++             return &UnknownNetworkError{name: n.name, id: string(n.id)}
+++     }
+++
+++     numEps := n.EndpointCnt()
+++     if numEps != 0 {
+++             return &ActiveEndpointsError{name: n.name, id: string(n.id)}
+++     }
+++
+++     // deleteNetworkFromStore performs an atomic delete operation and the network.endpointCnt field will help
+++     // prevent any possible race between endpoint join and network delete
+++     if err = ctrlr.deleteNetworkFromStore(n); err != nil {
+++             if err == datastore.ErrKeyModified {
+++                     return types.InternalErrorf("operation in progress. delete failed for network %s. Please try again.")
+++             }
+++             return err
+++     }
+++
+++     if err = n.deleteNetwork(); err != nil {
+++             return err
+++     }
+++
+++     return nil
+++}
+++
+++func (n *network) deleteNetwork() error {
+++     n.Lock()
+++     id := n.id
+++     d := n.driver
+++     n.ctrlr.Lock()
+++     delete(n.ctrlr.networks, id)
+++     n.ctrlr.Unlock()
+++     n.Unlock()
+++
+++     if err := d.DeleteNetwork(n.id); err != nil {
+++             // Forbidden Errors should be honored
+++             if _, ok := err.(types.ForbiddenError); ok {
+++                     n.ctrlr.Lock()
+++                     n.ctrlr.networks[n.id] = n
+++                     n.ctrlr.Unlock()
+++                     return err
+++             }
+++             log.Warnf("driver error deleting network %s : %v", n.name, err)
+++     }
+++     n.stopWatch()
+++     return nil
+++}
+++
+++func (n *network) addEndpoint(ep *endpoint) error {
+++     var err error
+++     n.Lock()
+++     n.endpoints[ep.id] = ep
+++     d := n.driver
+++     n.Unlock()
+++
+++     defer func() {
+++             if err != nil {
+++                     n.Lock()
+++                     delete(n.endpoints, ep.id)
+++                     n.Unlock()
+++             }
+++     }()
+++
+++     err = d.CreateEndpoint(n.id, ep.id, ep, ep.generic)
+++     if err != nil {
+++             return err
+++     }
+++
+++     n.updateSvcRecord(ep, true)
+++     return nil
+++}
+++
+++func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoint, error) {
+++     var err error
+++     if !config.IsValidName(name) {
+++             return nil, ErrInvalidName(name)
+++     }
+++
+++     if _, err = n.EndpointByName(name); err == nil {
+++             return nil, types.ForbiddenErrorf("service endpoint with name %s already exists", name)
+++     }
+++
+++     ep := &endpoint{name: name,
+++             iFaces:  []*endpointInterface{},
+++             generic: make(map[string]interface{})}
+++     ep.id = types.UUID(stringid.GenerateRandomID())
+++     ep.network = n
+++     ep.processOptions(options...)
+++
+++     n.Lock()
+++     ctrlr := n.ctrlr
+++     n.Unlock()
+++
+++     n.IncEndpointCnt()
+++     if err = ctrlr.updateNetworkToStore(n); err != nil {
+++             return nil, err
+++     }
+++     defer func() {
+++             if err != nil {
+++                     n.DecEndpointCnt()
+++                     if err = ctrlr.updateNetworkToStore(n); err != nil {
+++                             log.Warnf("endpoint count cleanup failed when updating network for %s : %v", name, err)
+++                     }
+++             }
+++     }()
+++     if err = n.addEndpoint(ep); err != nil {
+++             return nil, err
+++     }
+++     defer func() {
+++             if err != nil {
+++                     if e := ep.Delete(); ep != nil {
+++                             log.Warnf("cleaning up endpoint failed %s : %v", name, e)
+++                     }
+++             }
+++     }()
+++
+++     if err = ctrlr.updateEndpointToStore(ep); err != nil {
+++             return nil, err
+++     }
+++
+++     return ep, nil
+++}
+++
+++func (n *network) Endpoints() []Endpoint {
+++     n.Lock()
+++     defer n.Unlock()
+++     list := make([]Endpoint, 0, len(n.endpoints))
+++     for _, e := range n.endpoints {
+++             list = append(list, e)
+++     }
+++
+++     return list
+++}
+++
+++func (n *network) WalkEndpoints(walker EndpointWalker) {
+++     for _, e := range n.Endpoints() {
+++             if walker(e) {
+++                     return
+++             }
+++     }
+++}
+++
+++func (n *network) EndpointByName(name string) (Endpoint, error) {
+++     if name == "" {
+++             return nil, ErrInvalidName(name)
+++     }
+++     var e Endpoint
+++
+++     s := func(current Endpoint) bool {
+++             if current.Name() == name {
+++                     e = current
+++                     return true
+++             }
+++             return false
+++     }
+++
+++     n.WalkEndpoints(s)
+++
+++     if e == nil {
+++             return nil, ErrNoSuchEndpoint(name)
+++     }
+++
+++     return e, nil
+++}
+++
+++func (n *network) EndpointByID(id string) (Endpoint, error) {
+++     if id == "" {
+++             return nil, ErrInvalidID(id)
+++     }
+++     n.Lock()
+++     defer n.Unlock()
+++     if e, ok := n.endpoints[types.UUID(id)]; ok {
+++             return e, nil
+++     }
+++     return nil, ErrNoSuchEndpoint(id)
+++}
+++
+++func (n *network) isGlobalScoped() (bool, error) {
+++     n.Lock()
+++     c := n.ctrlr
+++     n.Unlock()
+++     return c.isDriverGlobalScoped(n.networkType)
+++}
+++
+++func (n *network) updateSvcRecord(ep *endpoint, isAdd bool) {
+++     n.Lock()
+++     var recs []etchosts.Record
+++     for _, iface := range ep.InterfaceList() {
+++             if isAdd {
+++                     n.svcRecords[ep.Name()] = iface.Address().IP
+++                     n.svcRecords[ep.Name()+"."+n.name] = iface.Address().IP
+++             } else {
+++                     delete(n.svcRecords, ep.Name())
+++                     delete(n.svcRecords, ep.Name()+"."+n.name)
+++             }
+++
+++             recs = append(recs, etchosts.Record{
+++                     Hosts: ep.Name(),
+++                     IP:    iface.Address().IP.String(),
+++             })
+++
+++             recs = append(recs, etchosts.Record{
+++                     Hosts: ep.Name() + "." + n.name,
+++                     IP:    iface.Address().IP.String(),
+++             })
+++     }
+++     n.Unlock()
+++
+++     // If there are no records to add or delete then simply return here
+++     if len(recs) == 0 {
+++             return
+++     }
+++
+++     var epList []*endpoint
+++     n.WalkEndpoints(func(e Endpoint) bool {
+++             cEp := e.(*endpoint)
+++             cEp.Lock()
+++             if cEp.container != nil {
+++                     epList = append(epList, cEp)
+++             }
+++             cEp.Unlock()
+++             return false
+++     })
+++
+++     for _, cEp := range epList {
+++             if isAdd {
+++                     cEp.addHostEntries(recs)
+++             } else {
+++                     cEp.deleteHostEntries(recs)
+++             }
+++     }
+++}
+++
+++func (n *network) getSvcRecords() []etchosts.Record {
+++     n.Lock()
+++     defer n.Unlock()
+++
+++     var recs []etchosts.Record
+++     for h, ip := range n.svcRecords {
+++             recs = append(recs, etchosts.Record{
+++                     Hosts: h,
+++                     IP:    ip.String(),
+++             })
+++     }
+++
+++     return recs
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e0e93ff9b770b3e6b45b7e94ccf8809a065ffe09
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,73 @@@@
+++// Package options provides a way to pass unstructured sets of options to a
+++// component expecting a strongly-typed configuration structure.
+++package options
+++
+++import (
+++     "fmt"
+++     "reflect"
+++)
+++
+++// NoSuchFieldError is the error returned when the generic parameters hold a
+++// value for a field absent from the destination structure.
+++type NoSuchFieldError struct {
+++     Field string
+++     Type  string
+++}
+++
+++func (e NoSuchFieldError) Error() string {
+++     return fmt.Sprintf("no field %q in type %q", e.Field, e.Type)
+++}
+++
+++// CannotSetFieldError is the error returned when the generic parameters hold a
+++// value for a field that cannot be set in the destination structure.
+++type CannotSetFieldError struct {
+++     Field string
+++     Type  string
+++}
+++
+++func (e CannotSetFieldError) Error() string {
+++     return fmt.Sprintf("cannot set field %q of type %q", e.Field, e.Type)
+++}
+++
+++// Generic is an basic type to store arbitrary settings.
+++type Generic map[string]interface{}
+++
+++// NewGeneric returns a new Generic instance.
+++func NewGeneric() Generic {
+++     return make(Generic)
+++}
+++
+++// GenerateFromModel takes the generic options, and tries to build a new
+++// instance of the model's type by matching keys from the generic options to
+++// fields in the model.
+++//
+++// The return value is of the same type than the model (including a potential
+++// pointer qualifier).
+++func GenerateFromModel(options Generic, model interface{}) (interface{}, error) {
+++     modType := reflect.TypeOf(model)
+++
+++     // If the model is of pointer type, we need to dereference for New.
+++     resType := reflect.TypeOf(model)
+++     if modType.Kind() == reflect.Ptr {
+++             resType = resType.Elem()
+++     }
+++
+++     // Populate the result structure with the generic layout content.
+++     res := reflect.New(resType)
+++     for name, value := range options {
+++             field := res.Elem().FieldByName(name)
+++             if !field.IsValid() {
+++                     return nil, NoSuchFieldError{name, resType.String()}
+++             }
+++             if !field.CanSet() {
+++                     return nil, CannotSetFieldError{name, resType.String()}
+++             }
+++             field.Set(reflect.ValueOf(value))
+++     }
+++
+++     // If the model is not of pointer type, return content of the result.
+++     if modType.Kind() == reflect.Ptr {
+++             return res.Interface(), nil
+++     }
+++     return res.Elem().Interface(), nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ecd3b3b311757280157b6d17cca296572ef520bd
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,97 @@@@
+++package options
+++
+++import (
+++     "reflect"
+++     "strings"
+++     "testing"
+++
+++     _ "github.com/docker/libnetwork/netutils"
+++)
+++
+++func TestGenerate(t *testing.T) {
+++     gen := NewGeneric()
+++     gen["Int"] = 1
+++     gen["Rune"] = 'b'
+++     gen["Float64"] = 2.0
+++
+++     type Model struct {
+++             Int     int
+++             Rune    rune
+++             Float64 float64
+++     }
+++
+++     result, err := GenerateFromModel(gen, Model{})
+++
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     cast, ok := result.(Model)
+++     if !ok {
+++             t.Fatalf("result has unexpected type %s", reflect.TypeOf(result))
+++     }
+++     if expected := 1; cast.Int != expected {
+++             t.Fatalf("wrong value for field Int: expected %v, got %v", expected, cast.Int)
+++     }
+++     if expected := 'b'; cast.Rune != expected {
+++             t.Fatalf("wrong value for field Rune: expected %v, got %v", expected, cast.Rune)
+++     }
+++     if expected := 2.0; cast.Float64 != expected {
+++             t.Fatalf("wrong value for field Int: expected %v, got %v", expected, cast.Float64)
+++     }
+++}
+++
+++func TestGeneratePtr(t *testing.T) {
+++     gen := NewGeneric()
+++     gen["Int"] = 1
+++     gen["Rune"] = 'b'
+++     gen["Float64"] = 2.0
+++
+++     type Model struct {
+++             Int     int
+++             Rune    rune
+++             Float64 float64
+++     }
+++
+++     result, err := GenerateFromModel(gen, &Model{})
+++
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     cast, ok := result.(*Model)
+++     if !ok {
+++             t.Fatalf("result has unexpected type %s", reflect.TypeOf(result))
+++     }
+++     if expected := 1; cast.Int != expected {
+++             t.Fatalf("wrong value for field Int: expected %v, got %v", expected, cast.Int)
+++     }
+++     if expected := 'b'; cast.Rune != expected {
+++             t.Fatalf("wrong value for field Rune: expected %v, got %v", expected, cast.Rune)
+++     }
+++     if expected := 2.0; cast.Float64 != expected {
+++             t.Fatalf("wrong value for field Int: expected %v, got %v", expected, cast.Float64)
+++     }
+++}
+++
+++func TestGenerateMissingField(t *testing.T) {
+++     type Model struct{}
+++     _, err := GenerateFromModel(Generic{"foo": "bar"}, Model{})
+++
+++     if _, ok := err.(NoSuchFieldError); !ok {
+++             t.Fatalf("expected NoSuchFieldError, got %#v", err)
+++     } else if expected := "no field"; !strings.Contains(err.Error(), expected) {
+++             t.Fatalf("expected %q in error message, got %s", expected, err.Error())
+++     }
+++}
+++
+++func TestFieldCannotBeSet(t *testing.T) {
+++     type Model struct{ foo int }
+++     _, err := GenerateFromModel(Generic{"foo": "bar"}, Model{})
+++
+++     if _, ok := err.(CannotSetFieldError); !ok {
+++             t.Fatalf("expected CannotSetFieldError, got %#v", err)
+++     } else if expected := "cannot set field"; !strings.Contains(err.Error(), expected) {
+++             t.Fatalf("expected %q in error message, got %s", expected, err.Error())
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..37d9c769b4d7895b71815faad8b2987626536ec8
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,212 @@@@
+++package portallocator
+++
+++import (
+++     "bufio"
+++     "errors"
+++     "fmt"
+++     "net"
+++     "os"
+++     "sync"
+++)
+++
+++const (
+++     // DefaultPortRangeStart indicates the first port in port range
+++     DefaultPortRangeStart = 49153
+++     // DefaultPortRangeEnd indicates the last port in port range
+++     DefaultPortRangeEnd = 65535
+++)
+++
+++type ipMapping map[string]protoMap
+++
+++var (
+++     // ErrAllPortsAllocated is returned when no more ports are available
+++     ErrAllPortsAllocated = errors.New("all ports are allocated")
+++     // ErrUnknownProtocol is returned when an unknown protocol was specified
+++     ErrUnknownProtocol = errors.New("unknown protocol")
+++     defaultIP          = net.ParseIP("0.0.0.0")
+++     once               sync.Once
+++     instance           *PortAllocator
+++     createInstance     = func() { instance = newInstance() }
+++)
+++
+++// ErrPortAlreadyAllocated is the returned error information when a requested port is already being used
+++type ErrPortAlreadyAllocated struct {
+++     ip   string
+++     port int
+++}
+++
+++func newErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated {
+++     return ErrPortAlreadyAllocated{
+++             ip:   ip,
+++             port: port,
+++     }
+++}
+++
+++// IP returns the address to which the used port is associated
+++func (e ErrPortAlreadyAllocated) IP() string {
+++     return e.ip
+++}
+++
+++// Port returns the value of the already used port
+++func (e ErrPortAlreadyAllocated) Port() int {
+++     return e.port
+++}
+++
+++// IPPort returns the address and the port in the form ip:port
+++func (e ErrPortAlreadyAllocated) IPPort() string {
+++     return fmt.Sprintf("%s:%d", e.ip, e.port)
+++}
+++
+++// Error is the implementation of error.Error interface
+++func (e ErrPortAlreadyAllocated) Error() string {
+++     return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port)
+++}
+++
+++type (
+++     // PortAllocator manages the transport ports database
+++     PortAllocator struct {
+++             mutex sync.Mutex
+++             ipMap ipMapping
+++             Begin int
+++             End   int
+++     }
+++     portMap struct {
+++             p          map[int]struct{}
+++             begin, end int
+++             last       int
+++     }
+++     protoMap map[string]*portMap
+++)
+++
+++// Get returns the default instance of PortAllocator
+++func Get() *PortAllocator {
+++     // Port Allocator is a singleton
+++     // Note: Long term solution will be each PortAllocator will have access to
+++     // the OS so that it can have up to date view of the OS port allocation.
+++     // When this happens singleton behavior will be removed. Clients do not
+++     // need to worry about this, they will not see a change in behavior.
+++     once.Do(createInstance)
+++     return instance
+++}
+++
+++func newInstance() *PortAllocator {
+++     start, end, err := getDynamicPortRange()
+++     if err != nil {
+++             start, end = DefaultPortRangeStart, DefaultPortRangeEnd
+++     }
+++     return &PortAllocator{
+++             ipMap: ipMapping{},
+++             Begin: start,
+++             End:   end,
+++     }
+++}
+++
+++func getDynamicPortRange() (start int, end int, err error) {
+++     const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range"
+++     portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd)
+++     file, err := os.Open(portRangeKernelParam)
+++     if err != nil {
+++             return 0, 0, fmt.Errorf("port allocator - %s due to error: %v", portRangeFallback, err)
+++     }
+++
+++     defer file.Close()
+++
+++     n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end)
+++     if n != 2 || err != nil {
+++             if err == nil {
+++                     err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
+++             }
+++             return 0, 0, fmt.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err)
+++     }
+++     return start, end, nil
+++}
+++
+++// RequestPort requests new port from global ports pool for specified ip and proto.
+++// If port is 0 it returns first free port. Otherwise it checks port availability
+++// in pool and return that port or error if port is already busy.
+++func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) {
+++     p.mutex.Lock()
+++     defer p.mutex.Unlock()
+++
+++     if proto != "tcp" && proto != "udp" {
+++             return 0, ErrUnknownProtocol
+++     }
+++
+++     if ip == nil {
+++             ip = defaultIP
+++     }
+++     ipstr := ip.String()
+++     protomap, ok := p.ipMap[ipstr]
+++     if !ok {
+++             protomap = protoMap{
+++                     "tcp": p.newPortMap(),
+++                     "udp": p.newPortMap(),
+++             }
+++
+++             p.ipMap[ipstr] = protomap
+++     }
+++     mapping := protomap[proto]
+++     if port > 0 {
+++             if _, ok := mapping.p[port]; !ok {
+++                     mapping.p[port] = struct{}{}
+++                     return port, nil
+++             }
+++             return 0, newErrPortAlreadyAllocated(ipstr, port)
+++     }
+++
+++     port, err := mapping.findPort()
+++     if err != nil {
+++             return 0, err
+++     }
+++     return port, nil
+++}
+++
+++// ReleasePort releases port from global ports pool for specified ip and proto.
+++func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error {
+++     p.mutex.Lock()
+++     defer p.mutex.Unlock()
+++
+++     if ip == nil {
+++             ip = defaultIP
+++     }
+++     protomap, ok := p.ipMap[ip.String()]
+++     if !ok {
+++             return nil
+++     }
+++     delete(protomap[proto].p, port)
+++     return nil
+++}
+++
+++func (p *PortAllocator) newPortMap() *portMap {
+++     return &portMap{
+++             p:     map[int]struct{}{},
+++             begin: p.Begin,
+++             end:   p.End,
+++             last:  p.End,
+++     }
+++}
+++
+++// ReleaseAll releases all ports for all ips.
+++func (p *PortAllocator) ReleaseAll() error {
+++     p.mutex.Lock()
+++     p.ipMap = ipMapping{}
+++     p.mutex.Unlock()
+++     return nil
+++}
+++
+++func (pm *portMap) findPort() (int, error) {
+++     port := pm.last
+++     for i := 0; i <= pm.end-pm.begin; i++ {
+++             port++
+++             if port > pm.end {
+++                     port = pm.begin
+++             }
+++
+++             if _, ok := pm.p[port]; !ok {
+++                     pm.p[port] = struct{}{}
+++                     pm.last = port
+++                     return port, nil
+++             }
+++     }
+++     return 0, ErrAllPortsAllocated
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..20756494af98a03ab54b6fb1f87ebd57702ab326
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,254 @@@@
+++package portallocator
+++
+++import (
+++     "net"
+++     "testing"
+++
+++     _ "github.com/docker/libnetwork/netutils"
+++)
+++
+++func resetPortAllocator() {
+++     instance = newInstance()
+++}
+++
+++func TestRequestNewPort(t *testing.T) {
+++     p := Get()
+++     defer resetPortAllocator()
+++
+++     port, err := p.RequestPort(defaultIP, "tcp", 0)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if expected := p.Begin; port != expected {
+++             t.Fatalf("Expected port %d got %d", expected, port)
+++     }
+++}
+++
+++func TestRequestSpecificPort(t *testing.T) {
+++     p := Get()
+++     defer resetPortAllocator()
+++
+++     port, err := p.RequestPort(defaultIP, "tcp", 5000)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if port != 5000 {
+++             t.Fatalf("Expected port 5000 got %d", port)
+++     }
+++}
+++
+++func TestReleasePort(t *testing.T) {
+++     p := Get()
+++
+++     port, err := p.RequestPort(defaultIP, "tcp", 5000)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if port != 5000 {
+++             t.Fatalf("Expected port 5000 got %d", port)
+++     }
+++
+++     if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestReuseReleasedPort(t *testing.T) {
+++     p := Get()
+++     defer resetPortAllocator()
+++
+++     port, err := p.RequestPort(defaultIP, "tcp", 5000)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if port != 5000 {
+++             t.Fatalf("Expected port 5000 got %d", port)
+++     }
+++
+++     if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     port, err = p.RequestPort(defaultIP, "tcp", 5000)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++}
+++
+++func TestReleaseUnreadledPort(t *testing.T) {
+++     p := Get()
+++     defer resetPortAllocator()
+++
+++     port, err := p.RequestPort(defaultIP, "tcp", 5000)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if port != 5000 {
+++             t.Fatalf("Expected port 5000 got %d", port)
+++     }
+++
+++     port, err = p.RequestPort(defaultIP, "tcp", 5000)
+++
+++     switch err.(type) {
+++     case ErrPortAlreadyAllocated:
+++     default:
+++             t.Fatalf("Expected port allocation error got %s", err)
+++     }
+++}
+++
+++func TestUnknowProtocol(t *testing.T) {
+++     if _, err := Get().RequestPort(defaultIP, "tcpp", 0); err != ErrUnknownProtocol {
+++             t.Fatalf("Expected error %s got %s", ErrUnknownProtocol, err)
+++     }
+++}
+++
+++func TestAllocateAllPorts(t *testing.T) {
+++     p := Get()
+++     defer resetPortAllocator()
+++
+++     for i := 0; i <= p.End-p.Begin; i++ {
+++             port, err := p.RequestPort(defaultIP, "tcp", 0)
+++             if err != nil {
+++                     t.Fatal(err)
+++             }
+++
+++             if expected := p.Begin + i; port != expected {
+++                     t.Fatalf("Expected port %d got %d", expected, port)
+++             }
+++     }
+++
+++     if _, err := p.RequestPort(defaultIP, "tcp", 0); err != ErrAllPortsAllocated {
+++             t.Fatalf("Expected error %s got %s", ErrAllPortsAllocated, err)
+++     }
+++
+++     _, err := p.RequestPort(defaultIP, "udp", 0)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     // release a port in the middle and ensure we get another tcp port
+++     port := p.Begin + 5
+++     if err := p.ReleasePort(defaultIP, "tcp", port); err != nil {
+++             t.Fatal(err)
+++     }
+++     newPort, err := p.RequestPort(defaultIP, "tcp", 0)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if newPort != port {
+++             t.Fatalf("Expected port %d got %d", port, newPort)
+++     }
+++
+++     // now pm.last == newPort, release it so that it's the only free port of
+++     // the range, and ensure we get it back
+++     if err := p.ReleasePort(defaultIP, "tcp", newPort); err != nil {
+++             t.Fatal(err)
+++     }
+++     port, err = p.RequestPort(defaultIP, "tcp", 0)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if newPort != port {
+++             t.Fatalf("Expected port %d got %d", newPort, port)
+++     }
+++}
+++
+++func BenchmarkAllocatePorts(b *testing.B) {
+++     p := Get()
+++     defer resetPortAllocator()
+++
+++     for i := 0; i < b.N; i++ {
+++             for i := 0; i <= p.End-p.Begin; i++ {
+++                     port, err := p.RequestPort(defaultIP, "tcp", 0)
+++                     if err != nil {
+++                             b.Fatal(err)
+++                     }
+++
+++                     if expected := p.Begin + i; port != expected {
+++                             b.Fatalf("Expected port %d got %d", expected, port)
+++                     }
+++             }
+++             p.ReleaseAll()
+++     }
+++}
+++
+++func TestPortAllocation(t *testing.T) {
+++     p := Get()
+++     defer resetPortAllocator()
+++
+++     ip := net.ParseIP("192.168.0.1")
+++     ip2 := net.ParseIP("192.168.0.2")
+++     if port, err := p.RequestPort(ip, "tcp", 80); err != nil {
+++             t.Fatal(err)
+++     } else if port != 80 {
+++             t.Fatalf("Acquire(80) should return 80, not %d", port)
+++     }
+++     port, err := p.RequestPort(ip, "tcp", 0)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if port <= 0 {
+++             t.Fatalf("Acquire(0) should return a non-zero port")
+++     }
+++
+++     if _, err := p.RequestPort(ip, "tcp", port); err == nil {
+++             t.Fatalf("Acquiring a port already in use should return an error")
+++     }
+++
+++     if newPort, err := p.RequestPort(ip, "tcp", 0); err != nil {
+++             t.Fatal(err)
+++     } else if newPort == port {
+++             t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
+++     }
+++
+++     if _, err := p.RequestPort(ip, "tcp", 80); err == nil {
+++             t.Fatalf("Acquiring a port already in use should return an error")
+++     }
+++     if _, err := p.RequestPort(ip2, "tcp", 80); err != nil {
+++             t.Fatalf("It should be possible to allocate the same port on a different interface")
+++     }
+++     if _, err := p.RequestPort(ip2, "tcp", 80); err == nil {
+++             t.Fatalf("Acquiring a port already in use should return an error")
+++     }
+++     if err := p.ReleasePort(ip, "tcp", 80); err != nil {
+++             t.Fatal(err)
+++     }
+++     if _, err := p.RequestPort(ip, "tcp", 80); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     port, err = p.RequestPort(ip, "tcp", 0)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     port2, err := p.RequestPort(ip, "tcp", port+1)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     port3, err := p.RequestPort(ip, "tcp", 0)
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if port3 == port2 {
+++             t.Fatal("Requesting a dynamic port should never allocate a used port")
+++     }
+++}
+++
+++func TestNoDuplicateBPR(t *testing.T) {
+++     p := Get()
+++     defer resetPortAllocator()
+++
+++     if port, err := p.RequestPort(defaultIP, "tcp", p.Begin); err != nil {
+++             t.Fatal(err)
+++     } else if port != p.Begin {
+++             t.Fatalf("Expected port %d got %d", p.Begin, port)
+++     }
+++
+++     if port, err := p.RequestPort(defaultIP, "tcp", 0); err != nil {
+++             t.Fatal(err)
+++     } else if port == p.Begin {
+++             t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b928e3c61941ddf07b94aafc3cc49aa00adf455f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,219 @@@@
+++package portmapper
+++
+++import (
+++     "errors"
+++     "fmt"
+++     "net"
+++     "sync"
+++
+++     "github.com/Sirupsen/logrus"
+++     "github.com/docker/libnetwork/iptables"
+++     "github.com/docker/libnetwork/portallocator"
+++)
+++
+++type mapping struct {
+++     proto         string
+++     userlandProxy userlandProxy
+++     host          net.Addr
+++     container     net.Addr
+++}
+++
+++var newProxy = newProxyCommand
+++
+++var (
+++     // ErrUnknownBackendAddressType refers to an unknown container or unsupported address type
+++     ErrUnknownBackendAddressType = errors.New("unknown container address type not supported")
+++     // ErrPortMappedForIP refers to a port already mapped to an ip address
+++     ErrPortMappedForIP = errors.New("port is already mapped to ip")
+++     // ErrPortNotMapped refers to an unmapped port
+++     ErrPortNotMapped = errors.New("port is not mapped")
+++)
+++
+++// PortMapper manages the network address translation
+++type PortMapper struct {
+++     chain *iptables.Chain
+++
+++     // udp:ip:port
+++     currentMappings map[string]*mapping
+++     lock            sync.Mutex
+++
+++     Allocator *portallocator.PortAllocator
+++}
+++
+++// New returns a new instance of PortMapper
+++func New() *PortMapper {
+++     return NewWithPortAllocator(portallocator.Get())
+++}
+++
+++// NewWithPortAllocator returns a new instance of PortMapper which will use the specified PortAllocator
+++func NewWithPortAllocator(allocator *portallocator.PortAllocator) *PortMapper {
+++     return &PortMapper{
+++             currentMappings: make(map[string]*mapping),
+++             Allocator:       allocator,
+++     }
+++}
+++
+++// SetIptablesChain sets the specified chain into portmapper
+++func (pm *PortMapper) SetIptablesChain(c *iptables.Chain) {
+++     pm.chain = c
+++}
+++
+++// Map maps the specified container transport address to the host's network address and transport port
+++func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, useProxy bool) (host net.Addr, err error) {
+++     pm.lock.Lock()
+++     defer pm.lock.Unlock()
+++
+++     var (
+++             m                 *mapping
+++             proto             string
+++             allocatedHostPort int
+++     )
+++
+++     switch container.(type) {
+++     case *net.TCPAddr:
+++             proto = "tcp"
+++             if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
+++                     return nil, err
+++             }
+++
+++             m = &mapping{
+++                     proto:     proto,
+++                     host:      &net.TCPAddr{IP: hostIP, Port: allocatedHostPort},
+++                     container: container,
+++             }
+++
+++             if useProxy {
+++                     m.userlandProxy = newProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
+++             } else {
+++                     m.userlandProxy = newDummyProxy(proto, hostIP, allocatedHostPort)
+++             }
+++     case *net.UDPAddr:
+++             proto = "udp"
+++             if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
+++                     return nil, err
+++             }
+++
+++             m = &mapping{
+++                     proto:     proto,
+++                     host:      &net.UDPAddr{IP: hostIP, Port: allocatedHostPort},
+++                     container: container,
+++             }
+++
+++             if useProxy {
+++                     m.userlandProxy = newProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port)
+++             } else {
+++                     m.userlandProxy = newDummyProxy(proto, hostIP, allocatedHostPort)
+++             }
+++     default:
+++             return nil, ErrUnknownBackendAddressType
+++     }
+++
+++     // release the allocated port on any further error during return.
+++     defer func() {
+++             if err != nil {
+++                     pm.Allocator.ReleasePort(hostIP, proto, allocatedHostPort)
+++             }
+++     }()
+++
+++     key := getKey(m.host)
+++     if _, exists := pm.currentMappings[key]; exists {
+++             return nil, ErrPortMappedForIP
+++     }
+++
+++     containerIP, containerPort := getIPAndPort(m.container)
+++     if err := pm.forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
+++             return nil, err
+++     }
+++
+++     cleanup := func() error {
+++             // need to undo the iptables rules before we return
+++             m.userlandProxy.Stop()
+++             pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
+++             if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
+++                     return err
+++             }
+++
+++             return nil
+++     }
+++
+++     if err := m.userlandProxy.Start(); err != nil {
+++             if err := cleanup(); err != nil {
+++                     return nil, fmt.Errorf("Error during port allocation cleanup: %v", err)
+++             }
+++             return nil, err
+++     }
+++
+++     pm.currentMappings[key] = m
+++     return m.host, nil
+++}
+++
+++// Unmap removes stored mapping for the specified host transport address
+++func (pm *PortMapper) Unmap(host net.Addr) error {
+++     pm.lock.Lock()
+++     defer pm.lock.Unlock()
+++
+++     key := getKey(host)
+++     data, exists := pm.currentMappings[key]
+++     if !exists {
+++             return ErrPortNotMapped
+++     }
+++
+++     if data.userlandProxy != nil {
+++             data.userlandProxy.Stop()
+++     }
+++
+++     delete(pm.currentMappings, key)
+++
+++     containerIP, containerPort := getIPAndPort(data.container)
+++     hostIP, hostPort := getIPAndPort(data.host)
+++     if err := pm.forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
+++             logrus.Errorf("Error on iptables delete: %s", err)
+++     }
+++
+++     switch a := host.(type) {
+++     case *net.TCPAddr:
+++             return pm.Allocator.ReleasePort(a.IP, "tcp", a.Port)
+++     case *net.UDPAddr:
+++             return pm.Allocator.ReleasePort(a.IP, "udp", a.Port)
+++     }
+++     return nil
+++}
+++
+++//ReMapAll will re-apply all port mappings
+++func (pm *PortMapper) ReMapAll() {
+++     logrus.Debugln("Re-applying all port mappings.")
+++     for _, data := range pm.currentMappings {
+++             containerIP, containerPort := getIPAndPort(data.container)
+++             hostIP, hostPort := getIPAndPort(data.host)
+++             if err := pm.forward(iptables.Append, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
+++                     logrus.Errorf("Error on iptables add: %s", err)
+++             }
+++     }
+++}
+++
+++func getKey(a net.Addr) string {
+++     switch t := a.(type) {
+++     case *net.TCPAddr:
+++             return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp")
+++     case *net.UDPAddr:
+++             return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp")
+++     }
+++     return ""
+++}
+++
+++func getIPAndPort(a net.Addr) (net.IP, int) {
+++     switch t := a.(type) {
+++     case *net.TCPAddr:
+++             return t.IP, t.Port
+++     case *net.UDPAddr:
+++             return t.IP, t.Port
+++     }
+++     return nil, 0
+++}
+++
+++func (pm *PortMapper) forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
+++     if pm.chain == nil {
+++             return nil
+++     }
+++     return pm.chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..635723de8c419acd3e7824a837642c6fe1461d42
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,271 @@@@
+++package portmapper
+++
+++import (
+++     "net"
+++     "strings"
+++     "testing"
+++
+++     "github.com/docker/libnetwork/iptables"
+++     _ "github.com/docker/libnetwork/netutils"
+++)
+++
+++func init() {
+++     // override this func to mock out the proxy server
+++     newProxy = newMockProxyCommand
+++}
+++
+++func TestSetIptablesChain(t *testing.T) {
+++     pm := New()
+++
+++     c := &iptables.Chain{
+++             Name:   "TEST",
+++             Bridge: "192.168.1.1",
+++     }
+++
+++     if pm.chain != nil {
+++             t.Fatal("chain should be nil at init")
+++     }
+++
+++     pm.SetIptablesChain(c)
+++     if pm.chain == nil {
+++             t.Fatal("chain should not be nil after set")
+++     }
+++}
+++
+++func TestMapTCPPorts(t *testing.T) {
+++     pm := New()
+++     dstIP1 := net.ParseIP("192.168.0.1")
+++     dstIP2 := net.ParseIP("192.168.0.2")
+++     dstAddr1 := &net.TCPAddr{IP: dstIP1, Port: 80}
+++     dstAddr2 := &net.TCPAddr{IP: dstIP2, Port: 80}
+++
+++     srcAddr1 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
+++     srcAddr2 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.2")}
+++
+++     addrEqual := func(addr1, addr2 net.Addr) bool {
+++             return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String())
+++     }
+++
+++     if host, err := pm.Map(srcAddr1, dstIP1, 80, true); err != nil {
+++             t.Fatalf("Failed to allocate port: %s", err)
+++     } else if !addrEqual(dstAddr1, host) {
+++             t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
+++                     dstAddr1.String(), dstAddr1.Network(), host.String(), host.Network())
+++     }
+++
+++     if _, err := pm.Map(srcAddr1, dstIP1, 80, true); err == nil {
+++             t.Fatalf("Port is in use - mapping should have failed")
+++     }
+++
+++     if _, err := pm.Map(srcAddr2, dstIP1, 80, true); err == nil {
+++             t.Fatalf("Port is in use - mapping should have failed")
+++     }
+++
+++     if _, err := pm.Map(srcAddr2, dstIP2, 80, true); err != nil {
+++             t.Fatalf("Failed to allocate port: %s", err)
+++     }
+++
+++     if pm.Unmap(dstAddr1) != nil {
+++             t.Fatalf("Failed to release port")
+++     }
+++
+++     if pm.Unmap(dstAddr2) != nil {
+++             t.Fatalf("Failed to release port")
+++     }
+++
+++     if pm.Unmap(dstAddr2) == nil {
+++             t.Fatalf("Port already released, but no error reported")
+++     }
+++}
+++
+++func TestGetUDPKey(t *testing.T) {
+++     addr := &net.UDPAddr{IP: net.ParseIP("192.168.1.5"), Port: 53}
+++
+++     key := getKey(addr)
+++
+++     if expected := "192.168.1.5:53/udp"; key != expected {
+++             t.Fatalf("expected key %s got %s", expected, key)
+++     }
+++}
+++
+++func TestGetTCPKey(t *testing.T) {
+++     addr := &net.TCPAddr{IP: net.ParseIP("192.168.1.5"), Port: 80}
+++
+++     key := getKey(addr)
+++
+++     if expected := "192.168.1.5:80/tcp"; key != expected {
+++             t.Fatalf("expected key %s got %s", expected, key)
+++     }
+++}
+++
+++func TestGetUDPIPAndPort(t *testing.T) {
+++     addr := &net.UDPAddr{IP: net.ParseIP("192.168.1.5"), Port: 53}
+++
+++     ip, port := getIPAndPort(addr)
+++     if expected := "192.168.1.5"; ip.String() != expected {
+++             t.Fatalf("expected ip %s got %s", expected, ip)
+++     }
+++
+++     if ep := 53; port != ep {
+++             t.Fatalf("expected port %d got %d", ep, port)
+++     }
+++}
+++
+++func TestMapUDPPorts(t *testing.T) {
+++     pm := New()
+++     dstIP1 := net.ParseIP("192.168.0.1")
+++     dstIP2 := net.ParseIP("192.168.0.2")
+++     dstAddr1 := &net.UDPAddr{IP: dstIP1, Port: 80}
+++     dstAddr2 := &net.UDPAddr{IP: dstIP2, Port: 80}
+++
+++     srcAddr1 := &net.UDPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
+++     srcAddr2 := &net.UDPAddr{Port: 1080, IP: net.ParseIP("172.16.0.2")}
+++
+++     addrEqual := func(addr1, addr2 net.Addr) bool {
+++             return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String())
+++     }
+++
+++     if host, err := pm.Map(srcAddr1, dstIP1, 80, true); err != nil {
+++             t.Fatalf("Failed to allocate port: %s", err)
+++     } else if !addrEqual(dstAddr1, host) {
+++             t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
+++                     dstAddr1.String(), dstAddr1.Network(), host.String(), host.Network())
+++     }
+++
+++     if _, err := pm.Map(srcAddr1, dstIP1, 80, true); err == nil {
+++             t.Fatalf("Port is in use - mapping should have failed")
+++     }
+++
+++     if _, err := pm.Map(srcAddr2, dstIP1, 80, true); err == nil {
+++             t.Fatalf("Port is in use - mapping should have failed")
+++     }
+++
+++     if _, err := pm.Map(srcAddr2, dstIP2, 80, true); err != nil {
+++             t.Fatalf("Failed to allocate port: %s", err)
+++     }
+++
+++     if pm.Unmap(dstAddr1) != nil {
+++             t.Fatalf("Failed to release port")
+++     }
+++
+++     if pm.Unmap(dstAddr2) != nil {
+++             t.Fatalf("Failed to release port")
+++     }
+++
+++     if pm.Unmap(dstAddr2) == nil {
+++             t.Fatalf("Port already released, but no error reported")
+++     }
+++}
+++
+++func TestMapAllPortsSingleInterface(t *testing.T) {
+++     pm := New()
+++     dstIP1 := net.ParseIP("0.0.0.0")
+++     srcAddr1 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
+++
+++     hosts := []net.Addr{}
+++     var host net.Addr
+++     var err error
+++
+++     defer func() {
+++             for _, val := range hosts {
+++                     pm.Unmap(val)
+++             }
+++     }()
+++
+++     for i := 0; i < 10; i++ {
+++             start, end := pm.Allocator.Begin, pm.Allocator.End
+++             for i := start; i < end; i++ {
+++                     if host, err = pm.Map(srcAddr1, dstIP1, 0, true); err != nil {
+++                             t.Fatal(err)
+++                     }
+++
+++                     hosts = append(hosts, host)
+++             }
+++
+++             if _, err := pm.Map(srcAddr1, dstIP1, start, true); err == nil {
+++                     t.Fatalf("Port %d should be bound but is not", start)
+++             }
+++
+++             for _, val := range hosts {
+++                     if err := pm.Unmap(val); err != nil {
+++                             t.Fatal(err)
+++                     }
+++             }
+++
+++             hosts = []net.Addr{}
+++     }
+++}
+++
+++func TestMapTCPDummyListen(t *testing.T) {
+++     pm := New()
+++     dstIP := net.ParseIP("0.0.0.0")
+++     dstAddr := &net.TCPAddr{IP: dstIP, Port: 80}
+++
+++     // no-op for dummy
+++     srcAddr := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
+++
+++     addrEqual := func(addr1, addr2 net.Addr) bool {
+++             return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String())
+++     }
+++
+++     if host, err := pm.Map(srcAddr, dstIP, 80, false); err != nil {
+++             t.Fatalf("Failed to allocate port: %s", err)
+++     } else if !addrEqual(dstAddr, host) {
+++             t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
+++                     dstAddr.String(), dstAddr.Network(), host.String(), host.Network())
+++     }
+++     if _, err := net.Listen("tcp", "0.0.0.0:80"); err == nil {
+++             t.Fatal("Listen on mapped port without proxy should fail")
+++     } else {
+++             if !strings.Contains(err.Error(), "address already in use") {
+++                     t.Fatalf("Error should be about address already in use, got %v", err)
+++             }
+++     }
+++     if _, err := net.Listen("tcp", "0.0.0.0:81"); err != nil {
+++             t.Fatal(err)
+++     }
+++     if host, err := pm.Map(srcAddr, dstIP, 81, false); err == nil {
+++             t.Fatalf("Bound port shouldn't be allocated, but it was on: %v", host)
+++     } else {
+++             if !strings.Contains(err.Error(), "address already in use") {
+++                     t.Fatalf("Error should be about address already in use, got %v", err)
+++             }
+++     }
+++}
+++
+++func TestMapUDPDummyListen(t *testing.T) {
+++     pm := New()
+++     dstIP := net.ParseIP("0.0.0.0")
+++     dstAddr := &net.UDPAddr{IP: dstIP, Port: 80}
+++
+++     // no-op for dummy
+++     srcAddr := &net.UDPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
+++
+++     addrEqual := func(addr1, addr2 net.Addr) bool {
+++             return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String())
+++     }
+++
+++     if host, err := pm.Map(srcAddr, dstIP, 80, false); err != nil {
+++             t.Fatalf("Failed to allocate port: %s", err)
+++     } else if !addrEqual(dstAddr, host) {
+++             t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
+++                     dstAddr.String(), dstAddr.Network(), host.String(), host.Network())
+++     }
+++     if _, err := net.ListenUDP("udp", &net.UDPAddr{IP: dstIP, Port: 80}); err == nil {
+++             t.Fatal("Listen on mapped port without proxy should fail")
+++     } else {
+++             if !strings.Contains(err.Error(), "address already in use") {
+++                     t.Fatalf("Error should be about address already in use, got %v", err)
+++             }
+++     }
+++     if _, err := net.ListenUDP("udp", &net.UDPAddr{IP: dstIP, Port: 81}); err != nil {
+++             t.Fatal(err)
+++     }
+++     if host, err := pm.Map(srcAddr, dstIP, 81, false); err == nil {
+++             t.Fatalf("Bound port shouldn't be allocated, but it was on: %v", host)
+++     } else {
+++             if !strings.Contains(err.Error(), "address already in use") {
+++                     t.Fatalf("Error should be about address already in use, got %v", err)
+++             }
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..29b1605889fdb18fbd5ed7787c4985b3ef8a5c41
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,18 @@@@
+++package portmapper
+++
+++import "net"
+++
+++func newMockProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int) userlandProxy {
+++     return &mockProxyCommand{}
+++}
+++
+++type mockProxyCommand struct {
+++}
+++
+++func (p *mockProxyCommand) Start() error {
+++     return nil
+++}
+++
+++func (p *mockProxyCommand) Stop() error {
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..530703b259c6117e9512fb0ceb30502095df6532
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,209 @@@@
+++package portmapper
+++
+++import (
+++     "flag"
+++     "fmt"
+++     "io"
+++     "io/ioutil"
+++     "log"
+++     "net"
+++     "os"
+++     "os/exec"
+++     "os/signal"
+++     "strconv"
+++     "syscall"
+++     "time"
+++
+++     "github.com/docker/docker/pkg/proxy"
+++     "github.com/docker/docker/pkg/reexec"
+++)
+++
+++const userlandProxyCommandName = "docker-proxy"
+++
+++func init() {
+++     reexec.Register(userlandProxyCommandName, execProxy)
+++}
+++
+++type userlandProxy interface {
+++     Start() error
+++     Stop() error
+++}
+++
+++// proxyCommand wraps an exec.Cmd to run the userland TCP and UDP
+++// proxies as separate processes.
+++type proxyCommand struct {
+++     cmd *exec.Cmd
+++}
+++
+++// execProxy is the reexec function that is registered to start the userland proxies
+++func execProxy() {
+++     f := os.NewFile(3, "signal-parent")
+++     host, container := parseHostContainerAddrs()
+++
+++     p, err := proxy.NewProxy(host, container)
+++     if err != nil {
+++             fmt.Fprintf(f, "1\n%s", err)
+++             f.Close()
+++             os.Exit(1)
+++     }
+++     go handleStopSignals(p)
+++     fmt.Fprint(f, "0\n")
+++     f.Close()
+++
+++     // Run will block until the proxy stops
+++     p.Run()
+++}
+++
+++// parseHostContainerAddrs parses the flags passed on reexec to create the TCP or UDP
+++// net.Addrs to map the host and container ports
+++func parseHostContainerAddrs() (host net.Addr, container net.Addr) {
+++     var (
+++             proto         = flag.String("proto", "tcp", "proxy protocol")
+++             hostIP        = flag.String("host-ip", "", "host ip")
+++             hostPort      = flag.Int("host-port", -1, "host port")
+++             containerIP   = flag.String("container-ip", "", "container ip")
+++             containerPort = flag.Int("container-port", -1, "container port")
+++     )
+++
+++     flag.Parse()
+++
+++     switch *proto {
+++     case "tcp":
+++             host = &net.TCPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort}
+++             container = &net.TCPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort}
+++     case "udp":
+++             host = &net.UDPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort}
+++             container = &net.UDPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort}
+++     default:
+++             log.Fatalf("unsupported protocol %s", *proto)
+++     }
+++
+++     return host, container
+++}
+++
+++func handleStopSignals(p proxy.Proxy) {
+++     s := make(chan os.Signal, 10)
+++     signal.Notify(s, os.Interrupt, syscall.SIGTERM, syscall.SIGSTOP)
+++
+++     for _ = range s {
+++             p.Close()
+++
+++             os.Exit(0)
+++     }
+++}
+++
+++func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int) userlandProxy {
+++     args := []string{
+++             userlandProxyCommandName,
+++             "-proto", proto,
+++             "-host-ip", hostIP.String(),
+++             "-host-port", strconv.Itoa(hostPort),
+++             "-container-ip", containerIP.String(),
+++             "-container-port", strconv.Itoa(containerPort),
+++     }
+++
+++     return &proxyCommand{
+++             cmd: &exec.Cmd{
+++                     Path: reexec.Self(),
+++                     Args: args,
+++                     SysProcAttr: &syscall.SysProcAttr{
+++                             Pdeathsig: syscall.SIGTERM, // send a sigterm to the proxy if the daemon process dies
+++                     },
+++             },
+++     }
+++}
+++
+++func (p *proxyCommand) Start() error {
+++     r, w, err := os.Pipe()
+++     if err != nil {
+++             return fmt.Errorf("proxy unable to open os.Pipe %s", err)
+++     }
+++     defer r.Close()
+++     p.cmd.ExtraFiles = []*os.File{w}
+++     if err := p.cmd.Start(); err != nil {
+++             return err
+++     }
+++     w.Close()
+++
+++     errchan := make(chan error, 1)
+++     go func() {
+++             buf := make([]byte, 2)
+++             r.Read(buf)
+++
+++             if string(buf) != "0\n" {
+++                     errStr, err := ioutil.ReadAll(r)
+++                     if err != nil {
+++                             errchan <- fmt.Errorf("Error reading exit status from userland proxy: %v", err)
+++                             return
+++                     }
+++
+++                     errchan <- fmt.Errorf("Error starting userland proxy: %s", errStr)
+++                     return
+++             }
+++             errchan <- nil
+++     }()
+++
+++     select {
+++     case err := <-errchan:
+++             return err
+++     case <-time.After(16 * time.Second):
+++             return fmt.Errorf("Timed out proxy starting the userland proxy")
+++     }
+++}
+++
+++func (p *proxyCommand) Stop() error {
+++     if p.cmd.Process != nil {
+++             if err := p.cmd.Process.Signal(os.Interrupt); err != nil {
+++                     return err
+++             }
+++             return p.cmd.Wait()
+++     }
+++     return nil
+++}
+++
+++// dummyProxy just listen on some port, it is needed to prevent accidental
+++// port allocations on bound port, because without userland proxy we using
+++// iptables rules and not net.Listen
+++type dummyProxy struct {
+++     listener io.Closer
+++     addr     net.Addr
+++}
+++
+++func newDummyProxy(proto string, hostIP net.IP, hostPort int) userlandProxy {
+++     switch proto {
+++     case "tcp":
+++             addr := &net.TCPAddr{IP: hostIP, Port: hostPort}
+++             return &dummyProxy{addr: addr}
+++     case "udp":
+++             addr := &net.UDPAddr{IP: hostIP, Port: hostPort}
+++             return &dummyProxy{addr: addr}
+++     }
+++     return nil
+++}
+++
+++func (p *dummyProxy) Start() error {
+++     switch addr := p.addr.(type) {
+++     case *net.TCPAddr:
+++             l, err := net.ListenTCP("tcp", addr)
+++             if err != nil {
+++                     return err
+++             }
+++             p.listener = l
+++     case *net.UDPAddr:
+++             l, err := net.ListenUDP("udp", addr)
+++             if err != nil {
+++                     return err
+++             }
+++             p.listener = l
+++     default:
+++             return fmt.Errorf("Unknown addr type: %T", p.addr)
+++     }
+++     return nil
+++}
+++
+++func (p *dummyProxy) Stop() error {
+++     if p.listener != nil {
+++             return p.listener.Close()
+++     }
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..cdda554ba5728b7e50bff76dfac225147bc3cee1
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,1 @@@@
+++Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d581a1913d844635018484a4e5c47c85745bdca0
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,17 @@@@
+++package dns
+++
+++import (
+++     "regexp"
+++)
+++
+++// IPLocalhost is a regex patter for localhost IP address range.
+++const IPLocalhost = `((127\.([0-9]{1,3}.){2}[0-9]{1,3})|(::1))`
+++
+++var localhostIPRegexp = regexp.MustCompile(IPLocalhost)
+++
+++// IsLocalhost returns true if ip matches the localhost IP regular expression.
+++// Used for determining if nameserver settings are being passed which are
+++// localhost addresses
+++func IsLocalhost(ip string) bool {
+++     return localhostIPRegexp.MatchString(ip)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ebe3b71aa406c1da67b473849dc1ae010046e007
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,187 @@@@
+++// Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf
+++package resolvconf
+++
+++import (
+++     "bytes"
+++     "io/ioutil"
+++     "regexp"
+++     "strings"
+++     "sync"
+++
+++     "github.com/Sirupsen/logrus"
+++     "github.com/docker/docker/pkg/ioutils"
+++     "github.com/docker/libnetwork/resolvconf/dns"
+++)
+++
+++var (
+++     // Note: the default IPv4 & IPv6 resolvers are set to Google's Public DNS
+++     defaultIPv4Dns = []string{"nameserver 8.8.8.8", "nameserver 8.8.4.4"}
+++     defaultIPv6Dns = []string{"nameserver 2001:4860:4860::8888", "nameserver 2001:4860:4860::8844"}
+++     ipv4NumBlock   = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`
+++     ipv4Address    = `(` + ipv4NumBlock + `\.){3}` + ipv4NumBlock
+++     // This is not an IPv6 address verifier as it will accept a super-set of IPv6, and also
+++     // will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants
+++     // -- e.g. other link-local types -- either won't work in containers or are unnecessary.
+++     // For readability and sufficiency for Docker purposes this seemed more reasonable than a
+++     // 1000+ character regexp with exact and complete IPv6 validation
+++     ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})`
+++
+++     localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + dns.IPLocalhost + `\s*\n*`)
+++     nsIPv6Regexp      = regexp.MustCompile(`(?m)^nameserver\s+` + ipv6Address + `\s*\n*`)
+++     nsRegexp          = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`)
+++     searchRegexp      = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`)
+++)
+++
+++var lastModified struct {
+++     sync.Mutex
+++     sha256   string
+++     contents []byte
+++}
+++
+++// Get returns the contents of /etc/resolv.conf
+++func Get() ([]byte, error) {
+++     resolv, err := ioutil.ReadFile("/etc/resolv.conf")
+++     if err != nil {
+++             return nil, err
+++     }
+++     return resolv, nil
+++}
+++
+++// GetIfChanged retrieves the host /etc/resolv.conf file, checks against the last hash
+++// and, if modified since last check, returns the bytes and new hash.
+++// This feature is used by the resolv.conf updater for containers
+++func GetIfChanged() ([]byte, string, error) {
+++     lastModified.Lock()
+++     defer lastModified.Unlock()
+++
+++     resolv, err := ioutil.ReadFile("/etc/resolv.conf")
+++     if err != nil {
+++             return nil, "", err
+++     }
+++     newHash, err := ioutils.HashData(bytes.NewReader(resolv))
+++     if err != nil {
+++             return nil, "", err
+++     }
+++     if lastModified.sha256 != newHash {
+++             lastModified.sha256 = newHash
+++             lastModified.contents = resolv
+++             return resolv, newHash, nil
+++     }
+++     // nothing changed, so return no data
+++     return nil, "", nil
+++}
+++
+++// GetLastModified retrieves the last used contents and hash of the host resolv.conf.
+++// Used by containers updating on restart
+++func GetLastModified() ([]byte, string) {
+++     lastModified.Lock()
+++     defer lastModified.Unlock()
+++
+++     return lastModified.contents, lastModified.sha256
+++}
+++
+++// FilterResolvDNS cleans up the config in resolvConf.  It has two main jobs:
+++// 1. It looks for localhost (127.*|::1) entries in the provided
+++//    resolv.conf, removing local nameserver entries, and, if the resulting
+++//    cleaned config has no defined nameservers left, adds default DNS entries
+++// 2. Given the caller provides the enable/disable state of IPv6, the filter
+++//    code will remove all IPv6 nameservers if it is not enabled for containers
+++//
+++// It returns a boolean to notify the caller if changes were made at all
+++func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool) ([]byte, bool) {
+++     changed := false
+++     cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{})
+++     // if IPv6 is not enabled, also clean out any IPv6 address nameserver
+++     if !ipv6Enabled {
+++             cleanedResolvConf = nsIPv6Regexp.ReplaceAll(cleanedResolvConf, []byte{})
+++     }
+++     // if the resulting resolvConf has no more nameservers defined, add appropriate
+++     // default DNS servers for IPv4 and (optionally) IPv6
+++     if len(GetNameservers(cleanedResolvConf)) == 0 {
+++             logrus.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers : %v", defaultIPv4Dns)
+++             dns := defaultIPv4Dns
+++             if ipv6Enabled {
+++                     logrus.Infof("IPv6 enabled; Adding default IPv6 external servers : %v", defaultIPv6Dns)
+++                     dns = append(dns, defaultIPv6Dns...)
+++             }
+++             cleanedResolvConf = append(cleanedResolvConf, []byte("\n"+strings.Join(dns, "\n"))...)
+++     }
+++     if !bytes.Equal(resolvConf, cleanedResolvConf) {
+++             changed = true
+++     }
+++     return cleanedResolvConf, changed
+++}
+++
+++// getLines parses input into lines and strips away comments.
+++func getLines(input []byte, commentMarker []byte) [][]byte {
+++     lines := bytes.Split(input, []byte("\n"))
+++     var output [][]byte
+++     for _, currentLine := range lines {
+++             var commentIndex = bytes.Index(currentLine, commentMarker)
+++             if commentIndex == -1 {
+++                     output = append(output, currentLine)
+++             } else {
+++                     output = append(output, currentLine[:commentIndex])
+++             }
+++     }
+++     return output
+++}
+++
+++// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf
+++func GetNameservers(resolvConf []byte) []string {
+++     nameservers := []string{}
+++     for _, line := range getLines(resolvConf, []byte("#")) {
+++             var ns = nsRegexp.FindSubmatch(line)
+++             if len(ns) > 0 {
+++                     nameservers = append(nameservers, string(ns[1]))
+++             }
+++     }
+++     return nameservers
+++}
+++
+++// GetNameserversAsCIDR returns nameservers (if any) listed in
+++// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32")
+++// This function's output is intended for net.ParseCIDR
+++func GetNameserversAsCIDR(resolvConf []byte) []string {
+++     nameservers := []string{}
+++     for _, nameserver := range GetNameservers(resolvConf) {
+++             nameservers = append(nameservers, nameserver+"/32")
+++     }
+++     return nameservers
+++}
+++
+++// GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf
+++// If more than one search line is encountered, only the contents of the last
+++// one is returned.
+++func GetSearchDomains(resolvConf []byte) []string {
+++     domains := []string{}
+++     for _, line := range getLines(resolvConf, []byte("#")) {
+++             match := searchRegexp.FindSubmatch(line)
+++             if match == nil {
+++                     continue
+++             }
+++             domains = strings.Fields(string(match[1]))
+++     }
+++     return domains
+++}
+++
+++// Build writes a configuration file to path containing a "nameserver" entry
+++// for every element in dns, and a "search" entry for every element in
+++// dnsSearch.
+++func Build(path string, dns, dnsSearch []string) error {
+++     content := bytes.NewBuffer(nil)
+++     for _, dns := range dns {
+++             if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil {
+++                     return err
+++             }
+++     }
+++     if len(dnsSearch) > 0 {
+++             if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." {
+++                     if _, err := content.WriteString("search " + searchString + "\n"); err != nil {
+++                             return err
+++                     }
+++             }
+++     }
+++
+++     return ioutil.WriteFile(path, content.Bytes(), 0644)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a21c7afb3e361228a5ad21eeaa9bc9669877922e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,240 @@@@
+++package resolvconf
+++
+++import (
+++     "bytes"
+++     "io/ioutil"
+++     "os"
+++     "testing"
+++
+++     _ "github.com/docker/libnetwork/netutils"
+++)
+++
+++func TestGet(t *testing.T) {
+++     resolvConfUtils, err := Get()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     if string(resolvConfUtils) != string(resolvConfSystem) {
+++             t.Fatalf("/etc/resolv.conf and GetResolvConf have different content.")
+++     }
+++}
+++
+++func TestGetNameservers(t *testing.T) {
+++     for resolv, result := range map[string][]string{`
+++nameserver 1.2.3.4
+++nameserver 40.3.200.10
+++search example.com`: {"1.2.3.4", "40.3.200.10"},
+++             `search example.com`: {},
+++             `nameserver 1.2.3.4
+++search example.com
+++nameserver 4.30.20.100`: {"1.2.3.4", "4.30.20.100"},
+++             ``: {},
+++             `  nameserver 1.2.3.4   `: {"1.2.3.4"},
+++             `search example.com
+++nameserver 1.2.3.4
+++#nameserver 4.3.2.1`: {"1.2.3.4"},
+++             `search example.com
+++nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4"},
+++     } {
+++             test := GetNameservers([]byte(resolv))
+++             if !strSlicesEqual(test, result) {
+++                     t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv)
+++             }
+++     }
+++}
+++
+++func TestGetNameserversAsCIDR(t *testing.T) {
+++     for resolv, result := range map[string][]string{`
+++nameserver 1.2.3.4
+++nameserver 40.3.200.10
+++search example.com`: {"1.2.3.4/32", "40.3.200.10/32"},
+++             `search example.com`: {},
+++             `nameserver 1.2.3.4
+++search example.com
+++nameserver 4.30.20.100`: {"1.2.3.4/32", "4.30.20.100/32"},
+++             ``: {},
+++             `  nameserver 1.2.3.4   `: {"1.2.3.4/32"},
+++             `search example.com
+++nameserver 1.2.3.4
+++#nameserver 4.3.2.1`: {"1.2.3.4/32"},
+++             `search example.com
+++nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4/32"},
+++     } {
+++             test := GetNameserversAsCIDR([]byte(resolv))
+++             if !strSlicesEqual(test, result) {
+++                     t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv)
+++             }
+++     }
+++}
+++
+++func TestGetSearchDomains(t *testing.T) {
+++     for resolv, result := range map[string][]string{
+++             `search example.com`:           {"example.com"},
+++             `search example.com # ignored`: {"example.com"},
+++             `         search         example.com      `: {"example.com"},
+++             `         search         example.com      # ignored`: {"example.com"},
+++             `search foo.example.com example.com`: {"foo.example.com", "example.com"},
+++             `          search          foo.example.com       example.com    `: {"foo.example.com", "example.com"},
+++             `          search          foo.example.com       example.com    # ignored`: {"foo.example.com", "example.com"},
+++             ``:          {},
+++             `# ignored`: {},
+++             `nameserver 1.2.3.4
+++search foo.example.com example.com`: {"foo.example.com", "example.com"},
+++             `nameserver 1.2.3.4
+++search dup1.example.com dup2.example.com
+++search foo.example.com example.com`: {"foo.example.com", "example.com"},
+++             `nameserver 1.2.3.4
+++search foo.example.com example.com
+++nameserver 4.30.20.100`: {"foo.example.com", "example.com"},
+++     } {
+++             test := GetSearchDomains([]byte(resolv))
+++             if !strSlicesEqual(test, result) {
+++                     t.Fatalf("Wrong search domain string {%s} should be %v. Input: %s", test, result, resolv)
+++             }
+++     }
+++}
+++
+++func strSlicesEqual(a, b []string) bool {
+++     if len(a) != len(b) {
+++             return false
+++     }
+++
+++     for i, v := range a {
+++             if v != b[i] {
+++                     return false
+++             }
+++     }
+++
+++     return true
+++}
+++
+++func TestBuild(t *testing.T) {
+++     file, err := ioutil.TempFile("", "")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer os.Remove(file.Name())
+++
+++     err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"search1"})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     content, err := ioutil.ReadFile(file.Name())
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if expected := "nameserver ns1\nnameserver ns2\nnameserver ns3\nsearch search1\n"; !bytes.Contains(content, []byte(expected)) {
+++             t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+++     }
+++}
+++
+++func TestBuildWithZeroLengthDomainSearch(t *testing.T) {
+++     file, err := ioutil.TempFile("", "")
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     defer os.Remove(file.Name())
+++
+++     err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"."})
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     content, err := ioutil.ReadFile(file.Name())
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if expected := "nameserver ns1\nnameserver ns2\nnameserver ns3\n"; !bytes.Contains(content, []byte(expected)) {
+++             t.Fatalf("Expected to find '%s' got '%s'", expected, content)
+++     }
+++     if notExpected := "search ."; bytes.Contains(content, []byte(notExpected)) {
+++             t.Fatalf("Expected to not find '%s' got '%s'", notExpected, content)
+++     }
+++}
+++
+++func TestFilterResolvDns(t *testing.T) {
+++     ns0 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\n"
+++
+++     if result, _ := FilterResolvDNS([]byte(ns0), false); result != nil {
+++             if ns0 != string(result) {
+++                     t.Fatalf("Failed No Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
+++             }
+++     }
+++
+++     ns1 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\nnameserver 127.0.0.1\n"
+++     if result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {
+++             if ns0 != string(result) {
+++                     t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
+++             }
+++     }
+++
+++     ns1 = "nameserver 10.16.60.14\nnameserver 127.0.0.1\nnameserver 10.16.60.21\n"
+++     if result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {
+++             if ns0 != string(result) {
+++                     t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
+++             }
+++     }
+++
+++     ns1 = "nameserver 127.0.1.1\nnameserver 10.16.60.14\nnameserver 10.16.60.21\n"
+++     if result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {
+++             if ns0 != string(result) {
+++                     t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
+++             }
+++     }
+++
+++     ns1 = "nameserver ::1\nnameserver 10.16.60.14\nnameserver 127.0.2.1\nnameserver 10.16.60.21\n"
+++     if result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {
+++             if ns0 != string(result) {
+++                     t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
+++             }
+++     }
+++
+++     ns1 = "nameserver 10.16.60.14\nnameserver ::1\nnameserver 10.16.60.21\nnameserver ::1"
+++     if result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {
+++             if ns0 != string(result) {
+++                     t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
+++             }
+++     }
+++
+++     // with IPv6 disabled (false param), the IPv6 nameserver should be removed
+++     ns1 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\nnameserver ::1"
+++     if result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {
+++             if ns0 != string(result) {
+++                     t.Fatalf("Failed Localhost+IPv6 off: expected \n<%s> got \n<%s>", ns0, string(result))
+++             }
+++     }
+++
+++     // with IPv6 enabled, the IPv6 nameserver should be preserved
+++     ns0 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\n"
+++     ns1 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\nnameserver ::1"
+++     if result, _ := FilterResolvDNS([]byte(ns1), true); result != nil {
+++             if ns0 != string(result) {
+++                     t.Fatalf("Failed Localhost+IPv6 on: expected \n<%s> got \n<%s>", ns0, string(result))
+++             }
+++     }
+++
+++     // with IPv6 enabled, and no non-localhost servers, Google defaults (both IPv4+IPv6) should be added
+++     ns0 = "\nnameserver 8.8.8.8\nnameserver 8.8.4.4\nnameserver 2001:4860:4860::8888\nnameserver 2001:4860:4860::8844"
+++     ns1 = "nameserver 127.0.0.1\nnameserver ::1\nnameserver 127.0.2.1"
+++     if result, _ := FilterResolvDNS([]byte(ns1), true); result != nil {
+++             if ns0 != string(result) {
+++                     t.Fatalf("Failed no Localhost+IPv6 enabled: expected \n<%s> got \n<%s>", ns0, string(result))
+++             }
+++     }
+++
+++     // with IPv6 disabled, and no non-localhost servers, Google defaults (only IPv4) should be added
+++     ns0 = "\nnameserver 8.8.8.8\nnameserver 8.8.4.4"
+++     ns1 = "nameserver 127.0.0.1\nnameserver ::1\nnameserver 127.0.2.1"
+++     if result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {
+++             if ns0 != string(result) {
+++                     t.Fatalf("Failed no Localhost+IPv6 enabled: expected \n<%s> got \n<%s>", ns0, string(result))
+++             }
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..115290d82b7939c7998c128b2965846f8b96559c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,4 @@@@
+++package sandbox
+++
+++// IfaceOption is a function option type to set interface options
+++type IfaceOption func()
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..7fc8c706818b9e9cfec3c6b748eec36003072059
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,370 @@@@
+++package sandbox
+++
+++import (
+++     "fmt"
+++     "net"
+++     "os/exec"
+++     "regexp"
+++     "sync"
+++
+++     "github.com/docker/libnetwork/types"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++// IfaceOption is a function option type to set interface options
+++type IfaceOption func(i *nwIface)
+++
+++type nwIface struct {
+++     srcName     string
+++     dstName     string
+++     master      string
+++     dstMaster   string
+++     address     *net.IPNet
+++     addressIPv6 *net.IPNet
+++     routes      []*net.IPNet
+++     bridge      bool
+++     ns          *networkNamespace
+++     sync.Mutex
+++}
+++
+++func (i *nwIface) SrcName() string {
+++     i.Lock()
+++     defer i.Unlock()
+++
+++     return i.srcName
+++}
+++
+++func (i *nwIface) DstName() string {
+++     i.Lock()
+++     defer i.Unlock()
+++
+++     return i.dstName
+++}
+++
+++func (i *nwIface) DstMaster() string {
+++     i.Lock()
+++     defer i.Unlock()
+++
+++     return i.dstMaster
+++}
+++
+++func (i *nwIface) Bridge() bool {
+++     i.Lock()
+++     defer i.Unlock()
+++
+++     return i.bridge
+++}
+++
+++func (i *nwIface) Master() string {
+++     i.Lock()
+++     defer i.Unlock()
+++
+++     return i.master
+++}
+++
+++func (i *nwIface) Address() *net.IPNet {
+++     i.Lock()
+++     defer i.Unlock()
+++
+++     return types.GetIPNetCopy(i.address)
+++}
+++
+++func (i *nwIface) AddressIPv6() *net.IPNet {
+++     i.Lock()
+++     defer i.Unlock()
+++
+++     return types.GetIPNetCopy(i.addressIPv6)
+++}
+++
+++func (i *nwIface) Routes() []*net.IPNet {
+++     i.Lock()
+++     defer i.Unlock()
+++
+++     routes := make([]*net.IPNet, len(i.routes))
+++     for index, route := range i.routes {
+++             r := types.GetIPNetCopy(route)
+++             routes[index] = r
+++     }
+++
+++     return routes
+++}
+++
+++func (n *networkNamespace) Interfaces() []Interface {
+++     n.Lock()
+++     defer n.Unlock()
+++
+++     ifaces := make([]Interface, len(n.iFaces))
+++
+++     for i, iface := range n.iFaces {
+++             ifaces[i] = iface
+++     }
+++
+++     return ifaces
+++}
+++
+++func (i *nwIface) Remove() error {
+++     i.Lock()
+++     n := i.ns
+++     i.Unlock()
+++
+++     n.Lock()
+++     path := n.path
+++     n.Unlock()
+++
+++     return nsInvoke(path, func(nsFD int) error { return nil }, func(callerFD int) error {
+++             // Find the network inteerface identified by the DstName attribute.
+++             iface, err := netlink.LinkByName(i.DstName())
+++             if err != nil {
+++                     return err
+++             }
+++
+++             // Down the interface before configuring
+++             if err := netlink.LinkSetDown(iface); err != nil {
+++                     return err
+++             }
+++
+++             err = netlink.LinkSetName(iface, i.SrcName())
+++             if err != nil {
+++                     fmt.Println("LinkSetName failed: ", err)
+++                     return err
+++             }
+++
+++             // if it is a bridge just delete it.
+++             if i.Bridge() {
+++                     if err := netlink.LinkDel(iface); err != nil {
+++                             return fmt.Errorf("failed deleting bridge %q: %v", i.SrcName(), err)
+++                     }
+++             } else {
+++                     // Move the network interface to caller namespace.
+++                     if err := netlink.LinkSetNsFd(iface, callerFD); err != nil {
+++                             fmt.Println("LinkSetNsPid failed: ", err)
+++                             return err
+++                     }
+++             }
+++
+++             n.Lock()
+++             for index, intf := range n.iFaces {
+++                     if intf == i {
+++                             n.iFaces = append(n.iFaces[:index], n.iFaces[index+1:]...)
+++                             break
+++                     }
+++             }
+++             n.Unlock()
+++
+++             return nil
+++     })
+++}
+++
+++// Returns the sandbox's side veth interface statistics
+++func (i *nwIface) Statistics() (*InterfaceStatistics, error) {
+++     i.Lock()
+++     n := i.ns
+++     i.Unlock()
+++
+++     n.Lock()
+++     path := n.path
+++     n.Unlock()
+++
+++     s := &InterfaceStatistics{}
+++
+++     err := nsInvoke(path, func(nsFD int) error { return nil }, func(callerFD int) error {
+++             // For some reason ioutil.ReadFile(netStatsFile) reads the file in
+++             // the default netns when this code is invoked from docker.
+++             // Executing "cat <netStatsFile>" works as expected.
+++             data, err := exec.Command("cat", netStatsFile).Output()
+++             if err != nil {
+++                     return fmt.Errorf("failure opening %s: %v", netStatsFile, err)
+++             }
+++             return scanInterfaceStats(string(data), i.DstName(), s)
+++     })
+++
+++     if err != nil {
+++             err = fmt.Errorf("failed to retrieve the statistics for %s in netns %s: %v", i.DstName(), path, err)
+++     }
+++
+++     return s, err
+++}
+++
+++func (n *networkNamespace) findDst(srcName string, isBridge bool) string {
+++     n.Lock()
+++     defer n.Unlock()
+++
+++     for _, i := range n.iFaces {
+++             // The master should match the srcname of the interface and the
+++             // master interface should be of type bridge, if searching for a bridge type
+++             if i.SrcName() == srcName && (!isBridge || i.Bridge()) {
+++                     return i.DstName()
+++             }
+++     }
+++
+++     return ""
+++}
+++
+++func (n *networkNamespace) AddInterface(srcName, dstPrefix string, options ...IfaceOption) error {
+++     i := &nwIface{srcName: srcName, dstName: dstPrefix, ns: n}
+++     i.processInterfaceOptions(options...)
+++
+++     if i.master != "" {
+++             i.dstMaster = n.findDst(i.master, true)
+++             if i.dstMaster == "" {
+++                     return fmt.Errorf("could not find an appropriate master %q for %q",
+++                             i.master, i.srcName)
+++             }
+++     }
+++
+++     n.Lock()
+++     i.dstName = fmt.Sprintf("%s%d", i.dstName, n.nextIfIndex)
+++     n.nextIfIndex++
+++     path := n.path
+++     n.Unlock()
+++
+++     return nsInvoke(path, func(nsFD int) error {
+++             // If it is a bridge interface we have to create the bridge inside
+++             // the namespace so don't try to lookup the interface using srcName
+++             if i.bridge {
+++                     return nil
+++             }
+++
+++             // Find the network interface identified by the SrcName attribute.
+++             iface, err := netlink.LinkByName(i.srcName)
+++             if err != nil {
+++                     return fmt.Errorf("failed to get link by name %q: %v", i.srcName, err)
+++             }
+++
+++             // Move the network interface to the destination namespace.
+++             if err := netlink.LinkSetNsFd(iface, nsFD); err != nil {
+++                     return fmt.Errorf("failed to set namespace on link %q: %v", i.srcName, err)
+++             }
+++
+++             return nil
+++     }, func(callerFD int) error {
+++             if i.bridge {
+++                     link := &netlink.Bridge{
+++                             LinkAttrs: netlink.LinkAttrs{
+++                                     Name: i.srcName,
+++                             },
+++                     }
+++
+++                     if err := netlink.LinkAdd(link); err != nil {
+++                             return fmt.Errorf("failed to create bridge %q: %v", i.srcName, err)
+++                     }
+++             }
+++
+++             // Find the network interface identified by the SrcName attribute.
+++             iface, err := netlink.LinkByName(i.srcName)
+++             if err != nil {
+++                     return fmt.Errorf("failed to get link by name %q: %v", i.srcName, err)
+++             }
+++
+++             // Down the interface before configuring
+++             if err := netlink.LinkSetDown(iface); err != nil {
+++                     return fmt.Errorf("failed to set link down: %v", err)
+++             }
+++
+++             // Configure the interface now this is moved in the proper namespace.
+++             if err := configureInterface(iface, i); err != nil {
+++                     return err
+++             }
+++
+++             // Up the interface.
+++             if err := netlink.LinkSetUp(iface); err != nil {
+++                     return fmt.Errorf("failed to set link up: %v", err)
+++             }
+++
+++             n.Lock()
+++             n.iFaces = append(n.iFaces, i)
+++             n.Unlock()
+++
+++             return nil
+++     })
+++}
+++
+++func configureInterface(iface netlink.Link, i *nwIface) error {
+++     ifaceName := iface.Attrs().Name
+++     ifaceConfigurators := []struct {
+++             Fn         func(netlink.Link, *nwIface) error
+++             ErrMessage string
+++     }{
+++             {setInterfaceName, fmt.Sprintf("error renaming interface %q to %q", ifaceName, i.DstName())},
+++             {setInterfaceIP, fmt.Sprintf("error setting interface %q IP to %q", ifaceName, i.Address())},
+++             {setInterfaceIPv6, fmt.Sprintf("error setting interface %q IPv6 to %q", ifaceName, i.AddressIPv6())},
+++             {setInterfaceRoutes, fmt.Sprintf("error setting interface %q routes to %q", ifaceName, i.Routes())},
+++             {setInterfaceMaster, fmt.Sprintf("error setting interface %q master to %q", ifaceName, i.DstMaster())},
+++     }
+++
+++     for _, config := range ifaceConfigurators {
+++             if err := config.Fn(iface, i); err != nil {
+++                     return fmt.Errorf("%s: %v", config.ErrMessage, err)
+++             }
+++     }
+++     return nil
+++}
+++
+++func setInterfaceMaster(iface netlink.Link, i *nwIface) error {
+++     if i.DstMaster() == "" {
+++             return nil
+++     }
+++
+++     return netlink.LinkSetMaster(iface, &netlink.Bridge{
+++             LinkAttrs: netlink.LinkAttrs{Name: i.DstMaster()}})
+++}
+++
+++func setInterfaceIP(iface netlink.Link, i *nwIface) error {
+++     if i.Address() == nil {
+++             return nil
+++     }
+++
+++     ipAddr := &netlink.Addr{IPNet: i.Address(), Label: ""}
+++     return netlink.AddrAdd(iface, ipAddr)
+++}
+++
+++func setInterfaceIPv6(iface netlink.Link, i *nwIface) error {
+++     if i.AddressIPv6() == nil {
+++             return nil
+++     }
+++     ipAddr := &netlink.Addr{IPNet: i.AddressIPv6(), Label: ""}
+++     return netlink.AddrAdd(iface, ipAddr)
+++}
+++
+++func setInterfaceName(iface netlink.Link, i *nwIface) error {
+++     return netlink.LinkSetName(iface, i.DstName())
+++}
+++
+++func setInterfaceRoutes(iface netlink.Link, i *nwIface) error {
+++     for _, route := range i.Routes() {
+++             err := netlink.RouteAdd(&netlink.Route{
+++                     Scope:     netlink.SCOPE_LINK,
+++                     LinkIndex: iface.Attrs().Index,
+++                     Dst:       route,
+++             })
+++             if err != nil {
+++                     return err
+++             }
+++     }
+++     return nil
+++}
+++
+++// In older kernels (like the one in Centos 6.6 distro) sysctl does not have netns support. Therefore
+++// we cannot gather the statistics from /sys/class/net/<dev>/statistics/<counter> files. Per-netns stats
+++// are naturally found in /proc/net/dev in kernels which support netns (ifconfig relyes on that).
+++const (
+++     netStatsFile = "/proc/net/dev"
+++     base         = "[ ]*%s:([ ]+[0-9]+){16}"
+++)
+++
+++func scanInterfaceStats(data, ifName string, i *InterfaceStatistics) error {
+++     var (
+++             bktStr string
+++             bkt    uint64
+++     )
+++
+++     regex := fmt.Sprintf(base, ifName)
+++     re := regexp.MustCompile(regex)
+++     line := re.FindString(data)
+++
+++     _, err := fmt.Sscanf(line, "%s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d",
+++             &bktStr, &i.RxBytes, &i.RxPackets, &i.RxErrors, &i.RxDropped, &bkt, &bkt, &bkt,
+++             &bkt, &i.TxBytes, &i.TxPackets, &i.TxErrors, &i.TxDropped, &bkt, &bkt, &bkt, &bkt)
+++
+++     return err
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..115290d82b7939c7998c128b2965846f8b96559c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,4 @@@@
+++package sandbox
+++
+++// IfaceOption is a function option type to set interface options
+++type IfaceOption func()
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..65246413316f26e3b48e897b41cae9d5b894b70e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,327 @@@@
+++package sandbox
+++
+++import (
+++     "fmt"
+++     "net"
+++     "os"
+++     "os/exec"
+++     "runtime"
+++     "sync"
+++     "syscall"
+++     "time"
+++
+++     log "github.com/Sirupsen/logrus"
+++     "github.com/docker/docker/pkg/reexec"
+++     "github.com/docker/libnetwork/types"
+++     "github.com/vishvananda/netlink"
+++     "github.com/vishvananda/netns"
+++)
+++
+++const prefix = "/var/run/docker/netns"
+++
+++var (
+++     once             sync.Once
+++     garbagePathMap   = make(map[string]bool)
+++     gpmLock          sync.Mutex
+++     gpmWg            sync.WaitGroup
+++     gpmCleanupPeriod = 60 * time.Second
+++     gpmChan          = make(chan chan struct{})
+++     nsOnce           sync.Once
+++     initNs           netns.NsHandle
+++)
+++
+++// The networkNamespace type is the linux implementation of the Sandbox
+++// interface. It represents a linux network namespace, and moves an interface
+++// into it when called on method AddInterface or sets the gateway etc.
+++type networkNamespace struct {
+++     path         string
+++     iFaces       []*nwIface
+++     gw           net.IP
+++     gwv6         net.IP
+++     staticRoutes []*types.StaticRoute
+++     neighbors    []*neigh
+++     nextIfIndex  int
+++     sync.Mutex
+++}
+++
+++func init() {
+++     reexec.Register("netns-create", reexecCreateNamespace)
+++}
+++
+++func createBasePath() {
+++     err := os.MkdirAll(prefix, 0644)
+++     if err != nil && !os.IsExist(err) {
+++             panic("Could not create net namespace path directory")
+++     }
+++
+++     // Start the garbage collection go routine
+++     go removeUnusedPaths()
+++}
+++
+++func removeUnusedPaths() {
+++     gpmLock.Lock()
+++     period := gpmCleanupPeriod
+++     gpmLock.Unlock()
+++
+++     ticker := time.NewTicker(period)
+++     for {
+++             var (
+++                     gc   chan struct{}
+++                     gcOk bool
+++             )
+++
+++             select {
+++             case <-ticker.C:
+++             case gc, gcOk = <-gpmChan:
+++             }
+++
+++             gpmLock.Lock()
+++             pathList := make([]string, 0, len(garbagePathMap))
+++             for path := range garbagePathMap {
+++                     pathList = append(pathList, path)
+++             }
+++             garbagePathMap = make(map[string]bool)
+++             gpmWg.Add(1)
+++             gpmLock.Unlock()
+++
+++             for _, path := range pathList {
+++                     os.Remove(path)
+++             }
+++
+++             gpmWg.Done()
+++             if gcOk {
+++                     close(gc)
+++             }
+++     }
+++}
+++
+++func addToGarbagePaths(path string) {
+++     gpmLock.Lock()
+++     garbagePathMap[path] = true
+++     gpmLock.Unlock()
+++}
+++
+++func removeFromGarbagePaths(path string) {
+++     gpmLock.Lock()
+++     delete(garbagePathMap, path)
+++     gpmLock.Unlock()
+++}
+++
+++// GC triggers garbage collection of namespace path right away
+++// and waits for it.
+++func GC() {
+++     gpmLock.Lock()
+++     if len(garbagePathMap) == 0 {
+++             // No need for GC if map is empty
+++             gpmLock.Unlock()
+++             return
+++     }
+++     gpmLock.Unlock()
+++
+++     // if content exists in the garbage paths
+++     // we can trigger GC to run, providing a
+++     // channel to be notified on completion
+++     waitGC := make(chan struct{})
+++     gpmChan <- waitGC
+++     // wait for GC completion
+++     <-waitGC
+++}
+++
+++// GenerateKey generates a sandbox key based on the passed
+++// container id.
+++func GenerateKey(containerID string) string {
+++     maxLen := 12
+++     if len(containerID) < maxLen {
+++             maxLen = len(containerID)
+++     }
+++
+++     return prefix + "/" + containerID[:maxLen]
+++}
+++
+++// NewSandbox provides a new sandbox instance created in an os specific way
+++// provided a key which uniquely identifies the sandbox
+++func NewSandbox(key string, osCreate bool) (Sandbox, error) {
+++     err := createNetworkNamespace(key, osCreate)
+++     if err != nil {
+++             return nil, err
+++     }
+++
+++     return &networkNamespace{path: key}, nil
+++}
+++
+++func (n *networkNamespace) InterfaceOptions() IfaceOptionSetter {
+++     return n
+++}
+++
+++func (n *networkNamespace) NeighborOptions() NeighborOptionSetter {
+++     return n
+++}
+++
+++func reexecCreateNamespace() {
+++     if len(os.Args) < 2 {
+++             log.Fatal("no namespace path provided")
+++     }
+++
+++     if err := syscall.Mount("/proc/self/ns/net", os.Args[1], "bind", syscall.MS_BIND, ""); err != nil {
+++             log.Fatal(err)
+++     }
+++
+++     if err := loopbackUp(); err != nil {
+++             log.Fatal(err)
+++     }
+++}
+++
+++func createNetworkNamespace(path string, osCreate bool) error {
+++     runtime.LockOSThread()
+++     defer runtime.UnlockOSThread()
+++
+++     origns, err := netns.Get()
+++     if err != nil {
+++             return err
+++     }
+++     defer origns.Close()
+++
+++     if err := createNamespaceFile(path); err != nil {
+++             return err
+++     }
+++
+++     cmd := &exec.Cmd{
+++             Path:   reexec.Self(),
+++             Args:   append([]string{"netns-create"}, path),
+++             Stdout: os.Stdout,
+++             Stderr: os.Stderr,
+++     }
+++     if osCreate {
+++             cmd.SysProcAttr = &syscall.SysProcAttr{}
+++             cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWNET
+++     }
+++     if err := cmd.Run(); err != nil {
+++             return fmt.Errorf("namespace creation reexec command failed: %v", err)
+++     }
+++
+++     return nil
+++}
+++
+++func unmountNamespaceFile(path string) {
+++     if _, err := os.Stat(path); err == nil {
+++             syscall.Unmount(path, syscall.MNT_DETACH)
+++     }
+++}
+++
+++func createNamespaceFile(path string) (err error) {
+++     var f *os.File
+++
+++     once.Do(createBasePath)
+++     // Remove it from garbage collection list if present
+++     removeFromGarbagePaths(path)
+++
+++     // If the path is there unmount it first
+++     unmountNamespaceFile(path)
+++
+++     // wait for garbage collection to complete if it is in progress
+++     // before trying to create the file.
+++     gpmWg.Wait()
+++
+++     if f, err = os.Create(path); err == nil {
+++             f.Close()
+++     }
+++
+++     return err
+++}
+++
+++func loopbackUp() error {
+++     iface, err := netlink.LinkByName("lo")
+++     if err != nil {
+++             return err
+++     }
+++     return netlink.LinkSetUp(iface)
+++}
+++
+++func (n *networkNamespace) InvokeFunc(f func()) error {
+++     return nsInvoke(n.nsPath(), func(nsFD int) error { return nil }, func(callerFD int) error {
+++             f()
+++             return nil
+++     })
+++}
+++
+++func getLink() (string, error) {
+++     return os.Readlink(fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), syscall.Gettid()))
+++}
+++
+++func nsInit() {
+++     var err error
+++
+++     if initNs, err = netns.Get(); err != nil {
+++             log.Errorf("could not get initial namespace: %v", err)
+++     }
+++}
+++
+++// InitOSContext initializes OS context while configuring network resources
+++func InitOSContext() func() {
+++     runtime.LockOSThread()
+++     nsOnce.Do(nsInit)
+++     if err := netns.Set(initNs); err != nil {
+++             linkInfo, linkErr := getLink()
+++             if linkErr != nil {
+++                     linkInfo = linkErr.Error()
+++             }
+++
+++             log.Errorf("failed to set to initial namespace, %v, initns fd %d: %v",
+++                     linkInfo, initNs, err)
+++     }
+++
+++     return runtime.UnlockOSThread
+++}
+++
+++func nsInvoke(path string, prefunc func(nsFD int) error, postfunc func(callerFD int) error) error {
+++     defer InitOSContext()()
+++
+++     f, err := os.OpenFile(path, os.O_RDONLY, 0)
+++     if err != nil {
+++             return fmt.Errorf("failed get network namespace %q: %v", path, err)
+++     }
+++     defer f.Close()
+++
+++     nsFD := f.Fd()
+++
+++     // Invoked before the namespace switch happens but after the namespace file
+++     // handle is obtained.
+++     if err := prefunc(int(nsFD)); err != nil {
+++             return fmt.Errorf("failed in prefunc: %v", err)
+++     }
+++
+++     if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
+++             return err
+++     }
+++     defer netns.Set(initNs)
+++
+++     // Invoked after the namespace switch.
+++     return postfunc(int(initNs))
+++}
+++
+++func (n *networkNamespace) nsPath() string {
+++     n.Lock()
+++     defer n.Unlock()
+++
+++     return n.path
+++}
+++
+++func (n *networkNamespace) Info() Info {
+++     return n
+++}
+++
+++func (n *networkNamespace) Key() string {
+++     return n.path
+++}
+++
+++func (n *networkNamespace) Destroy() error {
+++     // Assuming no running process is executing in this network namespace,
+++     // unmounting is sufficient to destroy it.
+++     if err := syscall.Unmount(n.path, syscall.MNT_DETACH); err != nil {
+++             return err
+++     }
+++
+++     // Stash it into the garbage collection list
+++     addToGarbagePaths(n.path)
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9d38206bd74a53477f0dba6c4f8a70d44df21a85
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,8 @@@@
+++// +build !linux,!windows,!freebsd
+++
+++package sandbox
+++
+++// GC triggers garbage collection of namespace path right away
+++// and waits for it.
+++func GC() {
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..52893283008a1d8a33c1251f70e5c150635994dd
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,28 @@@@
+++package sandbox
+++
+++// GenerateKey generates a sandbox key based on the passed
+++// container id.
+++func GenerateKey(containerID string) string {
+++     maxLen := 12
+++     if len(containerID) < maxLen {
+++             maxLen = len(containerID)
+++     }
+++
+++     return containerID[:maxLen]
+++}
+++
+++// NewSandbox provides a new sandbox instance created in an os specific way
+++// provided a key which uniquely identifies the sandbox
+++func NewSandbox(key string, osCreate bool) (Sandbox, error) {
+++     return nil, nil
+++}
+++
+++// GC triggers garbage collection of namespace path right away
+++// and waits for it.
+++func GC() {
+++}
+++
+++// InitOSContext initializes OS context while configuring network resources
+++func InitOSContext() func() {
+++     return func() {}
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..58b30587e25b49c6c93cbd3eb4c4681f0ecf3566
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,4 @@@@
+++package sandbox
+++
+++// NeighOption is a function option type to set neighbor options
+++type NeighOption func()
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..873f14f193b5da093157cb3e4768e27de8f2324e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,138 @@@@
+++package sandbox
+++
+++import (
+++     "bytes"
+++     "fmt"
+++     "net"
+++
+++     "github.com/vishvananda/netlink"
+++)
+++
+++// NeighOption is a function option type to set interface options
+++type NeighOption func(nh *neigh)
+++
+++type neigh struct {
+++     dstIP    net.IP
+++     dstMac   net.HardwareAddr
+++     linkName string
+++     linkDst  string
+++     family   int
+++}
+++
+++func (n *networkNamespace) findNeighbor(dstIP net.IP, dstMac net.HardwareAddr) *neigh {
+++     n.Lock()
+++     defer n.Unlock()
+++
+++     for _, nh := range n.neighbors {
+++             if nh.dstIP.Equal(dstIP) && bytes.Equal(nh.dstMac, dstMac) {
+++                     return nh
+++             }
+++     }
+++
+++     return nil
+++}
+++
+++func (n *networkNamespace) DeleteNeighbor(dstIP net.IP, dstMac net.HardwareAddr) error {
+++     nh := n.findNeighbor(dstIP, dstMac)
+++     if nh == nil {
+++             return fmt.Errorf("could not find the neighbor entry to delete")
+++     }
+++
+++     return nsInvoke(n.nsPath(), func(nsFD int) error { return nil }, func(callerFD int) error {
+++             var iface netlink.Link
+++
+++             if nh.linkDst != "" {
+++                     var err error
+++                     iface, err = netlink.LinkByName(nh.linkDst)
+++                     if err != nil {
+++                             return fmt.Errorf("could not find interface with destination name %s: %v",
+++                                     nh.linkDst, err)
+++                     }
+++             }
+++
+++             nlnh := &netlink.Neigh{
+++                     IP:     dstIP,
+++                     State:  netlink.NUD_PERMANENT,
+++                     Family: nh.family,
+++             }
+++
+++             if nlnh.Family > 0 {
+++                     nlnh.HardwareAddr = dstMac
+++                     nlnh.Flags = netlink.NTF_SELF
+++             }
+++
+++             if nh.linkDst != "" {
+++                     nlnh.LinkIndex = iface.Attrs().Index
+++             }
+++
+++             if err := netlink.NeighDel(nlnh); err != nil {
+++                     return fmt.Errorf("could not delete neighbor entry: %v", err)
+++             }
+++
+++             for i, nh := range n.neighbors {
+++                     if nh.dstIP.Equal(dstIP) && bytes.Equal(nh.dstMac, dstMac) {
+++                             n.neighbors = append(n.neighbors[:i], n.neighbors[i+1:]...)
+++                     }
+++             }
+++
+++             return nil
+++     })
+++}
+++
+++func (n *networkNamespace) AddNeighbor(dstIP net.IP, dstMac net.HardwareAddr, options ...NeighOption) error {
+++     nh := n.findNeighbor(dstIP, dstMac)
+++     if nh != nil {
+++             // If it exists silently return
+++             return nil
+++     }
+++
+++     nh = &neigh{
+++             dstIP:  dstIP,
+++             dstMac: dstMac,
+++     }
+++
+++     nh.processNeighOptions(options...)
+++
+++     if nh.linkName != "" {
+++             nh.linkDst = n.findDst(nh.linkName, false)
+++             if nh.linkDst == "" {
+++                     return fmt.Errorf("could not find the interface with name %s", nh.linkName)
+++             }
+++     }
+++
+++     return nsInvoke(n.nsPath(), func(nsFD int) error { return nil }, func(callerFD int) error {
+++             var iface netlink.Link
+++
+++             if nh.linkDst != "" {
+++                     var err error
+++                     iface, err = netlink.LinkByName(nh.linkDst)
+++                     if err != nil {
+++                             return fmt.Errorf("could not find interface with destination name %s: %v",
+++                                     nh.linkDst, err)
+++                     }
+++             }
+++
+++             nlnh := &netlink.Neigh{
+++                     IP:           dstIP,
+++                     HardwareAddr: dstMac,
+++                     State:        netlink.NUD_PERMANENT,
+++                     Family:       nh.family,
+++             }
+++
+++             if nlnh.Family > 0 {
+++                     nlnh.Flags = netlink.NTF_SELF
+++             }
+++
+++             if nh.linkDst != "" {
+++                     nlnh.LinkIndex = iface.Attrs().Index
+++             }
+++
+++             if err := netlink.NeighSet(nlnh); err != nil {
+++                     return fmt.Errorf("could not add neighbor entry: %v", err)
+++             }
+++
+++             n.neighbors = append(n.neighbors, nh)
+++
+++             return nil
+++     })
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..58b30587e25b49c6c93cbd3eb4c4681f0ecf3566
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,4 @@@@
+++package sandbox
+++
+++// NeighOption is a function option type to set neighbor options
+++type NeighOption func()
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e34699c790028bb6bb3af1ced298e9871ee74b7d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,61 @@@@
+++package sandbox
+++
+++import "net"
+++
+++func (nh *neigh) processNeighOptions(options ...NeighOption) {
+++     for _, opt := range options {
+++             if opt != nil {
+++                     opt(nh)
+++             }
+++     }
+++}
+++
+++func (n *networkNamespace) LinkName(name string) NeighOption {
+++     return func(nh *neigh) {
+++             nh.linkName = name
+++     }
+++}
+++
+++func (n *networkNamespace) Family(family int) NeighOption {
+++     return func(nh *neigh) {
+++             nh.family = family
+++     }
+++}
+++
+++func (i *nwIface) processInterfaceOptions(options ...IfaceOption) {
+++     for _, opt := range options {
+++             if opt != nil {
+++                     opt(i)
+++             }
+++     }
+++}
+++
+++func (n *networkNamespace) Bridge(isBridge bool) IfaceOption {
+++     return func(i *nwIface) {
+++             i.bridge = isBridge
+++     }
+++}
+++
+++func (n *networkNamespace) Master(name string) IfaceOption {
+++     return func(i *nwIface) {
+++             i.master = name
+++     }
+++}
+++
+++func (n *networkNamespace) Address(addr *net.IPNet) IfaceOption {
+++     return func(i *nwIface) {
+++             i.address = addr
+++     }
+++}
+++
+++func (n *networkNamespace) AddressIPv6(addr *net.IPNet) IfaceOption {
+++     return func(i *nwIface) {
+++             i.addressIPv6 = addr
+++     }
+++}
+++
+++func (n *networkNamespace) Routes(routes []*net.IPNet) IfaceOption {
+++     return func(i *nwIface) {
+++             i.routes = routes
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..946e364860572e264c7b77cad62d42c577e90375
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,197 @@@@
+++package sandbox
+++
+++import (
+++     "fmt"
+++     "net"
+++
+++     "github.com/docker/libnetwork/types"
+++     "github.com/vishvananda/netlink"
+++)
+++
+++func (n *networkNamespace) Gateway() net.IP {
+++     n.Lock()
+++     defer n.Unlock()
+++
+++     return n.gw
+++}
+++
+++func (n *networkNamespace) GatewayIPv6() net.IP {
+++     n.Lock()
+++     defer n.Unlock()
+++
+++     return n.gwv6
+++}
+++
+++func (n *networkNamespace) StaticRoutes() []*types.StaticRoute {
+++     n.Lock()
+++     defer n.Unlock()
+++
+++     routes := make([]*types.StaticRoute, len(n.staticRoutes))
+++     for i, route := range n.staticRoutes {
+++             r := route.GetCopy()
+++             routes[i] = r
+++     }
+++
+++     return routes
+++}
+++
+++func (n *networkNamespace) setGateway(gw net.IP) {
+++     n.Lock()
+++     n.gw = gw
+++     n.Unlock()
+++}
+++
+++func (n *networkNamespace) setGatewayIPv6(gwv6 net.IP) {
+++     n.Lock()
+++     n.gwv6 = gwv6
+++     n.Unlock()
+++}
+++
+++func (n *networkNamespace) SetGateway(gw net.IP) error {
+++     // Silently return if the gateway is empty
+++     if len(gw) == 0 {
+++             return nil
+++     }
+++
+++     err := programGateway(n.nsPath(), gw, true)
+++     if err == nil {
+++             n.setGateway(gw)
+++     }
+++
+++     return err
+++}
+++
+++func (n *networkNamespace) UnsetGateway() error {
+++     gw := n.Gateway()
+++
+++     // Silently return if the gateway is empty
+++     if len(gw) == 0 {
+++             return nil
+++     }
+++
+++     err := programGateway(n.nsPath(), gw, false)
+++     if err == nil {
+++             n.setGateway(net.IP{})
+++     }
+++
+++     return err
+++}
+++
+++func programGateway(path string, gw net.IP, isAdd bool) error {
+++     return nsInvoke(path, func(nsFD int) error { return nil }, func(callerFD int) error {
+++             gwRoutes, err := netlink.RouteGet(gw)
+++             if err != nil {
+++                     return fmt.Errorf("route for the gateway %s could not be found: %v", gw, err)
+++             }
+++
+++             if isAdd {
+++                     return netlink.RouteAdd(&netlink.Route{
+++                             Scope:     netlink.SCOPE_UNIVERSE,
+++                             LinkIndex: gwRoutes[0].LinkIndex,
+++                             Gw:        gw,
+++                     })
+++             }
+++
+++             return netlink.RouteDel(&netlink.Route{
+++                     Scope:     netlink.SCOPE_UNIVERSE,
+++                     LinkIndex: gwRoutes[0].LinkIndex,
+++                     Gw:        gw,
+++             })
+++     })
+++}
+++
+++// Program a route in to the namespace routing table.
+++func programRoute(path string, dest *net.IPNet, nh net.IP) error {
+++     return nsInvoke(path, func(nsFD int) error { return nil }, func(callerFD int) error {
+++             gwRoutes, err := netlink.RouteGet(nh)
+++             if err != nil {
+++                     return fmt.Errorf("route for the next hop %s could not be found: %v", nh, err)
+++             }
+++
+++             return netlink.RouteAdd(&netlink.Route{
+++                     Scope:     netlink.SCOPE_UNIVERSE,
+++                     LinkIndex: gwRoutes[0].LinkIndex,
+++                     Gw:        nh,
+++                     Dst:       dest,
+++             })
+++     })
+++}
+++
+++// Delete a route from the namespace routing table.
+++func removeRoute(path string, dest *net.IPNet, nh net.IP) error {
+++     return nsInvoke(path, func(nsFD int) error { return nil }, func(callerFD int) error {
+++             gwRoutes, err := netlink.RouteGet(nh)
+++             if err != nil {
+++                     return fmt.Errorf("route for the next hop could not be found: %v", err)
+++             }
+++
+++             return netlink.RouteDel(&netlink.Route{
+++                     Scope:     netlink.SCOPE_UNIVERSE,
+++                     LinkIndex: gwRoutes[0].LinkIndex,
+++                     Gw:        nh,
+++                     Dst:       dest,
+++             })
+++     })
+++}
+++
+++func (n *networkNamespace) SetGatewayIPv6(gwv6 net.IP) error {
+++     // Silently return if the gateway is empty
+++     if len(gwv6) == 0 {
+++             return nil
+++     }
+++
+++     err := programGateway(n.nsPath(), gwv6, true)
+++     if err == nil {
+++             n.SetGatewayIPv6(gwv6)
+++     }
+++
+++     return err
+++}
+++
+++func (n *networkNamespace) UnsetGatewayIPv6() error {
+++     gwv6 := n.GatewayIPv6()
+++
+++     // Silently return if the gateway is empty
+++     if len(gwv6) == 0 {
+++             return nil
+++     }
+++
+++     err := programGateway(n.nsPath(), gwv6, false)
+++     if err == nil {
+++             n.Lock()
+++             n.gwv6 = net.IP{}
+++             n.Unlock()
+++     }
+++
+++     return err
+++}
+++
+++func (n *networkNamespace) AddStaticRoute(r *types.StaticRoute) error {
+++     err := programRoute(n.nsPath(), r.Destination, r.NextHop)
+++     if err == nil {
+++             n.Lock()
+++             n.staticRoutes = append(n.staticRoutes, r)
+++             n.Unlock()
+++     }
+++     return err
+++}
+++
+++func (n *networkNamespace) RemoveStaticRoute(r *types.StaticRoute) error {
+++
+++     err := removeRoute(n.nsPath(), r.Destination, r.NextHop)
+++     if err == nil {
+++             n.Lock()
+++             lastIndex := len(n.staticRoutes) - 1
+++             for i, v := range n.staticRoutes {
+++                     if v == r {
+++                             // Overwrite the route we're removing with the last element
+++                             n.staticRoutes[i] = n.staticRoutes[lastIndex]
+++                             // Shorten the slice to trim the extra element
+++                             n.staticRoutes = n.staticRoutes[:lastIndex]
+++                             break
+++                     }
+++             }
+++             n.Unlock()
+++     }
+++     return err
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..f32bcfe30a566b034bc2290159ffb13c5663daf8
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,170 @@@@
+++package sandbox
+++
+++import (
+++     "fmt"
+++     "net"
+++
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++// Sandbox represents a network sandbox, identified by a specific key.  It
+++// holds a list of Interfaces, routes etc, and more can be added dynamically.
+++type Sandbox interface {
+++     // The path where the network namespace is mounted.
+++     Key() string
+++
+++     // Add an existing Interface to this sandbox. The operation will rename
+++     // from the Interface SrcName to DstName as it moves, and reconfigure the
+++     // interface according to the specified settings. The caller is expected
+++     // to only provide a prefix for DstName. The AddInterface api will auto-generate
+++     // an appropriate suffix for the DstName to disambiguate.
+++     AddInterface(SrcName string, DstPrefix string, options ...IfaceOption) error
+++
+++     // Set default IPv4 gateway for the sandbox
+++     SetGateway(gw net.IP) error
+++
+++     // Set default IPv6 gateway for the sandbox
+++     SetGatewayIPv6(gw net.IP) error
+++
+++     // Unset the previously set default IPv4 gateway in the sandbox
+++     UnsetGateway() error
+++
+++     // Unset the previously set default IPv6 gateway in the sandbox
+++     UnsetGatewayIPv6() error
+++
+++     // Add a static route to the sandbox.
+++     AddStaticRoute(*types.StaticRoute) error
+++
+++     // Remove a static route from the sandbox.
+++     RemoveStaticRoute(*types.StaticRoute) error
+++
+++     // AddNeighbor adds a neighbor entry into the sandbox.
+++     AddNeighbor(dstIP net.IP, dstMac net.HardwareAddr, option ...NeighOption) error
+++
+++     // DeleteNeighbor deletes neighbor entry from the sandbox.
+++     DeleteNeighbor(dstIP net.IP, dstMac net.HardwareAddr) error
+++
+++     // Returns an interface with methods to set neighbor options.
+++     NeighborOptions() NeighborOptionSetter
+++
+++     // Returns an interface with methods to set interface options.
+++     InterfaceOptions() IfaceOptionSetter
+++
+++     //Invoke
+++     InvokeFunc(func()) error
+++
+++     // Returns an interface with methods to get sandbox state.
+++     Info() Info
+++
+++     // Destroy the sandbox
+++     Destroy() error
+++}
+++
+++// NeighborOptionSetter interfaces defines the option setter methods for interface options
+++type NeighborOptionSetter interface {
+++     // LinkName returns an option setter to set the srcName of the link that should
+++     // be used in the neighbor entry
+++     LinkName(string) NeighOption
+++
+++     // Family returns an option setter to set the address family for the neighbor
+++     // entry. eg. AF_BRIDGE
+++     Family(int) NeighOption
+++}
+++
+++// IfaceOptionSetter interface defines the option setter methods for interface options.
+++type IfaceOptionSetter interface {
+++     // Bridge returns an option setter to set if the interface is a bridge.
+++     Bridge(bool) IfaceOption
+++
+++     // Address returns an option setter to set IPv4 address.
+++     Address(*net.IPNet) IfaceOption
+++
+++     // Address returns an option setter to set IPv6 address.
+++     AddressIPv6(*net.IPNet) IfaceOption
+++
+++     // Master returns an option setter to set the master interface if any for this
+++     // interface. The master interface name should refer to the srcname of a
+++     // previously added interface of type bridge.
+++     Master(string) IfaceOption
+++
+++     // Address returns an option setter to set interface routes.
+++     Routes([]*net.IPNet) IfaceOption
+++}
+++
+++// Info represents all possible information that
+++// the driver wants to place in the sandbox which includes
+++// interfaces, routes and gateway
+++type Info interface {
+++     // The collection of Interface previously added with the AddInterface
+++     // method. Note that this doesn't incude network interfaces added in any
+++     // other way (such as the default loopback interface which are automatically
+++     // created on creation of a sandbox).
+++     Interfaces() []Interface
+++
+++     // IPv4 gateway for the sandbox.
+++     Gateway() net.IP
+++
+++     // IPv6 gateway for the sandbox.
+++     GatewayIPv6() net.IP
+++
+++     // Additional static routes for the sandbox.  (Note that directly
+++     // connected routes are stored on the particular interface they refer to.)
+++     StaticRoutes() []*types.StaticRoute
+++
+++     // TODO: Add ip tables etc.
+++}
+++
+++// Interface represents the settings and identity of a network device. It is
+++// used as a return type for Network.Link, and it is common practice for the
+++// caller to use this information when moving interface SrcName from host
+++// namespace to DstName in a different net namespace with the appropriate
+++// network settings.
+++type Interface interface {
+++     // The name of the interface in the origin network namespace.
+++     SrcName() string
+++
+++     // The name that will be assigned to the interface once moves inside a
+++     // network namespace. When the caller passes in a DstName, it is only
+++     // expected to pass a prefix. The name will modified with an appropriately
+++     // auto-generated suffix.
+++     DstName() string
+++
+++     // IPv4 address for the interface.
+++     Address() *net.IPNet
+++
+++     // IPv6 address for the interface.
+++     AddressIPv6() *net.IPNet
+++
+++     // IP routes for the interface.
+++     Routes() []*net.IPNet
+++
+++     // Bridge returns true if the interface is a bridge
+++     Bridge() bool
+++
+++     // Master returns the srcname of the master interface for this interface.
+++     Master() string
+++
+++     // Remove an interface from the sandbox by renaming to original name
+++     // and moving it out of the sandbox.
+++     Remove() error
+++
+++     // Statistics returns the statistics for this interface
+++     Statistics() (*InterfaceStatistics, error)
+++}
+++
+++// InterfaceStatistics represents the interface's statistics
+++type InterfaceStatistics struct {
+++     RxBytes   uint64
+++     RxPackets uint64
+++     RxErrors  uint64
+++     RxDropped uint64
+++     TxBytes   uint64
+++     TxPackets uint64
+++     TxErrors  uint64
+++     TxDropped uint64
+++}
+++
+++func (is *InterfaceStatistics) String() string {
+++     return fmt.Sprintf("\nRxBytes: %d, RxPackets: %d, RxErrors: %d, RxDropped: %d, TxBytes: %d, TxPackets: %d, TxErrors: %d, TxDropped: %d",
+++             is.RxBytes, is.RxPackets, is.RxErrors, is.RxDropped, is.TxBytes, is.TxPackets, is.TxErrors, is.TxDropped)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..52893283008a1d8a33c1251f70e5c150635994dd
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,28 @@@@
+++package sandbox
+++
+++// GenerateKey generates a sandbox key based on the passed
+++// container id.
+++func GenerateKey(containerID string) string {
+++     maxLen := 12
+++     if len(containerID) < maxLen {
+++             maxLen = len(containerID)
+++     }
+++
+++     return containerID[:maxLen]
+++}
+++
+++// NewSandbox provides a new sandbox instance created in an os specific way
+++// provided a key which uniquely identifies the sandbox
+++func NewSandbox(key string, osCreate bool) (Sandbox, error) {
+++     return nil, nil
+++}
+++
+++// GC triggers garbage collection of namespace path right away
+++// and waits for it.
+++func GC() {
+++}
+++
+++// InitOSContext initializes OS context while configuring network resources
+++func InitOSContext() func() {
+++     return func() {}
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e5ad326cf5ebe4ce3ddb469ef33d1f9557390946
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,180 @@@@
+++package sandbox
+++
+++import (
+++     "net"
+++     "os"
+++     "path/filepath"
+++     "runtime"
+++     "testing"
+++     "time"
+++
+++     "github.com/docker/libnetwork/netutils"
+++     "github.com/vishvananda/netlink"
+++     "github.com/vishvananda/netns"
+++)
+++
+++const (
+++     vethName1     = "wierdlongname1"
+++     vethName2     = "wierdlongname2"
+++     vethName3     = "wierdlongname3"
+++     vethName4     = "wierdlongname4"
+++     sboxIfaceName = "containername"
+++)
+++
+++func newKey(t *testing.T) (string, error) {
+++     name, err := netutils.GenerateRandomName("netns", 12)
+++     if err != nil {
+++             return "", err
+++     }
+++
+++     name = filepath.Join("/tmp", name)
+++     if _, err := os.Create(name); err != nil {
+++             return "", err
+++     }
+++
+++     // Set the rpmCleanupPeriod to be low to make the test run quicker
+++     gpmLock.Lock()
+++     gpmCleanupPeriod = 2 * time.Second
+++     gpmLock.Unlock()
+++
+++     return name, nil
+++}
+++
+++func newInfo(t *testing.T) (Sandbox, error) {
+++     veth := &netlink.Veth{
+++             LinkAttrs: netlink.LinkAttrs{Name: vethName1, TxQLen: 0},
+++             PeerName:  vethName2}
+++     if err := netlink.LinkAdd(veth); err != nil {
+++             return nil, err
+++     }
+++
+++     // Store the sandbox side pipe interface
+++     // This is needed for cleanup on DeleteEndpoint()
+++     intf1 := &nwIface{}
+++     intf1.srcName = vethName2
+++     intf1.dstName = sboxIfaceName
+++
+++     ip4, addr, err := net.ParseCIDR("192.168.1.100/24")
+++     if err != nil {
+++             return nil, err
+++     }
+++     intf1.address = addr
+++     intf1.address.IP = ip4
+++
+++     // ip6, addrv6, err := net.ParseCIDR("2001:DB8::ABCD/48")
+++     ip6, addrv6, err := net.ParseCIDR("fe80::2/64")
+++     if err != nil {
+++             return nil, err
+++     }
+++     intf1.addressIPv6 = addrv6
+++     intf1.addressIPv6.IP = ip6
+++
+++     _, route, err := net.ParseCIDR("192.168.2.1/32")
+++     if err != nil {
+++             return nil, err
+++     }
+++
+++     intf1.routes = []*net.IPNet{route}
+++
+++     intf2 := &nwIface{}
+++     intf2.srcName = "testbridge"
+++     intf2.dstName = sboxIfaceName
+++     intf2.bridge = true
+++
+++     veth = &netlink.Veth{
+++             LinkAttrs: netlink.LinkAttrs{Name: vethName3, TxQLen: 0},
+++             PeerName:  vethName4}
+++
+++     if err := netlink.LinkAdd(veth); err != nil {
+++             return nil, err
+++     }
+++
+++     intf3 := &nwIface{}
+++     intf3.srcName = vethName4
+++     intf3.dstName = sboxIfaceName
+++     intf3.master = "testbridge"
+++
+++     info := &networkNamespace{iFaces: []*nwIface{intf1, intf2, intf3}}
+++
+++     info.gw = net.ParseIP("192.168.1.1")
+++     // sinfo.GatewayIPv6 = net.ParseIP("2001:DB8::1")
+++     info.gwv6 = net.ParseIP("fe80::1")
+++
+++     return info, nil
+++}
+++
+++func verifySandbox(t *testing.T, s Sandbox, ifaceSuffixes []string) {
+++     _, ok := s.(*networkNamespace)
+++     if !ok {
+++             t.Fatalf("The sandox interface returned is not of type networkNamespace")
+++     }
+++
+++     origns, err := netns.Get()
+++     if err != nil {
+++             t.Fatalf("Could not get the current netns: %v", err)
+++     }
+++     defer origns.Close()
+++
+++     f, err := os.OpenFile(s.Key(), os.O_RDONLY, 0)
+++     if err != nil {
+++             t.Fatalf("Failed top open network namespace path %q: %v", s.Key(), err)
+++     }
+++     defer f.Close()
+++
+++     runtime.LockOSThread()
+++     defer runtime.UnlockOSThread()
+++
+++     nsFD := f.Fd()
+++     if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
+++             t.Fatalf("Setting to the namespace pointed to by the sandbox %s failed: %v", s.Key(), err)
+++     }
+++     defer netns.Set(origns)
+++
+++     for _, suffix := range ifaceSuffixes {
+++             _, err = netlink.LinkByName(sboxIfaceName + suffix)
+++             if err != nil {
+++                     t.Fatalf("Could not find the interface %s inside the sandbox: %v",
+++                             sboxIfaceName+suffix, err)
+++             }
+++     }
+++}
+++
+++func verifyCleanup(t *testing.T, s Sandbox, wait bool) {
+++     if wait {
+++             time.Sleep(time.Duration(gpmCleanupPeriod * 2))
+++     }
+++
+++     if _, err := os.Stat(s.Key()); err == nil {
+++             if wait {
+++                     t.Fatalf("The sandbox path %s is not getting cleaned up even after twice the cleanup period", s.Key())
+++             } else {
+++                     t.Fatalf("The sandbox path %s is not cleaned up after running gc", s.Key())
+++             }
+++     }
+++}
+++
+++func TestScanStatistics(t *testing.T) {
+++     data :=
+++             "Inter-|   Receive                                                |  Transmit\n" +
+++                     "       face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed\n" +
+++                     "  eth0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0\n" +
+++                     " wlan0: 7787685   11141    0    0    0     0          0         0  1681390    7220    0    0    0     0       0          0\n" +
+++                     "    lo:  783782    1853    0    0    0     0          0         0   783782    1853    0    0    0     0       0          0\n" +
+++                     "lxcbr0:       0       0    0    0    0     0          0         0     9006      61    0    0    0     0       0          0\n"
+++
+++     i := &InterfaceStatistics{}
+++
+++     if err := scanInterfaceStats(data, "wlan0", i); err != nil {
+++             t.Fatal(err)
+++     }
+++     if i.TxBytes != 1681390 || i.TxPackets != 7220 || i.RxBytes != 7787685 || i.RxPackets != 11141 {
+++             t.Fatalf("Error scanning the statistics")
+++     }
+++
+++     if err := scanInterfaceStats(data, "lxcbr0", i); err != nil {
+++             t.Fatal(err)
+++     }
+++     if i.TxBytes != 9006 || i.TxPackets != 61 || i.RxBytes != 0 || i.RxPackets != 0 {
+++             t.Fatalf("Error scanning the statistics")
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..10cdb97f02db53d6fb272c9c34e0a77b3aa11214
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,189 @@@@
+++package sandbox
+++
+++import (
+++     "os"
+++     "runtime"
+++     "testing"
+++
+++     "github.com/docker/docker/pkg/reexec"
+++)
+++
+++func TestMain(m *testing.M) {
+++     if reexec.Init() {
+++             return
+++     }
+++     os.Exit(m.Run())
+++}
+++
+++func TestSandboxCreate(t *testing.T) {
+++     defer SetupTestOSContext(t)()
+++
+++     key, err := newKey(t)
+++     if err != nil {
+++             t.Fatalf("Failed to obtain a key: %v", err)
+++     }
+++
+++     s, err := NewSandbox(key, true)
+++     if err != nil {
+++             t.Fatalf("Failed to create a new sandbox: %v", err)
+++     }
+++     runtime.LockOSThread()
+++
+++     if s.Key() != key {
+++             t.Fatalf("s.Key() returned %s. Expected %s", s.Key(), key)
+++     }
+++
+++     tbox, err := newInfo(t)
+++     if err != nil {
+++             t.Fatalf("Failed to generate new sandbox info: %v", err)
+++     }
+++
+++     for _, i := range tbox.Info().Interfaces() {
+++             err = s.AddInterface(i.SrcName(), i.DstName(),
+++                     tbox.InterfaceOptions().Bridge(i.Bridge()),
+++                     tbox.InterfaceOptions().Address(i.Address()),
+++                     tbox.InterfaceOptions().AddressIPv6(i.AddressIPv6()))
+++             if err != nil {
+++                     t.Fatalf("Failed to add interfaces to sandbox: %v", err)
+++             }
+++             runtime.LockOSThread()
+++     }
+++
+++     err = s.SetGateway(tbox.Info().Gateway())
+++     if err != nil {
+++             t.Fatalf("Failed to set gateway to sandbox: %v", err)
+++     }
+++     runtime.LockOSThread()
+++
+++     err = s.SetGatewayIPv6(tbox.Info().GatewayIPv6())
+++     if err != nil {
+++             t.Fatalf("Failed to set ipv6 gateway to sandbox: %v", err)
+++     }
+++     runtime.LockOSThread()
+++
+++     verifySandbox(t, s, []string{"0", "1", "2"})
+++     runtime.LockOSThread()
+++
+++     err = s.Destroy()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     verifyCleanup(t, s, true)
+++}
+++
+++func TestSandboxCreateTwice(t *testing.T) {
+++     defer SetupTestOSContext(t)()
+++
+++     key, err := newKey(t)
+++     if err != nil {
+++             t.Fatalf("Failed to obtain a key: %v", err)
+++     }
+++
+++     _, err = NewSandbox(key, true)
+++     if err != nil {
+++             t.Fatalf("Failed to create a new sandbox: %v", err)
+++     }
+++     runtime.LockOSThread()
+++
+++     // Create another sandbox with the same key to see if we handle it
+++     // gracefully.
+++     s, err := NewSandbox(key, true)
+++     if err != nil {
+++             t.Fatalf("Failed to create a new sandbox: %v", err)
+++     }
+++     runtime.LockOSThread()
+++
+++     err = s.Destroy()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++     GC()
+++     verifyCleanup(t, s, false)
+++}
+++
+++func TestSandboxGC(t *testing.T) {
+++     key, err := newKey(t)
+++     if err != nil {
+++             t.Fatalf("Failed to obtain a key: %v", err)
+++     }
+++
+++     s, err := NewSandbox(key, true)
+++     if err != nil {
+++             t.Fatalf("Failed to create a new sandbox: %v", err)
+++     }
+++
+++     err = s.Destroy()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     GC()
+++     verifyCleanup(t, s, false)
+++}
+++
+++func TestAddRemoveInterface(t *testing.T) {
+++     defer SetupTestOSContext(t)()
+++
+++     key, err := newKey(t)
+++     if err != nil {
+++             t.Fatalf("Failed to obtain a key: %v", err)
+++     }
+++
+++     s, err := NewSandbox(key, true)
+++     if err != nil {
+++             t.Fatalf("Failed to create a new sandbox: %v", err)
+++     }
+++     runtime.LockOSThread()
+++
+++     if s.Key() != key {
+++             t.Fatalf("s.Key() returned %s. Expected %s", s.Key(), key)
+++     }
+++
+++     tbox, err := newInfo(t)
+++     if err != nil {
+++             t.Fatalf("Failed to generate new sandbox info: %v", err)
+++     }
+++
+++     for _, i := range tbox.Info().Interfaces() {
+++             err = s.AddInterface(i.SrcName(), i.DstName(),
+++                     tbox.InterfaceOptions().Bridge(i.Bridge()),
+++                     tbox.InterfaceOptions().Address(i.Address()),
+++                     tbox.InterfaceOptions().AddressIPv6(i.AddressIPv6()))
+++             if err != nil {
+++                     t.Fatalf("Failed to add interfaces to sandbox: %v", err)
+++             }
+++             runtime.LockOSThread()
+++     }
+++
+++     verifySandbox(t, s, []string{"0", "1", "2"})
+++     runtime.LockOSThread()
+++
+++     interfaces := s.Info().Interfaces()
+++     if err := interfaces[0].Remove(); err != nil {
+++             t.Fatalf("Failed to remove interfaces from sandbox: %v", err)
+++     }
+++     runtime.LockOSThread()
+++
+++     verifySandbox(t, s, []string{"1", "2"})
+++     runtime.LockOSThread()
+++
+++     i := tbox.Info().Interfaces()[0]
+++     if err := s.AddInterface(i.SrcName(), i.DstName(),
+++             tbox.InterfaceOptions().Bridge(i.Bridge()),
+++             tbox.InterfaceOptions().Address(i.Address()),
+++             tbox.InterfaceOptions().AddressIPv6(i.AddressIPv6())); err != nil {
+++             t.Fatalf("Failed to add interfaces to sandbox: %v", err)
+++     }
+++     runtime.LockOSThread()
+++
+++     verifySandbox(t, s, []string{"1", "2", "3"})
+++     runtime.LockOSThread()
+++
+++     err = s.Destroy()
+++     if err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     GC()
+++     verifyCleanup(t, s, false)
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4ed3c9f58b664e9680d7529a4af5ba99d870f809
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,22 @@@@
+++// +build !linux,!windows,!freebsd
+++
+++package sandbox
+++
+++import "errors"
+++
+++var (
+++     // ErrNotImplemented is for platforms which don't implement sandbox
+++     ErrNotImplemented = errors.New("not implemented")
+++)
+++
+++// NewSandbox provides a new sandbox instance created in an os specific way
+++// provided a key which uniquely identifies the sandbox
+++func NewSandbox(key string, osCreate bool) (Sandbox, error) {
+++     return nil, ErrNotImplemented
+++}
+++
+++// GenerateKey generates a sandbox key based on the passed
+++// container id.
+++func GenerateKey(containerID string) string {
+++     return ""
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..48dc2aa72676e0b525ed547ed02f421adc533992
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,20 @@@@
+++// +build !linux
+++
+++package sandbox
+++
+++import (
+++     "errors"
+++     "testing"
+++)
+++
+++var (
+++     ErrNotImplemented = errors.New("not implemented")
+++)
+++
+++func newKey(t *testing.T) (string, error) {
+++     return nil, ErrNotImplemented
+++}
+++
+++func verifySandbox(t *testing.T, s Sandbox) {
+++     return
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..80af79f41e50fbee7fec807e6bc063dfcb0f558d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,8 @@@@
+++package sandbox
+++
+++import "testing"
+++
+++// SetupTestOSContext sets up a separate test  OS context in which tests will be executed.
+++func SetupTestOSContext(t *testing.T) func() {
+++     return func() {}
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..cfabdd9ab5d8133b52148358ba39b0166daa6983
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,37 @@@@
+++package sandbox
+++
+++import (
+++     "runtime"
+++     "syscall"
+++     "testing"
+++)
+++
+++// SetupTestOSContext joins a new network namespace, and returns its associated
+++// teardown function.
+++//
+++// Example usage:
+++//
+++//     defer SetupTestOSContext(t)()
+++//
+++func SetupTestOSContext(t *testing.T) func() {
+++     runtime.LockOSThread()
+++     if err := syscall.Unshare(syscall.CLONE_NEWNET); err != nil {
+++             t.Fatalf("Failed to enter netns: %v", err)
+++     }
+++
+++     fd, err := syscall.Open("/proc/self/ns/net", syscall.O_RDONLY, 0)
+++     if err != nil {
+++             t.Fatal("Failed to open netns file")
+++     }
+++
+++     // Since we are switching to a new test namespace make
+++     // sure to re-initialize initNs context
+++     nsInit()
+++
+++     return func() {
+++             if err := syscall.Close(fd); err != nil {
+++                     t.Logf("Warning: netns closing failed (%v)", err)
+++             }
+++             runtime.UnlockOSThread()
+++     }
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..80af79f41e50fbee7fec807e6bc063dfcb0f558d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,8 @@@@
+++package sandbox
+++
+++import "testing"
+++
+++// SetupTestOSContext sets up a separate test  OS context in which tests will be executed.
+++func SetupTestOSContext(t *testing.T) func() {
+++     return func() {}
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9b0d8ea1bfed80920925252e4c5c0bd79e732c8a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,259 @@@@
+++package libnetwork
+++
+++import (
+++     "container/heap"
+++     "fmt"
+++     "sync"
+++
+++     "github.com/Sirupsen/logrus"
+++     "github.com/docker/libnetwork/sandbox"
+++)
+++
+++type epHeap []*endpoint
+++
+++type sandboxData struct {
+++     sbox      sandbox.Sandbox
+++     refCnt    int
+++     endpoints epHeap
+++     sync.Mutex
+++}
+++
+++func (eh epHeap) Len() int { return len(eh) }
+++
+++func (eh epHeap) Less(i, j int) bool {
+++     eh[i].Lock()
+++     eh[j].Lock()
+++     defer eh[j].Unlock()
+++     defer eh[i].Unlock()
+++
+++     if eh[i].container.config.prio == eh[j].container.config.prio {
+++             return eh[i].network.Name() < eh[j].network.Name()
+++     }
+++
+++     return eh[i].container.config.prio > eh[j].container.config.prio
+++}
+++
+++func (eh epHeap) Swap(i, j int) { eh[i], eh[j] = eh[j], eh[i] }
+++
+++func (eh *epHeap) Push(x interface{}) {
+++     *eh = append(*eh, x.(*endpoint))
+++}
+++
+++func (eh *epHeap) Pop() interface{} {
+++     old := *eh
+++     n := len(old)
+++     x := old[n-1]
+++     *eh = old[0 : n-1]
+++     return x
+++}
+++
+++func (s *sandboxData) updateGateway(ep *endpoint) error {
+++     sb := s.sandbox()
+++
+++     sb.UnsetGateway()
+++     sb.UnsetGatewayIPv6()
+++
+++     if ep == nil {
+++             return nil
+++     }
+++
+++     ep.Lock()
+++     joinInfo := ep.joinInfo
+++     ep.Unlock()
+++
+++     if err := sb.SetGateway(joinInfo.gw); err != nil {
+++             return fmt.Errorf("failed to set gateway while updating gateway: %v", err)
+++     }
+++
+++     if err := sb.SetGatewayIPv6(joinInfo.gw6); err != nil {
+++             return fmt.Errorf("failed to set IPv6 gateway while updating gateway: %v", err)
+++     }
+++
+++     return nil
+++}
+++
+++func (s *sandboxData) addEndpoint(ep *endpoint) error {
+++     ep.Lock()
+++     joinInfo := ep.joinInfo
+++     ifaces := ep.iFaces
+++     ep.Unlock()
+++
+++     sb := s.sandbox()
+++     for _, i := range ifaces {
+++             var ifaceOptions []sandbox.IfaceOption
+++
+++             ifaceOptions = append(ifaceOptions, sb.InterfaceOptions().Address(&i.addr),
+++                     sb.InterfaceOptions().Routes(i.routes))
+++             if i.addrv6.IP.To16() != nil {
+++                     ifaceOptions = append(ifaceOptions,
+++                             sb.InterfaceOptions().AddressIPv6(&i.addrv6))
+++             }
+++
+++             if err := sb.AddInterface(i.srcName, i.dstPrefix, ifaceOptions...); err != nil {
+++                     return fmt.Errorf("failed to add interface %s to sandbox: %v", i.srcName, err)
+++             }
+++     }
+++
+++     if joinInfo != nil {
+++             // Set up non-interface routes.
+++             for _, r := range ep.joinInfo.StaticRoutes {
+++                     if err := sb.AddStaticRoute(r); err != nil {
+++                             return fmt.Errorf("failed to add static route %s: %v", r.Destination.String(), err)
+++                     }
+++             }
+++     }
+++
+++     s.Lock()
+++     heap.Push(&s.endpoints, ep)
+++     highEp := s.endpoints[0]
+++     s.Unlock()
+++
+++     if ep == highEp {
+++             if err := s.updateGateway(ep); err != nil {
+++                     return err
+++             }
+++     }
+++
+++     return nil
+++}
+++
+++func (s *sandboxData) rmEndpoint(ep *endpoint) {
+++     ep.Lock()
+++     joinInfo := ep.joinInfo
+++     ep.Unlock()
+++
+++     sb := s.sandbox()
+++     for _, i := range sb.Info().Interfaces() {
+++             // Only remove the interfaces owned by this endpoint from the sandbox.
+++             if ep.hasInterface(i.SrcName()) {
+++                     if err := i.Remove(); err != nil {
+++                             logrus.Debugf("Remove interface failed: %v", err)
+++                     }
+++             }
+++     }
+++
+++     // Remove non-interface routes.
+++     for _, r := range joinInfo.StaticRoutes {
+++             if err := sb.RemoveStaticRoute(r); err != nil {
+++                     logrus.Debugf("Remove route failed: %v", err)
+++             }
+++     }
+++
+++     s.Lock()
+++     if len(s.endpoints) == 0 {
+++             // s.endpoints should never be empty and this is unexpected error condition
+++             // We log an error message to note this down for debugging purposes.
+++             logrus.Errorf("No endpoints in sandbox while trying to remove endpoint %s", ep.Name())
+++             s.Unlock()
+++             return
+++     }
+++
+++     highEpBefore := s.endpoints[0]
+++     var (
+++             i int
+++             e *endpoint
+++     )
+++     for i, e = range s.endpoints {
+++             if e == ep {
+++                     break
+++             }
+++     }
+++     heap.Remove(&s.endpoints, i)
+++     var highEpAfter *endpoint
+++     if len(s.endpoints) > 0 {
+++             highEpAfter = s.endpoints[0]
+++     }
+++
+++     s.Unlock()
+++
+++     if highEpBefore != highEpAfter {
+++             s.updateGateway(highEpAfter)
+++     }
+++}
+++
+++func (s *sandboxData) sandbox() sandbox.Sandbox {
+++     s.Lock()
+++     defer s.Unlock()
+++
+++     return s.sbox
+++}
+++
+++func (c *controller) sandboxAdd(key string, create bool, ep *endpoint) (sandbox.Sandbox, error) {
+++     c.Lock()
+++     sData, ok := c.sandboxes[key]
+++     c.Unlock()
+++
+++     if !ok {
+++             sb, err := sandbox.NewSandbox(key, create)
+++             if err != nil {
+++                     return nil, fmt.Errorf("failed to create new sandbox: %v", err)
+++             }
+++
+++             sData = &sandboxData{
+++                     sbox:      sb,
+++                     endpoints: epHeap{},
+++             }
+++
+++             heap.Init(&sData.endpoints)
+++             c.Lock()
+++             c.sandboxes[key] = sData
+++             c.Unlock()
+++     }
+++
+++     if err := sData.addEndpoint(ep); err != nil {
+++             return nil, err
+++     }
+++
+++     return sData.sandbox(), nil
+++}
+++
+++func (c *controller) sandboxRm(key string, ep *endpoint) {
+++     c.Lock()
+++     sData := c.sandboxes[key]
+++     c.Unlock()
+++
+++     sData.rmEndpoint(ep)
+++}
+++
+++func (c *controller) sandboxGet(key string) sandbox.Sandbox {
+++     c.Lock()
+++     sData, ok := c.sandboxes[key]
+++     c.Unlock()
+++
+++     if !ok {
+++             return nil
+++     }
+++
+++     return sData.sandbox()
+++}
+++
+++func (c *controller) LeaveAll(id string) error {
+++     c.Lock()
+++     sData, ok := c.sandboxes[sandbox.GenerateKey(id)]
+++     c.Unlock()
+++
+++     if !ok {
+++             return fmt.Errorf("could not find sandbox for container id %s", id)
+++     }
+++
+++     sData.Lock()
+++     eps := make([]*endpoint, len(sData.endpoints))
+++     for i, ep := range sData.endpoints {
+++             eps[i] = ep
+++     }
+++     sData.Unlock()
+++
+++     for _, ep := range eps {
+++             if err := ep.Leave(id); err != nil {
+++                     logrus.Warnf("Failed leaving endpoint id %s: %v\n", ep.ID(), err)
+++             }
+++     }
+++
+++     sData.sandbox().Destroy()
+++
+++     c.Lock()
+++     delete(c.sandboxes, sandbox.GenerateKey(id))
+++     c.Unlock()
+++
+++     return nil
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8084320967c11e6bc5efa3bcb65ece3c0cda70df
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,141 @@@@
+++package libnetwork
+++
+++import (
+++     "testing"
+++
+++     "github.com/docker/libnetwork/sandbox"
+++)
+++
+++func createEmptyCtrlr() *controller {
+++     return &controller{sandboxes: sandboxTable{}}
+++}
+++
+++func createEmptyEndpoint() *endpoint {
+++     return &endpoint{
+++             container: &containerInfo{},
+++             joinInfo:  &endpointJoinInfo{},
+++             iFaces:    []*endpointInterface{},
+++     }
+++}
+++
+++func TestSandboxAddEmpty(t *testing.T) {
+++     ctrlr := createEmptyCtrlr()
+++     ep := createEmptyEndpoint()
+++
+++     if _, err := ctrlr.sandboxAdd(sandbox.GenerateKey("sandbox1"), true, ep); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     ctrlr.sandboxRm(sandbox.GenerateKey("sandbox1"), ep)
+++
+++     ctrlr.LeaveAll("sandbox1")
+++     if len(ctrlr.sandboxes) != 0 {
+++             t.Fatalf("controller sandboxes is not empty. len = %d", len(ctrlr.sandboxes))
+++     }
+++
+++     sandbox.GC()
+++}
+++
+++func TestSandboxAddMultiPrio(t *testing.T) {
+++     ctrlr := createEmptyCtrlr()
+++     ep1 := createEmptyEndpoint()
+++     ep2 := createEmptyEndpoint()
+++     ep3 := createEmptyEndpoint()
+++
+++     ep1.container.config.prio = 1
+++     ep2.container.config.prio = 2
+++     ep3.container.config.prio = 3
+++
+++     sKey := sandbox.GenerateKey("sandbox1")
+++
+++     if _, err := ctrlr.sandboxAdd(sKey, true, ep1); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if _, err := ctrlr.sandboxAdd(sKey, true, ep2); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if _, err := ctrlr.sandboxAdd(sKey, true, ep3); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if ctrlr.sandboxes[sKey].endpoints[0] != ep3 {
+++             t.Fatal("Expected ep3 to be at the top of the heap. But did not find ep3 at the top of the heap")
+++     }
+++
+++     ctrlr.sandboxRm(sKey, ep3)
+++
+++     if ctrlr.sandboxes[sKey].endpoints[0] != ep2 {
+++             t.Fatal("Expected ep2 to be at the top of the heap after removing ep3. But did not find ep2 at the top of the heap")
+++     }
+++
+++     ctrlr.sandboxRm(sKey, ep2)
+++
+++     if ctrlr.sandboxes[sKey].endpoints[0] != ep1 {
+++             t.Fatal("Expected ep1 to be at the top of the heap after removing ep2. But did not find ep1 at the top of the heap")
+++     }
+++
+++     // Re-add ep3 back
+++     if _, err := ctrlr.sandboxAdd(sKey, true, ep3); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if ctrlr.sandboxes[sKey].endpoints[0] != ep3 {
+++             t.Fatal("Expected ep3 to be at the top of the heap after adding ep3 back. But did not find ep3 at the top of the heap")
+++     }
+++
+++     ctrlr.sandboxRm(sKey, ep3)
+++     ctrlr.sandboxRm(sKey, ep1)
+++
+++     if err := ctrlr.LeaveAll("sandbox1"); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if len(ctrlr.sandboxes) != 0 {
+++             t.Fatalf("controller sandboxes is not empty. len = %d", len(ctrlr.sandboxes))
+++     }
+++
+++     sandbox.GC()
+++}
+++
+++func TestSandboxAddSamePrio(t *testing.T) {
+++     ctrlr := createEmptyCtrlr()
+++     ep1 := createEmptyEndpoint()
+++     ep2 := createEmptyEndpoint()
+++
+++     ep1.network = &network{name: "aaa"}
+++     ep2.network = &network{name: "bbb"}
+++
+++     sKey := sandbox.GenerateKey("sandbox1")
+++
+++     if _, err := ctrlr.sandboxAdd(sKey, true, ep1); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if _, err := ctrlr.sandboxAdd(sKey, true, ep2); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if ctrlr.sandboxes[sKey].endpoints[0] != ep1 {
+++             t.Fatal("Expected ep1 to be at the top of the heap. But did not find ep1 at the top of the heap")
+++     }
+++
+++     ctrlr.sandboxRm(sKey, ep1)
+++
+++     if ctrlr.sandboxes[sKey].endpoints[0] != ep2 {
+++             t.Fatal("Expected ep2 to be at the top of the heap after removing ep3. But did not find ep2 at the top of the heap")
+++     }
+++
+++     ctrlr.sandboxRm(sKey, ep2)
+++
+++     if err := ctrlr.LeaveAll("sandbox1"); err != nil {
+++             t.Fatal(err)
+++     }
+++
+++     if len(ctrlr.sandboxes) != 0 {
+++             t.Fatalf("controller sandboxes is not empty. len = %d", len(ctrlr.sandboxes))
+++     }
+++
+++     sandbox.GC()
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a832adf01b700d31bdc54e4ccab206a79cba357e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,371 @@@@
+++package libnetwork
+++
+++import (
+++     "encoding/json"
+++     "fmt"
+++
+++     log "github.com/Sirupsen/logrus"
+++     "github.com/docker/libkv/store"
+++     "github.com/docker/libnetwork/datastore"
+++     "github.com/docker/libnetwork/types"
+++)
+++
+++func (c *controller) validateDatastoreConfig() bool {
+++     return c.cfg != nil && c.cfg.Datastore.Client.Provider != "" && c.cfg.Datastore.Client.Address != ""
+++}
+++
+++func (c *controller) initDataStore() error {
+++     c.Lock()
+++     cfg := c.cfg
+++     c.Unlock()
+++     if !c.validateDatastoreConfig() {
+++             return fmt.Errorf("datastore initialization requires a valid configuration")
+++     }
+++
+++     store, err := datastore.NewDataStore(&cfg.Datastore)
+++     if err != nil {
+++             return err
+++     }
+++     c.Lock()
+++     c.store = store
+++     c.Unlock()
+++
+++     nws, err := c.getNetworksFromStore()
+++     if err == nil {
+++             c.processNetworkUpdate(nws, nil)
+++     } else if err != datastore.ErrKeyNotFound {
+++             log.Warnf("failed to read networks from datastore during init : %v", err)
+++     }
+++     return c.watchNetworks()
+++}
+++
+++func (c *controller) getNetworksFromStore() ([]*store.KVPair, error) {
+++     c.Lock()
+++     cs := c.store
+++     c.Unlock()
+++     return cs.KVStore().List(datastore.Key(datastore.NetworkKeyPrefix))
+++}
+++
+++func (c *controller) newNetworkFromStore(n *network) error {
+++     n.Lock()
+++     n.ctrlr = c
+++     n.endpoints = endpointTable{}
+++     n.Unlock()
+++
+++     return c.addNetwork(n)
+++}
+++
+++func (c *controller) updateNetworkToStore(n *network) error {
+++     global, err := n.isGlobalScoped()
+++     if err != nil || !global {
+++             return err
+++     }
+++     c.Lock()
+++     cs := c.store
+++     c.Unlock()
+++     if cs == nil {
+++             log.Debugf("datastore not initialized. Network %s is not added to the store", n.Name())
+++             return nil
+++     }
+++
+++     return cs.PutObjectAtomic(n)
+++}
+++
+++func (c *controller) deleteNetworkFromStore(n *network) error {
+++     global, err := n.isGlobalScoped()
+++     if err != nil || !global {
+++             return err
+++     }
+++     c.Lock()
+++     cs := c.store
+++     c.Unlock()
+++     if cs == nil {
+++             log.Debugf("datastore not initialized. Network %s is not deleted from datastore", n.Name())
+++             return nil
+++     }
+++
+++     if err := cs.DeleteObjectAtomic(n); err != nil {
+++             return err
+++     }
+++
+++     return nil
+++}
+++
+++func (c *controller) getNetworkFromStore(nid types.UUID) (*network, error) {
+++     n := network{id: nid}
+++     if err := c.store.GetObject(datastore.Key(n.Key()...), &n); err != nil {
+++             return nil, err
+++     }
+++     return &n, nil
+++}
+++
+++func (c *controller) newEndpointFromStore(key string, ep *endpoint) error {
+++     ep.Lock()
+++     n := ep.network
+++     id := ep.id
+++     ep.Unlock()
+++
+++     _, err := n.EndpointByID(string(id))
+++     if err != nil {
+++             if _, ok := err.(ErrNoSuchEndpoint); ok {
+++                     return n.addEndpoint(ep)
+++             }
+++     }
+++     return err
+++}
+++
+++func (c *controller) updateEndpointToStore(ep *endpoint) error {
+++     ep.Lock()
+++     n := ep.network
+++     name := ep.name
+++     ep.Unlock()
+++     global, err := n.isGlobalScoped()
+++     if err != nil || !global {
+++             return err
+++     }
+++     c.Lock()
+++     cs := c.store
+++     c.Unlock()
+++     if cs == nil {
+++             log.Debugf("datastore not initialized. endpoint %s is not added to the store", name)
+++             return nil
+++     }
+++
+++     return cs.PutObjectAtomic(ep)
+++}
+++
+++func (c *controller) getEndpointFromStore(eid types.UUID) (*endpoint, error) {
+++     ep := endpoint{id: eid}
+++     if err := c.store.GetObject(datastore.Key(ep.Key()...), &ep); err != nil {
+++             return nil, err
+++     }
+++     return &ep, nil
+++}
+++
+++func (c *controller) deleteEndpointFromStore(ep *endpoint) error {
+++     ep.Lock()
+++     n := ep.network
+++     ep.Unlock()
+++     global, err := n.isGlobalScoped()
+++     if err != nil || !global {
+++             return err
+++     }
+++
+++     c.Lock()
+++     cs := c.store
+++     c.Unlock()
+++     if cs == nil {
+++             log.Debugf("datastore not initialized. endpoint %s is not deleted from datastore", ep.Name())
+++             return nil
+++     }
+++
+++     if err := cs.DeleteObjectAtomic(ep); err != nil {
+++             return err
+++     }
+++
+++     return nil
+++}
+++
+++func (c *controller) watchNetworks() error {
+++     if !c.validateDatastoreConfig() {
+++             return nil
+++     }
+++
+++     c.Lock()
+++     cs := c.store
+++     c.Unlock()
+++
+++     nwPairs, err := cs.KVStore().WatchTree(datastore.Key(datastore.NetworkKeyPrefix), nil)
+++     if err != nil {
+++             return err
+++     }
+++     go func() {
+++             for {
+++                     select {
+++                     case nws := <-nwPairs:
+++                             c.Lock()
+++                             tmpview := networkTable{}
+++                             lview := c.networks
+++                             c.Unlock()
+++                             for k, v := range lview {
+++                                     global, _ := v.isGlobalScoped()
+++                                     if global {
+++                                             tmpview[k] = v
+++                                     }
+++                             }
+++                             c.processNetworkUpdate(nws, &tmpview)
+++
+++                             // Delete processing
+++                             for k := range tmpview {
+++                                     c.Lock()
+++                                     existing, ok := c.networks[k]
+++                                     c.Unlock()
+++                                     if !ok {
+++                                             continue
+++                                     }
+++                                     tmp := network{}
+++                                     if err := c.store.GetObject(datastore.Key(existing.Key()...), &tmp); err != datastore.ErrKeyNotFound {
+++                                             continue
+++                                     }
+++                                     if err := existing.deleteNetwork(); err != nil {
+++                                             log.Debugf("Delete failed %s: %s", existing.name, err)
+++                                     }
+++                             }
+++                     }
+++             }
+++     }()
+++     return nil
+++}
+++
+++func (n *network) watchEndpoints() error {
+++     if !n.ctrlr.validateDatastoreConfig() {
+++             return nil
+++     }
+++
+++     n.Lock()
+++     cs := n.ctrlr.store
+++     tmp := endpoint{network: n}
+++     n.stopWatchCh = make(chan struct{})
+++     stopCh := n.stopWatchCh
+++     n.Unlock()
+++
+++     epPairs, err := cs.KVStore().WatchTree(datastore.Key(tmp.KeyPrefix()...), stopCh)
+++     if err != nil {
+++             return err
+++     }
+++     go func() {
+++             for {
+++                     select {
+++                     case <-stopCh:
+++                             return
+++                     case eps := <-epPairs:
+++                             n.Lock()
+++                             tmpview := endpointTable{}
+++                             lview := n.endpoints
+++                             n.Unlock()
+++                             for k, v := range lview {
+++                                     global, _ := v.network.isGlobalScoped()
+++                                     if global {
+++                                             tmpview[k] = v
+++                                     }
+++                             }
+++                             for _, epe := range eps {
+++                                     var ep endpoint
+++                                     err := json.Unmarshal(epe.Value, &ep)
+++                                     if err != nil {
+++                                             log.Error(err)
+++                                             continue
+++                                     }
+++                                     delete(tmpview, ep.id)
+++                                     ep.SetIndex(epe.LastIndex)
+++                                     ep.network = n
+++                                     if n.ctrlr.processEndpointUpdate(&ep) {
+++                                             err = n.ctrlr.newEndpointFromStore(epe.Key, &ep)
+++                                             if err != nil {
+++                                                     log.Error(err)
+++                                             }
+++                                     }
+++                             }
+++                             // Delete processing
+++                             for k := range tmpview {
+++                                     n.Lock()
+++                                     existing, ok := n.endpoints[k]
+++                                     n.Unlock()
+++                                     if !ok {
+++                                             continue
+++                                     }
+++                                     tmp := endpoint{}
+++                                     if err := cs.GetObject(datastore.Key(existing.Key()...), &tmp); err != datastore.ErrKeyNotFound {
+++                                             continue
+++                                     }
+++                                     if err := existing.deleteEndpoint(); err != nil {
+++                                             log.Debugf("Delete failed %s: %s", existing.name, err)
+++                                     }
+++                             }
+++                     }
+++             }
+++     }()
+++     return nil
+++}
+++
+++func (n *network) stopWatch() {
+++     n.Lock()
+++     if n.stopWatchCh != nil {
+++             close(n.stopWatchCh)
+++             n.stopWatchCh = nil
+++     }
+++     n.Unlock()
+++}
+++
+++func (c *controller) processNetworkUpdate(nws []*store.KVPair, prune *networkTable) {
+++     for _, kve := range nws {
+++             var n network
+++             err := json.Unmarshal(kve.Value, &n)
+++             if err != nil {
+++                     log.Error(err)
+++                     continue
+++             }
+++             if prune != nil {
+++                     delete(*prune, n.id)
+++             }
+++             n.SetIndex(kve.LastIndex)
+++             c.Lock()
+++             existing, ok := c.networks[n.id]
+++             c.Unlock()
+++             if ok {
+++                     existing.Lock()
+++                     // Skip existing network update
+++                     if existing.dbIndex != n.Index() {
+++                             // Can't use SetIndex() since existing is locked.
+++                             existing.dbIndex = n.Index()
+++                             existing.dbExists = true
+++                             existing.endpointCnt = n.endpointCnt
+++                     }
+++                     existing.Unlock()
+++                     continue
+++             }
+++
+++             if err = c.newNetworkFromStore(&n); err != nil {
+++                     log.Error(err)
+++             }
+++     }
+++}
+++
+++func (c *controller) processEndpointUpdate(ep *endpoint) bool {
+++     nw := ep.network
+++     if nw == nil {
+++             return true
+++     }
+++     nw.Lock()
+++     id := nw.id
+++     nw.Unlock()
+++
+++     c.Lock()
+++     n, ok := c.networks[id]
+++     c.Unlock()
+++     if !ok {
+++             return true
+++     }
+++     existing, _ := n.EndpointByID(string(ep.id))
+++     if existing == nil {
+++             return true
+++     }
+++
+++     ee := existing.(*endpoint)
+++     ee.Lock()
+++     if ee.dbIndex != ep.Index() {
+++             // Can't use SetIndex() because ee is locked.
+++             ee.dbIndex = ep.Index()
+++             ee.dbExists = true
+++             if ee.container != nil && ep.container != nil {
+++                     // we care only about the container id
+++                     ee.container.id = ep.container.id
+++             } else {
+++                     // we still care only about the container id, but this is a short-cut to communicate join or leave operation
+++                     ee.container = ep.container
+++             }
+++     }
+++     ee.Unlock()
+++
+++     return false
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..777b1cfa46523eee671c8a28edb960cbc2771984
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,34 @@@@
+++# LibNetwork Integration Tests
+++
+++Integration tests provide end-to-end testing of LibNetwork and Drivers.
+++
+++While unit tests verify the code is working as expected by relying on mocks and
+++artificially created fixtures, integration tests actually use real docker
+++engines and communicate to it through the CLI.
+++
+++Note that integration tests do **not** replace unit tests and Docker is used as a good use-case.
+++
+++As a rule of thumb, code should be tested thoroughly with unit tests.
+++Integration tests on the other hand are meant to test a specific feature end to end.
+++
+++Integration tests are written in *bash* using the
+++[bats](https://github.com/sstephenson/bats) framework.
+++
+++## Pre-Requisites
+++
+++1. Bats (https://github.com/sstephenson/bats#installing-bats-from-source)
+++2. Docker Machine (https://github.com/docker/machine)
+++3. Virtualbox (as a Docker machine driver)
+++
+++## Running integration tests
+++
+++* Start by [installing] (https://github.com/sstephenson/bats#installing-bats-from-source) *bats* on your system.
+++* If not done already, [install](https://docs.docker.com/machine/) *docker-machine* into /usr/bin
+++* Make sure Virtualbox is installed as well, which will be used by docker-machine as a driver to launch VMs
+++
+++In order to run all integration tests, pass *bats* the test path:
+++```
+++$ bats test/integration/daemon-configs.bats
+++```
+++
+++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..fd48fbe199e7ee2c1ef41322baea4aacd449320a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,104 @@@@
+++#!/usr/bin/env bats
+++
+++load helpers
+++
+++export DRIVER=virtualbox
+++export NAME="bats-$DRIVER-daemon-configs"
+++export MACHINE_STORAGE_PATH=/tmp/machine-bats-daemon-test-$DRIVER
+++# Default memsize is 1024MB and disksize is 20000MB
+++# These values are defined in drivers/virtualbox/virtualbox.go
+++export DEFAULT_MEMSIZE=1024
+++export DEFAULT_DISKSIZE=20000
+++export CUSTOM_MEMSIZE=1536
+++export CUSTOM_DISKSIZE=10000
+++export CUSTOM_CPUCOUNT=1
+++export BAD_URL="http://dev.null:9111/bad.iso"
+++
+++function setup() {
+++  # add sleep because vbox; ugh
+++  sleep 1
+++}
+++
+++findDiskSize() {
+++  # SATA-0-0 is usually the boot2disk.iso image
+++  # We assume that SATA 1-0 is root disk VMDK and grab this UUID
+++  # e.g. "SATA-ImageUUID-1-0"="fb5f33a7-e4e3-4cb9-877c-f9415ae2adea"
+++  # TODO(slashk): does this work on Windows ?
+++  run bash -c "VBoxManage showvminfo --machinereadable $NAME | grep SATA-ImageUUID-1-0 | cut -d'=' -f2"
+++  run bash -c "VBoxManage showhdinfo $output | grep "Capacity:" | awk -F' ' '{ print $2 }'"
+++}
+++
+++findMemorySize() {
+++  run bash -c "VBoxManage showvminfo --machinereadable $NAME | grep memory= | cut -d'=' -f2"
+++}
+++
+++findCPUCount() {
+++  run bash -c "VBoxManage showvminfo --machinereadable $NAME | grep cpus= | cut -d'=' -f2"
+++}
+++
+++buildMachineWithOldIsoCheckUpgrade() {
+++  run wget https://github.com/boot2docker/boot2docker/releases/download/v1.4.1/boot2docker.iso -O $MACHINE_STORAGE_PATH/cache/boot2docker.iso
+++  run machine create -d virtualbox $NAME
+++  run machine upgrade $NAME
+++}
+++
+++@test "$DRIVER: machine should not exist" {
+++  run machine active $NAME
+++  [ "$status" -eq 1  ]
+++}
+++
+++@test "$DRIVER: VM should not exist" {
+++  run VBoxManage showvminfo $NAME
+++  [ "$status" -eq 1  ]
+++}
+++
+++@test "$DRIVER: create" {
+++  run machine create -d $DRIVER $NAME
+++  [ "$status" -eq 0  ]
+++}
+++
+++@test "$DRIVER: active" {
+++  run machine active $NAME
+++  [ "$status" -eq 0  ]
+++}
+++
+++@test "$DRIVER: check default machine memory size" {
+++  findMemorySize
+++  [[ ${output} == "${DEFAULT_MEMSIZE}"  ]]
+++}
+++
+++@test "$DRIVER: check default machine disksize" {
+++  findDiskSize
+++  [[ ${output} == *"$DEFAULT_DISKSIZE"* ]]
+++}
+++
+++@test "$DRIVER: test bridge-ip" {
+++  run machine ssh $NAME sudo /etc/init.d/docker stop
+++  run machine ssh $NAME sudo ifconfig docker0 down
+++  run machine ssh $NAME sudo ip link delete docker0
+++  BIP='--bip=172.168.45.1/24'
+++  set_extra_config $BIP
+++  cat ${TMP_EXTRA_ARGS_FILE} | machine ssh $NAME sudo tee /var/lib/boot2docker/profile
+++  cat ${DAEMON_CFG_FILE} | machine ssh $NAME "sudo tee -a /var/lib/boot2docker/profile"
+++  run machine ssh $NAME sudo /etc/init.d/docker start
+++  run machine ssh $NAME ifconfig docker0
+++  [ "$status" -eq 0  ]
+++  [[ ${lines[1]} =~ "172.168.45.1"  ]]
+++}
+++
+++@test "$DRIVER: run busybox container" {
+++  run machine ssh $NAME sudo cat /var/lib/boot2docker/profile
+++  run docker $(machine config $NAME) run busybox echo hello world
+++  [ "$status" -eq 0  ]
+++}
+++
+++@test "$DRIVER: remove machine" {
+++  run machine rm -f $NAME
+++}
+++
+++# Cleanup of machine store should always be the last 'test'
+++@test "$DRIVER: cleanup" {
+++  run rm -rf $MACHINE_STORAGE_PATH
+++  [ "$status" -eq 0  ]
+++}
+++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..fc93dbd6041be3b62443517ab88a8363bc8c38b5
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,4 @@@@
+++CACERT=/var/lib/boot2docker/ca.pem
+++SERVERCERT=/var/lib/boot2docker/server-key.pem
+++SERVERKEY=/var/lib/boot2docker/server.pem
+++DOCKER_TLS=no
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ec18e5d4ebbab82508098969579937f16d3a9518
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,50 @@@@
+++#!/bin/bash
+++
+++# Root directory of the repository.
+++MACHINE_ROOT=/usr/bin
+++
+++PLATFORM=`uname -s | tr '[:upper:]' '[:lower:]'`
+++ARCH=`uname -m`
+++
+++if [ "$ARCH" = "x86_64" ]; then
+++    ARCH="amd64"
+++else
+++    ARCH="386"
+++fi
+++MACHINE_BIN_NAME=docker-machine_$PLATFORM-$ARCH
+++BATS_LOG=/tmp/bats.log
+++
+++touch ${BATS_LOG}
+++rm ${BATS_LOG}
+++
+++teardown() {
+++  echo "$BATS_TEST_NAME
+++----------
+++$output
+++----------
+++
+++" >> ${BATS_LOG}
+++}
+++
+++EXTRA_ARGS_CFG='EXTRA_ARGS'
+++EXTRA_ARGS='--tlsverify --tlscacert=/var/lib/boot2docker/ca.pem --tlskey=/var/lib/boot2docker/server-key.pem --tlscert=/var/lib/boot2docker/server.pem --label=provider=virtualbox -H tcp://0.0.0.0:2376'
+++TMP_EXTRA_ARGS_FILE=/tmp/tmp_extra_args
+++DAEMON_CFG_FILE=${BATS_TEST_DIRNAME}/daemon.cfg
+++set_extra_config() {
+++  if [ -f ${TMP_EXTRA_ARGS_FILE} ];
+++  then
+++    rm ${TMP_EXTRA_ARGS_FILE}
+++  fi
+++  echo -n "${EXTRA_ARGS_CFG}='"  > ${TMP_EXTRA_ARGS_FILE}
+++  echo -n "$1 "  >> ${TMP_EXTRA_ARGS_FILE}
+++  echo "${EXTRA_ARGS}'"  >> ${TMP_EXTRA_ARGS_FILE}
+++}
+++
+++if [ ! -e $MACHINE_ROOT/$MACHINE_BIN_NAME ]; then
+++  echo "${MACHINE_ROOT}/${MACHINE_BIN_NAME} not found"
+++  exit 1
+++fi
+++
+++function machine() {
+++    ${MACHINE_ROOT}/$MACHINE_BIN_NAME "$@"
+++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..fa13bdeacc30cc09582c44fd47fa8ea2eef84788
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,407 @@@@
+++// Package types contains types that are common across libnetwork project
+++package types
+++
+++import (
+++     "bytes"
+++     "fmt"
+++     "net"
+++     "strings"
+++)
+++
+++// UUID represents a globally unique ID of various resources like network and endpoint
+++type UUID string
+++
+++// TransportPort represent a local Layer 4 endpoint
+++type TransportPort struct {
+++     Proto Protocol
+++     Port  uint16
+++}
+++
+++// GetCopy returns a copy of this TransportPort structure instance
+++func (t *TransportPort) GetCopy() TransportPort {
+++     return TransportPort{Proto: t.Proto, Port: t.Port}
+++}
+++
+++// PortBinding represent a port binding between the container and the host
+++type PortBinding struct {
+++     Proto    Protocol
+++     IP       net.IP
+++     Port     uint16
+++     HostIP   net.IP
+++     HostPort uint16
+++}
+++
+++// HostAddr returns the host side transport address
+++func (p PortBinding) HostAddr() (net.Addr, error) {
+++     switch p.Proto {
+++     case UDP:
+++             return &net.UDPAddr{IP: p.HostIP, Port: int(p.HostPort)}, nil
+++     case TCP:
+++             return &net.TCPAddr{IP: p.HostIP, Port: int(p.HostPort)}, nil
+++     default:
+++             return nil, ErrInvalidProtocolBinding(p.Proto.String())
+++     }
+++}
+++
+++// ContainerAddr returns the container side transport address
+++func (p PortBinding) ContainerAddr() (net.Addr, error) {
+++     switch p.Proto {
+++     case UDP:
+++             return &net.UDPAddr{IP: p.IP, Port: int(p.Port)}, nil
+++     case TCP:
+++             return &net.TCPAddr{IP: p.IP, Port: int(p.Port)}, nil
+++     default:
+++             return nil, ErrInvalidProtocolBinding(p.Proto.String())
+++     }
+++}
+++
+++// GetCopy returns a copy of this PortBinding structure instance
+++func (p *PortBinding) GetCopy() PortBinding {
+++     return PortBinding{
+++             Proto:    p.Proto,
+++             IP:       GetIPCopy(p.IP),
+++             Port:     p.Port,
+++             HostIP:   GetIPCopy(p.HostIP),
+++             HostPort: p.HostPort,
+++     }
+++}
+++
+++// Equal checks if this instance of PortBinding is equal to the passed one
+++func (p *PortBinding) Equal(o *PortBinding) bool {
+++     if p == o {
+++             return true
+++     }
+++
+++     if o == nil {
+++             return false
+++     }
+++
+++     if p.Proto != o.Proto || p.Port != o.Port || p.HostPort != o.HostPort {
+++             return false
+++     }
+++
+++     if p.IP != nil {
+++             if !p.IP.Equal(o.IP) {
+++                     return false
+++             }
+++     } else {
+++             if o.IP != nil {
+++                     return false
+++             }
+++     }
+++
+++     if p.HostIP != nil {
+++             if !p.HostIP.Equal(o.HostIP) {
+++                     return false
+++             }
+++     } else {
+++             if o.HostIP != nil {
+++                     return false
+++             }
+++     }
+++
+++     return true
+++}
+++
+++// ErrInvalidProtocolBinding is returned when the port binding protocol is not valid.
+++type ErrInvalidProtocolBinding string
+++
+++func (ipb ErrInvalidProtocolBinding) Error() string {
+++     return fmt.Sprintf("invalid transport protocol: %s", string(ipb))
+++}
+++
+++const (
+++     // ICMP is for the ICMP ip protocol
+++     ICMP = 1
+++     // TCP is for the TCP ip protocol
+++     TCP = 6
+++     // UDP is for the UDP ip protocol
+++     UDP = 17
+++)
+++
+++// Protocol represents a IP protocol number
+++type Protocol uint8
+++
+++func (p Protocol) String() string {
+++     switch p {
+++     case ICMP:
+++             return "icmp"
+++     case TCP:
+++             return "tcp"
+++     case UDP:
+++             return "udp"
+++     default:
+++             return fmt.Sprintf("%d", p)
+++     }
+++}
+++
+++// ParseProtocol returns the respective Protocol type for the passed string
+++func ParseProtocol(s string) Protocol {
+++     switch strings.ToLower(s) {
+++     case "icmp":
+++             return ICMP
+++     case "udp":
+++             return UDP
+++     case "tcp":
+++             return TCP
+++     default:
+++             return 0
+++     }
+++}
+++
+++// GetMacCopy returns a copy of the passed MAC address
+++func GetMacCopy(from net.HardwareAddr) net.HardwareAddr {
+++     to := make(net.HardwareAddr, len(from))
+++     copy(to, from)
+++     return to
+++}
+++
+++// GetIPCopy returns a copy of the passed IP address
+++func GetIPCopy(from net.IP) net.IP {
+++     to := make(net.IP, len(from))
+++     copy(to, from)
+++     return to
+++}
+++
+++// GetIPNetCopy returns a copy of the passed IP Network
+++func GetIPNetCopy(from *net.IPNet) *net.IPNet {
+++     if from == nil {
+++             return nil
+++     }
+++     bm := make(net.IPMask, len(from.Mask))
+++     copy(bm, from.Mask)
+++     return &net.IPNet{IP: GetIPCopy(from.IP), Mask: bm}
+++}
+++
+++// GetIPNetCanonical returns the canonical form for the passed network
+++func GetIPNetCanonical(nw *net.IPNet) *net.IPNet {
+++     if nw == nil {
+++             return nil
+++     }
+++     c := GetIPNetCopy(nw)
+++     c.IP = c.IP.Mask(nw.Mask)
+++     return c
+++}
+++
+++// CompareIPNet returns equal if the two IP Networks are equal
+++func CompareIPNet(a, b *net.IPNet) bool {
+++     if a == b {
+++             return true
+++     }
+++     if a == nil || b == nil {
+++             return false
+++     }
+++     return a.IP.Equal(b.IP) && bytes.Equal(a.Mask, b.Mask)
+++}
+++
+++const (
+++     // NEXTHOP indicates a StaticRoute with an IP next hop.
+++     NEXTHOP = iota
+++
+++     // CONNECTED indicates a StaticRoute with a interface for directly connected peers.
+++     CONNECTED
+++)
+++
+++// StaticRoute is a statically-provisioned IP route.
+++type StaticRoute struct {
+++     Destination *net.IPNet
+++
+++     RouteType int // NEXT_HOP or CONNECTED
+++
+++     // NextHop will be resolved by the kernel (i.e. as a loose hop).
+++     NextHop net.IP
+++
+++     // InterfaceID must refer to a defined interface on the
+++     // Endpoint to which the routes are specified.  Routes specified this way
+++     // are interpreted as directly connected to the specified interface (no
+++     // next hop will be used).
+++     InterfaceID int
+++}
+++
+++// GetCopy returns a copy of this StaticRoute structure
+++func (r *StaticRoute) GetCopy() *StaticRoute {
+++     d := GetIPNetCopy(r.Destination)
+++     nh := GetIPCopy(r.NextHop)
+++     return &StaticRoute{Destination: d,
+++             RouteType:   r.RouteType,
+++             NextHop:     nh,
+++             InterfaceID: r.InterfaceID}
+++}
+++
+++/******************************
+++ * Well-known Error Interfaces
+++ ******************************/
+++
+++// MaskableError is an interface for errors which can be ignored by caller
+++type MaskableError interface {
+++     // Maskable makes implementer into MaskableError type
+++     Maskable()
+++}
+++
+++// RetryError is an interface for errors which might get resolved through retry
+++type RetryError interface {
+++     // Retry makes implementer into RetryError type
+++     Retry()
+++}
+++
+++// BadRequestError is an interface for errors originated by a bad request
+++type BadRequestError interface {
+++     // BadRequest makes implementer into BadRequestError type
+++     BadRequest()
+++}
+++
+++// NotFoundError is an interface for errors raised because a needed resource is not available
+++type NotFoundError interface {
+++     // NotFound makes implementer into NotFoundError type
+++     NotFound()
+++}
+++
+++// ForbiddenError is an interface for errors which denote an valid request that cannot be honored
+++type ForbiddenError interface {
+++     // Forbidden makes implementer into ForbiddenError type
+++     Forbidden()
+++}
+++
+++// NoServiceError  is an interface for errors returned when the required service is not available
+++type NoServiceError interface {
+++     // NoService makes implementer into NoServiceError type
+++     NoService()
+++}
+++
+++// TimeoutError  is an interface for errors raised because of timeout
+++type TimeoutError interface {
+++     // Timeout makes implementer into TimeoutError type
+++     Timeout()
+++}
+++
+++// NotImplementedError  is an interface for errors raised because of requested functionality is not yet implemented
+++type NotImplementedError interface {
+++     // NotImplemented makes implementer into NotImplementedError type
+++     NotImplemented()
+++}
+++
+++// InternalError is an interface for errors raised because of an internal error
+++type InternalError interface {
+++     // Internal makes implementer into InternalError type
+++     Internal()
+++}
+++
+++/******************************
+++ * Well-known Error Formatters
+++ ******************************/
+++
+++// BadRequestErrorf creates an instance of BadRequestError
+++func BadRequestErrorf(format string, params ...interface{}) error {
+++     return badRequest(fmt.Sprintf(format, params...))
+++}
+++
+++// NotFoundErrorf creates an instance of NotFoundError
+++func NotFoundErrorf(format string, params ...interface{}) error {
+++     return notFound(fmt.Sprintf(format, params...))
+++}
+++
+++// ForbiddenErrorf creates an instance of ForbiddenError
+++func ForbiddenErrorf(format string, params ...interface{}) error {
+++     return forbidden(fmt.Sprintf(format, params...))
+++}
+++
+++// NoServiceErrorf creates an instance of NoServiceError
+++func NoServiceErrorf(format string, params ...interface{}) error {
+++     return noService(fmt.Sprintf(format, params...))
+++}
+++
+++// NotImplementedErrorf creates an instance of NotImplementedError
+++func NotImplementedErrorf(format string, params ...interface{}) error {
+++     return notImpl(fmt.Sprintf(format, params...))
+++}
+++
+++// TimeoutErrorf creates an instance of TimeoutError
+++func TimeoutErrorf(format string, params ...interface{}) error {
+++     return timeout(fmt.Sprintf(format, params...))
+++}
+++
+++// InternalErrorf creates an instance of InternalError
+++func InternalErrorf(format string, params ...interface{}) error {
+++     return internal(fmt.Sprintf(format, params...))
+++}
+++
+++// InternalMaskableErrorf creates an instance of InternalError and MaskableError
+++func InternalMaskableErrorf(format string, params ...interface{}) error {
+++     return maskInternal(fmt.Sprintf(format, params...))
+++}
+++
+++// RetryErrorf creates an instance of RetryError
+++func RetryErrorf(format string, params ...interface{}) error {
+++     return retry(fmt.Sprintf(format, params...))
+++}
+++
+++/***********************
+++ * Internal Error Types
+++ ***********************/
+++type badRequest string
+++
+++func (br badRequest) Error() string {
+++     return string(br)
+++}
+++func (br badRequest) BadRequest() {}
+++
+++type maskBadRequest string
+++
+++type notFound string
+++
+++func (nf notFound) Error() string {
+++     return string(nf)
+++}
+++func (nf notFound) NotFound() {}
+++
+++type forbidden string
+++
+++func (frb forbidden) Error() string {
+++     return string(frb)
+++}
+++func (frb forbidden) Forbidden() {}
+++
+++type noService string
+++
+++func (ns noService) Error() string {
+++     return string(ns)
+++}
+++func (ns noService) NoService() {}
+++
+++type maskNoService string
+++
+++type timeout string
+++
+++func (to timeout) Error() string {
+++     return string(to)
+++}
+++func (to timeout) Timeout() {}
+++
+++type notImpl string
+++
+++func (ni notImpl) Error() string {
+++     return string(ni)
+++}
+++func (ni notImpl) NotImplemented() {}
+++
+++type internal string
+++
+++func (nt internal) Error() string {
+++     return string(nt)
+++}
+++func (nt internal) Internal() {}
+++
+++type maskInternal string
+++
+++func (mnt maskInternal) Error() string {
+++     return string(mnt)
+++}
+++func (mnt maskInternal) Internal() {}
+++func (mnt maskInternal) Maskable() {}
+++
+++type retry string
+++
+++func (r retry) Error() string {
+++     return string(r)
+++}
+++func (r retry) Retry() {}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..efa26d7e2e1b3f6fa2999ea5497f141cd313a05f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
--- /dev/null
@@@@ -1,0 -1,0 -1,0 +1,111 @@@@
+++package types
+++
+++import (
+++     "flag"
+++     "testing"
+++)
+++
+++var runningInContainer = flag.Bool("incontainer", false, "Indicates if the test is running in a container")
+++
+++func TestErrorConstructors(t *testing.T) {
+++     var err error
+++
+++     err = BadRequestErrorf("Io ho %d uccello", 1)
+++     if err.Error() != "Io ho 1 uccello" {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(BadRequestError); !ok {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(MaskableError); ok {
+++             t.Fatal(err)
+++     }
+++
+++     err = RetryErrorf("Incy wincy %s went up the spout again", "spider")
+++     if err.Error() != "Incy wincy spider went up the spout again" {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(RetryError); !ok {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(MaskableError); ok {
+++             t.Fatal(err)
+++     }
+++
+++     err = NotFoundErrorf("Can't find the %s", "keys")
+++     if err.Error() != "Can't find the keys" {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(NotFoundError); !ok {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(MaskableError); ok {
+++             t.Fatal(err)
+++     }
+++
+++     err = ForbiddenErrorf("Can't open door %d", 2)
+++     if err.Error() != "Can't open door 2" {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(ForbiddenError); !ok {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(MaskableError); ok {
+++             t.Fatal(err)
+++     }
+++
+++     err = NotImplementedErrorf("Functionality %s is not implemented", "x")
+++     if err.Error() != "Functionality x is not implemented" {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(NotImplementedError); !ok {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(MaskableError); ok {
+++             t.Fatal(err)
+++     }
+++
+++     err = TimeoutErrorf("Process %s timed out", "abc")
+++     if err.Error() != "Process abc timed out" {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(TimeoutError); !ok {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(MaskableError); ok {
+++             t.Fatal(err)
+++     }
+++
+++     err = NoServiceErrorf("Driver %s is not available", "mh")
+++     if err.Error() != "Driver mh is not available" {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(NoServiceError); !ok {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(MaskableError); ok {
+++             t.Fatal(err)
+++     }
+++
+++     err = InternalErrorf("Not sure what happened")
+++     if err.Error() != "Not sure what happened" {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(InternalError); !ok {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(MaskableError); ok {
+++             t.Fatal(err)
+++     }
+++
+++     err = InternalMaskableErrorf("Minor issue, it can be ignored")
+++     if err.Error() != "Minor issue, it can be ignored" {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(InternalError); !ok {
+++             t.Fatal(err)
+++     }
+++     if _, ok := err.(MaskableError); !ok {
+++             t.Fatal(err)
+++     }
+++}