Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9d25255c6 | ||
|
|
3e55603f3f | ||
|
|
ec779c5ebd | ||
|
|
62de82f349 | ||
|
|
00b3d5bf98 | ||
|
|
f522f5b730 | ||
|
|
bd185abb29 | ||
|
|
203dfbb3e9 | ||
|
|
c09f0f7037 | ||
|
|
a334059e85 | ||
|
|
a9c710211b | ||
|
|
0f888e9735 | ||
|
|
17c8a16757 | ||
|
|
4e41f649fb | ||
|
|
f8ed319428 | ||
|
|
ef0153cb0f | ||
|
|
b77abf480b | ||
|
|
2fa5cda2c7 | ||
|
|
225e6871e3 | ||
|
|
5d2d857dbe | ||
|
|
d7c4bc04d3 | ||
|
|
32b2bcf01b | ||
|
|
b1308823d2 | ||
|
|
0627ade9de | ||
|
|
4d0ca39f97 | ||
|
|
8a54af92ef |
No files matched your search
@@ -1,123 +0,0 @@
|
||||
version: 2
|
||||
defaults: &defaults
|
||||
docker:
|
||||
- image: weaveworks/weavebuild:go-1-13-1-5110a604
|
||||
working_directory: /go/src/github.com/weaveworks/weave
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- run: git submodule update --init
|
||||
- run: make DEBUG=1 BUILD_IN_CONTAINER=false lint
|
||||
|
||||
unit-test:
|
||||
<<: *defaults
|
||||
parallelism: 2
|
||||
steps:
|
||||
- checkout
|
||||
- run: COVERDIR=test/coverage make BUILD_IN_CONTAINER=false tests
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- test/coverage
|
||||
|
||||
build:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
- run: git submodule update --init
|
||||
- run: make COVERAGE=true BUILD_IN_CONTAINER=false SUDO= exes all
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- weave.tar.gz
|
||||
- tools/runner/runner
|
||||
- test/tls/tls
|
||||
|
||||
smoke-tests:
|
||||
machine:
|
||||
image: circleci/classic:201709-01
|
||||
working_directory: /home/circleci/src/github.com/weaveworks/weave
|
||||
environment:
|
||||
SRCDIR: /home/circleci/src/github.com/weaveworks/weave
|
||||
CIRCLE_ARTIFACTS: /tmp/artifacts
|
||||
parallelism: 2
|
||||
steps:
|
||||
- checkout
|
||||
- run: mkdir $CIRCLE_ARTIFACTS
|
||||
- run: git submodule update --init
|
||||
- attach_workspace:
|
||||
at: .
|
||||
# kick off creation of test VMs
|
||||
- run: bin/provision_test_vms.sh
|
||||
# when VMs are ready, copy built software to them
|
||||
- run: bin/circle-test-pre
|
||||
- run:
|
||||
command: bin/circle-test-smoke
|
||||
no_output_timeout: 360s
|
||||
# Destroy testing VMs:
|
||||
- run:
|
||||
command: bin/circle-destroy-vms
|
||||
background: true
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- test/coverage
|
||||
- store_artifacts:
|
||||
path: /tmp/artifacts
|
||||
|
||||
gen-coverage:
|
||||
<<: *defaults
|
||||
environment:
|
||||
CIRCLE_ARTIFACTS: /tmp/artifacts
|
||||
steps:
|
||||
- checkout
|
||||
- run: mkdir $CIRCLE_ARTIFACTS
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: cd test; ./gen_coverage_reports.sh
|
||||
- run: goveralls -repotoken $COVERALLS_REPO_TOKEN -coverprofile=test/profile.cov -service=circleci
|
||||
- run: cp test/coverage.* $CIRCLE_ARTIFACTS
|
||||
- store_artifacts:
|
||||
path: /tmp/artifacts
|
||||
|
||||
deploy:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
# Register qemu hooks, rebuild without coverage and push to dockerhub
|
||||
- deploy:
|
||||
command: |
|
||||
docker login -u $DOCKER_USER -p $DOCKER_PASS
|
||||
docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
wget https://github.com/estesp/manifest-tool/releases/download/v0.8.0/manifest-tool-linux-amd64
|
||||
chmod +x manifest-tool-linux-amd64
|
||||
make clean-bin
|
||||
make publish MANIFEST_TOOL_CMD=./manifest-tool-linux-amd64 UPDATE_LATEST=latest-only SUDO= BUILD_IN_CONTAINER=false
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build_test:
|
||||
jobs:
|
||||
- lint
|
||||
- unit-test
|
||||
- build
|
||||
- smoke-tests:
|
||||
requires:
|
||||
- lint
|
||||
- unit-test
|
||||
- build
|
||||
- gen-coverage:
|
||||
requires:
|
||||
- unit-test
|
||||
- smoke-tests
|
||||
- deploy:
|
||||
requires:
|
||||
- smoke-tests
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
@@ -1,65 +0,0 @@
|
||||
<!--
|
||||
Hi, thank you for opening an issue!
|
||||
Before hitting the button...
|
||||
|
||||
** Is this a REQUEST FOR HELP? **
|
||||
If so, please have a look at:
|
||||
- our FAQ: https://www.weave.works/docs/net/latest/faq/
|
||||
- our troubleshooting page: https://www.weave.works/docs/net/latest/troubleshooting/
|
||||
- our help page, to choose the best channel (Slack, etc.) to reach out: https://www.weave.works/help/
|
||||
|
||||
** Is this a FEATURE REQUEST? **
|
||||
If so, please search existing feature requests, and if you find a similar one, up-vote it and/or add your comments to it instead.
|
||||
If you did not find a similar one, please describe in details:
|
||||
- why: your use-case, specific constraints you may have, etc.
|
||||
- what: the feature/behaviour/change you would like to see in Weave Net
|
||||
Do not hesitate, when appropriate, to share the exact commands or API you would like, and/or to share a diagram (e.g.: asciiflow.com): "a picture is worth a thousand words".
|
||||
|
||||
** Is this a BUG REPORT? **
|
||||
Please fill in as much of the template below as you can.
|
||||
|
||||
Thank you!
|
||||
-->
|
||||
|
||||
## What you expected to happen?
|
||||
|
||||
## What happened?
|
||||
<!-- Error message, actual behaviour, etc. -->
|
||||
|
||||
## How to reproduce it?
|
||||
<!-- Specific steps, as minimally and precisely as possible. -->
|
||||
|
||||
## Anything else we need to know?
|
||||
<!-- Cloud provider? Hardware? How did you configure your cluster? Kubernetes YAML, KOPS, etc. -->
|
||||
|
||||
## Versions:
|
||||
<!-- Please paste in the output of these commands; 'kubectl' only if using Kubernetes -->
|
||||
```
|
||||
$ weave version
|
||||
$ docker version
|
||||
$ uname -a
|
||||
$ kubectl version
|
||||
```
|
||||
|
||||
## Logs:
|
||||
```
|
||||
$ docker logs weave
|
||||
```
|
||||
or, if using Kubernetes:
|
||||
```
|
||||
$ kubectl logs -n kube-system <weave-net-pod> weave
|
||||
```
|
||||
<!-- (If output is long, please consider a Gist.) -->
|
||||
<!-- Anything interesting or unusual output by the below, potentially relevant, commands?
|
||||
$ journalctl -u docker.service --no-pager
|
||||
$ journalctl -u kubelet --no-pager
|
||||
$ kubectl get events
|
||||
-->
|
||||
|
||||
## Network:
|
||||
<!-- If your problem has anything to do with one network endpoint not being able to contact another, please run the following commands -->
|
||||
```
|
||||
$ ip route
|
||||
$ ip -4 -o addr
|
||||
$ sudo iptables-save
|
||||
```
|
||||
+15
-44
@@ -25,54 +25,25 @@ _testmain.go
|
||||
*.prof
|
||||
profile.cov
|
||||
coverage.html
|
||||
.pkg
|
||||
|
||||
# Emacs backup files
|
||||
*~
|
||||
|
||||
# Vim backup files
|
||||
*.swp
|
||||
|
||||
# Sed backup files
|
||||
*.bak
|
||||
|
||||
# Project specific
|
||||
prog/weaver/weaver
|
||||
prog/weavedns/weavedns
|
||||
prog/weavehosts/weavehosts
|
||||
prog/sigproxy/sigproxy
|
||||
prog/weavewait/weavewait
|
||||
prog/weavewait/weavewait_noop
|
||||
prog/weavewait/weavewait_nomcast
|
||||
prog/weaveutil/weaveutil
|
||||
prog/*/Dockerfile.weaveworks
|
||||
prog/kube-utils/kube-utils
|
||||
prog/weave-kube/kube-utils
|
||||
prog/weave-npc/weave-npc
|
||||
prog/plugin/plugin
|
||||
testing/cover/cover
|
||||
testing/runner/runner
|
||||
weaver/weaver
|
||||
weavedns/weavedns
|
||||
weaveproxy/weaveproxy
|
||||
sigproxy/sigproxy
|
||||
weavewait/weavewait
|
||||
tools/bin
|
||||
tools/build
|
||||
releases
|
||||
.*.uptodate
|
||||
weave*.tar.gz
|
||||
prog/weaver/weave
|
||||
prog/weaveexec/weavehosts
|
||||
prog/weaveexec/weavewait
|
||||
prog/weaveexec/weavewait_noop
|
||||
prog/weaveexec/weavewait_nomcast
|
||||
prog/weaveexec/sigproxy
|
||||
prog/weaver/weaveutil
|
||||
prog/weaveexec/docker*
|
||||
weave.tar
|
||||
weavedns.tar
|
||||
weaveexec.tar
|
||||
weaveexec/weave
|
||||
weaveexec/weaveproxy
|
||||
weaveexec/weavewait
|
||||
weaveexec/sigproxy
|
||||
weaveexec/docker*.tgz
|
||||
Vagrantfile.local
|
||||
test/tls/tls
|
||||
test/tls/*.pem
|
||||
test/coverage
|
||||
test/coverage.*
|
||||
test/images/network-tester/webserver
|
||||
*qemu-*-static
|
||||
terraform.tfstate
|
||||
terraform.tfstate.backup
|
||||
prog/net-plugin/rootfs/
|
||||
|
||||
# Website
|
||||
site-build
|
||||
@@ -1,3 +0,0 @@
|
||||
[submodule "tools"]
|
||||
path = tools
|
||||
url = https://github.com/weaveworks/build-tools
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
# IMPORTANT: Please only use GLOBs to ignore files and directories.
|
||||
|
||||
# Do not lint the below bash scripts in Weave Net, in order to
|
||||
# avoid breaking the build on make lint:
|
||||
bin/circle-deploy-issues
|
||||
bin/circle-deploy-master
|
||||
bin/circle-test-teardown
|
||||
bin/circle-test-unit
|
||||
bin/install-wordepress
|
||||
bin/multiweave
|
||||
bin/publish-site
|
||||
bin/release
|
||||
build/build.sh
|
||||
prog/weave-kube/launch.sh
|
||||
prog/weaveexec/symlink
|
||||
test/*_test.sh
|
||||
test/600_proxy_docker_py.sh
|
||||
test/assert.sh
|
||||
test/config.sh
|
||||
test/gce.sh
|
||||
test/gen_coverage_reports.sh
|
||||
test/run_all.sh
|
||||
test/sanity_check.sh
|
||||
test/setup.sh
|
||||
vendor/*.sh
|
||||
weave
|
||||
@@ -1 +0,0 @@
|
||||
2.3.0
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.4
|
||||
|
||||
env:
|
||||
- PATH="${PATH}:${HOME}/.local/bin"
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libpcap-dev
|
||||
|
||||
install:
|
||||
- go clean -i net
|
||||
- go install -tags netgo std
|
||||
- pip install --user --upgrade gcloud gsutil
|
||||
- bin/setup-ci-secrets $encrypted_5ba036e89377_key $encrypted_5ba036e89377_iv
|
||||
- make travis
|
||||
|
||||
script:
|
||||
- bin/lint .
|
||||
- make tests
|
||||
|
||||
after_success:
|
||||
- go get github.com/mattn/goveralls
|
||||
- $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci
|
||||
- test "${TRAVIS_PULL_REQUEST}" = "false" && site/build-and-publish.sh
|
||||
+3
-1173
File diff suppressed because it is too large.
Load diff
@@ -1,51 +0,0 @@
|
||||
Thanks for your interest in contributing to this project and for
|
||||
taking the time to read this guide.
|
||||
|
||||
# Code of conduct
|
||||
|
||||
**Be respectful:** disagreements between people are to be expected and are usually the sign of healthy debate and engagement. Disagreements can lead to frustration and even anger for some members. Turning to personal insults, intimidation or threatening behaviour does not improve the situation. Participants should thus take care to ensure all communications / interactions stay professional at all times.
|
||||
|
||||
**Be considerate:** remember that the community has members with a diverse background, many of whom have English as a second language. What might appear impolite, may simply be a result of a lack of knowledge of the English language. Bear in mind that actions will have an impact on other community members and the project as a whole, so take potential consequences into account before pursuing a course of action.
|
||||
|
||||
**Be forgiving:** humans are fallible and as such prone to make mistakes and inexplicably change their positions at times. Don't assume that other members are acting with malicious intent. Be prepared to forgive people who make mistakes and assist each other in learning from them. Playing a blame game doesn't help anyone.
|
||||
|
||||
# Issues
|
||||
* Before reporting an issue make sure you search first if anybody has already reported a similar issue and whether or not it has been fixed.
|
||||
* Make sure your issue report sufficiently details the problem:
|
||||
* If you set up weave in some way other than that described by our [main instructions](https://github.com/zettio/weave#installation), please mention that.
|
||||
* Include step-by-step instructions to reproduce the issue.
|
||||
* State the OS platform you are using, i.e. `uname -a`
|
||||
* `iptables` rules and firewalls can have a great impact on any Linux network; please note whether you have set up any rules and/or are running a firewall on your machine(s).
|
||||
* If you want to tackle any open issue, make sure you let people know you are working on it.
|
||||
|
||||
## Pull Requests
|
||||
* Please be generous describing your changes.
|
||||
* Although it is highly suggested to include tests, they are not a hard requirement in order to get your contributions accepted.
|
||||
* Keep pull requests small so core developers can review them quickly.
|
||||
* Keep each pull request focused on a specific topic. If you have two things to change, create two pull requests. This helps reviewers to understand the meat of your contribution.
|
||||
|
||||
## Managing Vendored Dependencies
|
||||
|
||||
We copy all Go dependencies into the `vendor` subdirectory and use
|
||||
[`dep`](https://github.com/golang/dep) to manage them.
|
||||
|
||||
If you add a dependency in the code, run `dep ensure` to include it in
|
||||
the config and vendor dir. Same if you remove a dependency from the code.
|
||||
|
||||
To update a single dependency run `dep ensure -update github.com/foo/bar`
|
||||
|
||||
To override `dep`'s choice of library version, you can add
|
||||
[rules](https://golang.github.io/dep/docs/daily-dep.html#rule-changes-in-gopkgtoml).
|
||||
Please add a comment saying why the override is necessary and when it can be
|
||||
removed.
|
||||
|
||||
These operations result in staged but uncommitted changes to your
|
||||
branch; you will need to commit them as normal. Execute them in the
|
||||
root of your checkout.
|
||||
|
||||
## Workflow
|
||||
See [WorkingOnWeave](https://github.com/weaveworks/weave/wiki/WorkingOnWeave)
|
||||
|
||||
# Acknowledgements
|
||||
|
||||
Much of this taken from https://github.com/cloudescape/gowsdl/blob/master/CONTRIBUTING.md
|
||||
@@ -0,0 +1,5 @@
|
||||
This file lists all individuals having contributed content to the
|
||||
repository.
|
||||
|
||||
Matthew Sackman <matthew@wellquite.org>
|
||||
Matthias Radestock <matthias.radestock@gmail.com>
|
||||
-175
@@ -1,175 +0,0 @@
|
||||
./test/assert.sh is a copy of
|
||||
|
||||
https://github.com/lehmannro/assert.sh/blob/master/assert.sh
|
||||
|
||||
Since it was imported from its original source, it has only received
|
||||
cosmetic modifications. As it is licensed under the LGPL-3, here's the
|
||||
license text in its entirety:
|
||||
|
||||
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
@@ -1,3 +0,0 @@
|
||||
DOCKER_VERSION=17.06
|
||||
KUBERNETES_VERSION=1.11.0
|
||||
KUBERNETES_CNI_VERSION=0.6.0
|
||||
Generated
-1138
File diff suppressed because it is too large.
Load diff
-34
@@ -1,34 +0,0 @@
|
||||
# Pin to version before sctp was added, because latest libnetwork is
|
||||
# incompatible with latest sctp
|
||||
[[constraint]]
|
||||
name = "github.com/docker/libnetwork"
|
||||
revision = "119badbd"
|
||||
|
||||
# Encourage dep not to use the docker-1.9 version
|
||||
[[constraint]]
|
||||
name = "github.com/fsouza/go-dockerclient"
|
||||
version = "1"
|
||||
|
||||
# Use recent version: last tagged version is years old
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/prometheus/client_golang"
|
||||
|
||||
# For compatibility with fsouza/go-dockerclient
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/docker/docker"
|
||||
|
||||
# Most recent release tag is incompatible with docker/docker; see https://github.com/docker/distribution/issues/2503
|
||||
[[override]]
|
||||
branch = "master"
|
||||
name = "github.com/docker/distribution"
|
||||
|
||||
# v8.0.0 removes juju/ratelimit licensed under LGPL3
|
||||
[[constraint]]
|
||||
name = "k8s.io/client-go"
|
||||
revision = "v8.0.0"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
@@ -176,7 +176,7 @@
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2014-2017 Weaveworks Ltd.
|
||||
Copyright 2014-2015 Weaveworks Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,441 +1,109 @@
|
||||
PUBLISH=publish_weave publish_weavedns publish_weaveexec
|
||||
|
||||
.DEFAULT: all
|
||||
.PHONY: all exes testrunner update tests lint publish-one-arch $(PUBLISH) clean clean-bin clean-work-dir prerequisites build run-smoketests
|
||||
.PHONY: all update tests publish $(PUBLISH) clean prerequisites build travis run-smoketests
|
||||
|
||||
# If you can use docker without being root, you can do "make SUDO="
|
||||
SUDO=$(shell docker info >/dev/null 2>&1 || echo "sudo -E")
|
||||
BUILD_IN_CONTAINER=true
|
||||
RM=--rm
|
||||
RUN_FLAGS=-ti
|
||||
COVERAGE=
|
||||
SUDO=sudo
|
||||
|
||||
# This specifies the architecture we're building for
|
||||
ARCH?=amd64
|
||||
DOCKERHUB_USER=weaveworks
|
||||
WEAVE_VERSION=git-$(shell git rev-parse --short=12 HEAD)
|
||||
WEAVER_EXE=weaver/weaver
|
||||
WEAVEDNS_EXE=weavedns/weavedns
|
||||
WEAVEPROXY_EXE=weaveproxy/weaveproxy
|
||||
SIGPROXY_EXE=sigproxy/sigproxy
|
||||
WEAVEWAIT_EXE=weavewait/weavewait
|
||||
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave
|
||||
WEAVEDNS_IMAGE=$(DOCKERHUB_USER)/weavedns
|
||||
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec
|
||||
WEAVER_EXPORT=weave.tar
|
||||
WEAVEDNS_EXPORT=weavedns.tar
|
||||
WEAVEEXEC_EXPORT=weaveexec.tar
|
||||
|
||||
# We're using QEMU to be able to build Docker images for other architectures
|
||||
# from amd64 machines.
|
||||
QEMU_VERSION=v2.7.0
|
||||
WEAVEEXEC_DOCKER_VERSION=1.3.1
|
||||
DOCKER_DISTRIB=weaveexec/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
|
||||
DOCKER_DISTRIB_URL=https://get.docker.com/builds/Linux/x86_64/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
|
||||
|
||||
# A list of all supported architectures here. Should be named as Go is naming platforms
|
||||
# All supported architectures must have an "ifeq" block below that customizes the parameters
|
||||
ALL_ARCHITECTURES=amd64 arm arm64 ppc64le s390x
|
||||
ML_PLATFORMS=linux/amd64,linux/arm,linux/arm64,linux/ppc64le,linux/s390x
|
||||
all: $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT)
|
||||
|
||||
ifeq ($(ARCH),amd64)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=x86_64
|
||||
travis: $(WEAVER_EXE) $(WEAVEDNS_EXE)
|
||||
|
||||
# The name of the alpine baseimage to use as the base for weave images
|
||||
ALPINE_BASEIMAGE?=alpine:3.8
|
||||
update:
|
||||
go get -u -f -v -tags -netgo ./$(dir $(WEAVER_EXE)) ./$(dir $(WEAVEDNS_EXE)) ./$(dir $(SIGPROXY_EXE)) ./$(dir $(WEAVEPROXY_EXE))
|
||||
|
||||
# The extension for the made images
|
||||
# Specifying none means for example weaveworks/weave:latest
|
||||
ARCH_EXT?=
|
||||
$(WEAVER_EXE) $(WEAVEDNS_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE): common/*.go
|
||||
go get -tags netgo ./$(@D)
|
||||
go build -ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)" -tags netgo -o $@ ./$(@D)
|
||||
@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
|
||||
rm $@; \
|
||||
echo "\nYour go standard library was built without the 'netgo' build tag."; \
|
||||
echo "To fix that, run"; \
|
||||
echo " sudo go clean -i net"; \
|
||||
echo " sudo go install -tags netgo std"; \
|
||||
false; \
|
||||
}
|
||||
|
||||
# The name of the gcc we're using for compiling C code
|
||||
CC=gcc
|
||||
$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go weaver/main.go
|
||||
$(WEAVEDNS_EXE): nameserver/*.go weavedns/main.go
|
||||
$(WEAVEPROXY_EXE): proxy/*.go weaveproxy/main.go
|
||||
$(WEAVEWAIT_EXE): weavewait/*.go weavewait/main.go
|
||||
|
||||
# Optional parameters that can be passed when linking C code into the Go binary
|
||||
CGO_LDFLAGS=
|
||||
endif
|
||||
ifeq ($(ARCH),arm)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=armel
|
||||
# Sigproxy needs separate rule as it fails the netgo check in the main
|
||||
# build stanza due to not importing net package
|
||||
$(SIGPROXY_EXE): sigproxy/main.go
|
||||
go build -o $@ ./$(@D)
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=arm32v6/alpine:3.8
|
||||
$(WEAVER_EXPORT): weaver/Dockerfile $(WEAVER_EXE)
|
||||
$(SUDO) docker build -t $(WEAVER_IMAGE) weaver
|
||||
$(SUDO) docker save $(WEAVER_IMAGE):latest > $@
|
||||
|
||||
# arm images have the -arm suffix, for instance weaveworks/weave-arm:latest
|
||||
ARCH_EXT?=-arm
|
||||
$(WEAVEDNS_EXPORT): weavedns/Dockerfile $(WEAVEDNS_EXE)
|
||||
$(SUDO) docker build -t $(WEAVEDNS_IMAGE) weavedns
|
||||
$(SUDO) docker save $(WEAVEDNS_IMAGE):latest > $@
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=arm-linux-gnueabihf-gcc
|
||||
$(WEAVEEXEC_EXPORT): weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE)
|
||||
cp weave weaveexec/weave
|
||||
cp $(SIGPROXY_EXE) weaveexec/sigproxy
|
||||
cp $(WEAVEWAIT_EXE) weaveexec/weavewait
|
||||
cp $(WEAVEPROXY_EXE) weaveexec/weaveproxy
|
||||
cp $(DOCKER_DISTRIB) weaveexec/docker.tgz
|
||||
$(SUDO) docker build -t $(WEAVEEXEC_IMAGE) weaveexec
|
||||
$(SUDO) docker save $(WEAVEEXEC_IMAGE):latest > $@
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=arm
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for arm are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
ifeq ($(ARCH),arm64)
|
||||
# Use the arm 32-bit docker client variant
|
||||
WEAVEEXEC_DOCKER_ARCH?=armel
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=arm64v8/alpine:3.8
|
||||
|
||||
# arm64 images have the -arm64 suffix, for instance weaveworks/weave-arm64:latest
|
||||
ARCH_EXT?=-arm64
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=aarch64-linux-gnu-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=aarch64
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for arm64 are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
ifeq ($(ARCH),ppc64le)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=ppc64le
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=ppc64le/alpine:3.8
|
||||
|
||||
# ppc64le images have the -ppc64le suffix, for instance weaveworks/weave-ppc64le:latest
|
||||
ARCH_EXT?=-ppc64le
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=powerpc64le-linux-gnu-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=ppc64le
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for ppc64le are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
ifeq ($(ARCH),s390x)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=s390x
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=s390x/alpine:3.6
|
||||
|
||||
# s390x images have the -s390x suffix, for instance weaveworks/weave-s390x:latest
|
||||
ARCH_EXT?=-s390x
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=s390x-linux-gnu-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=s390x
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for s390x are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
|
||||
# The name of the user that this Makefile should produce image artifacts for. Can/should be overridden
|
||||
DOCKERHUB_USER?=weaveworks
|
||||
# The default version that's chosen when pushing the images. Can/should be overridden
|
||||
GIT_REVISION=$(shell git rev-parse HEAD)
|
||||
WEAVE_VERSION?=git-$(shell git rev-parse --short=12 HEAD)
|
||||
# Docker Store does not allow the "latest" tag.
|
||||
NET_PLUGIN_LATEST=latest_release
|
||||
|
||||
# Paths to all relevant binaries that should be compiled
|
||||
WEAVER_EXE=prog/weaver/weaver
|
||||
SIGPROXY_EXE=prog/sigproxy/sigproxy
|
||||
KUBEUTILS_EXE=prog/kube-utils/kube-utils
|
||||
WEAVENPC_EXE=prog/weave-npc/weave-npc
|
||||
WEAVEWAIT_EXE=prog/weavewait/weavewait
|
||||
WEAVEWAIT_NOOP_EXE=prog/weavewait/weavewait_noop
|
||||
WEAVEWAIT_NOMCAST_EXE=prog/weavewait/weavewait_nomcast
|
||||
WEAVEUTIL_EXE=prog/weaveutil/weaveutil
|
||||
RUNNER_EXE=tools/runner/runner
|
||||
# Get this from https://github.com/estesp/manifest-tool/releases
|
||||
MANIFEST_TOOL_CMD=manifest-tool
|
||||
TEST_TLS_EXE=test/tls/tls
|
||||
NETWORKTESTER_EXE=test/images/network-tester/webserver
|
||||
|
||||
# All binaries together in a list
|
||||
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(KUBEUTILS_EXE) $(WEAVENPC_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE) $(RUNNER_EXE) $(TEST_TLS_EXE) $(NETWORKTESTER_EXE)
|
||||
|
||||
# These stamp files are used to mark the current state of the build; whether an image has been built or not
|
||||
BUILD_UPTODATE=.build.uptodate
|
||||
WEAVER_UPTODATE=.weaver$(ARCH_EXT).uptodate
|
||||
WEAVEEXEC_UPTODATE=.weaveexec$(ARCH_EXT).uptodate
|
||||
PLUGIN_UPTODATE=.net-plugin$(ARCH_EXT).uptodate
|
||||
WEAVEKUBE_UPTODATE=.weavekube$(ARCH_EXT).uptodate
|
||||
WEAVENPC_UPTODATE=.weavenpc$(ARCH_EXT).uptodate
|
||||
WEAVEDB_UPTODATE=.weavedb.uptodate
|
||||
NETWORKTESTER_UPTODATE=.networktester$(ARCH_EXT).uptodate
|
||||
|
||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) $(WEAVEKUBE_UPTODATE) $(WEAVENPC_UPTODATE) $(NETWORKTESTER_UPTODATE)
|
||||
|
||||
# The names of the images. Note that the images for other architectures than amd64 have a suffix in the image name.
|
||||
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave$(ARCH_EXT)
|
||||
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec$(ARCH_EXT)
|
||||
WEAVEKUBE_IMAGE=$(DOCKERHUB_USER)/weave-kube$(ARCH_EXT)
|
||||
WEAVENPC_IMAGE=$(DOCKERHUB_USER)/weave-npc$(ARCH_EXT)
|
||||
BUILD_IMAGE=weaveworks/weavebuild
|
||||
WEAVEDB_IMAGE=$(DOCKERHUB_USER)/weavedb
|
||||
PLUGIN_IMAGE=$(DOCKERHUB_USER)/net-plugin
|
||||
NETWORKTESTER_IMAGE=$(DOCKERHUB_USER)/network-tester
|
||||
|
||||
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) $(WEAVEKUBE_IMAGE) $(WEAVENPC_IMAGE) $(WEAVEDB_IMAGE) $(NETWORKTESTER_IMAGE)
|
||||
|
||||
PLUGIN_WORK_DIR="prog/net-plugin/rootfs"
|
||||
PLUGIN_BUILD_IMG="plugin-builder"
|
||||
|
||||
PUBLISH=publish_weave publish_weaveexec publish_weave-kube publish_weave-npc
|
||||
PUSH_ML=push_ml_weave push_ml_weaveexec push_ml_weave-kube push_ml_weave-npc
|
||||
|
||||
WEAVE_EXPORT=weave$(ARCH_EXT).tar.gz
|
||||
|
||||
# The flags we are passing to go build. -extldflags -static for making a static binary,
|
||||
# -X main.version for telling the Go binary which version it is,
|
||||
# -s -w drop symbol tables used by debuggers (not Go's internal symbol info)
|
||||
# -tags "osusergo netgo" to use native Go UID and DNS implementations
|
||||
BUILD_FLAGS=-ldflags "-extldflags -static -X main.version=$(WEAVE_VERSION) -s -w" -tags "osusergo netgo"
|
||||
|
||||
PACKAGE_BASE=$(shell go list -e ./)
|
||||
|
||||
all: $(WEAVE_EXPORT)
|
||||
testrunner: $(RUNNER_EXE) $(TEST_TLS_EXE)
|
||||
|
||||
$(WEAVER_EXE) $(WEAVEUTIL_EXE): common/*.go common/*/*.go net/*.go net/*/*.go
|
||||
$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go db/*.go nameserver/*.go prog/weaver/*.go
|
||||
$(WEAVER_EXE): api/*.go plugin/*.go plugin/*/*
|
||||
$(WEAVER_EXE): proxy/*.go
|
||||
$(WEAVEUTIL_EXE): prog/weaveutil/*.go net/*.go plugin/net/*.go plugin/ipam/*.go db/*.go
|
||||
$(SIGPROXY_EXE): prog/sigproxy/*.go
|
||||
$(KUBEUTILS_EXE): prog/kube-utils/*.go
|
||||
$(WEAVENPC_EXE): prog/weave-npc/*.go npc/*.go npc/*/*.go
|
||||
$(TEST_TLS_EXE): test/tls/*.go
|
||||
$(RUNNER_EXE): tools/runner/*.go
|
||||
$(WEAVEWAIT_NOOP_EXE): prog/weavewait/*.go
|
||||
$(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go
|
||||
$(WEAVEWAIT_NOMCAST_EXE): prog/weavewait/*.go net/*.go
|
||||
$(NETWORKTESTER_EXE): test/images/network-tester/*.go
|
||||
tests: tools/.git
|
||||
lint: tools/.git
|
||||
|
||||
ifeq ($(BUILD_IN_CONTAINER),true)
|
||||
|
||||
# This make target compiles all binaries inside of the weaveworks/build container
|
||||
# It bind-mounts the source into the container and passes all important variables
|
||||
exes $(EXES) tests lint: $(BUILD_UPTODATE)
|
||||
$(SUDO) docker run $(RM) $(RUN_FLAGS) \
|
||||
-v $(shell pwd):/go/src/github.com/weaveworks/weave \
|
||||
-e GOARCH=$(ARCH) -e CGO_ENABLED=1 -e GOOS=linux -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX -e COVERDIR -e SLOW -e DEBUG \
|
||||
$(BUILD_IMAGE) COVERAGE=$(COVERAGE) WEAVE_VERSION=$(WEAVE_VERSION) CC=$(CC) QEMUARCH=$(QEMUARCH) CGO_LDFLAGS=$(CGO_LDFLAGS) $@
|
||||
touch $@
|
||||
|
||||
else
|
||||
|
||||
exes: $(EXES)
|
||||
|
||||
$(WEAVER_EXE):
|
||||
ifeq ($(COVERAGE),true)
|
||||
$(eval COVERAGE_MODULES := $(shell (go list ./$(@D); go list -f '{{join .Deps "\n"}}' ./$(@D) | grep "^$(PACKAGE_BASE)/") | grep -v "^$(PACKAGE_BASE)/vendor/" | paste -s -d,))
|
||||
go test -c -o ./$@ $(BUILD_FLAGS) -v -covermode=atomic -coverpkg $(COVERAGE_MODULES) ./$(@D)/
|
||||
else
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
endif
|
||||
|
||||
$(WEAVEUTIL_EXE) $(KUBEUTILS_EXE) $(WEAVENPC_EXE) $(NETWORKTESTER_EXE):
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
|
||||
$(WEAVEWAIT_EXE):
|
||||
go build $(BUILD_FLAGS) -tags "osusergo netgo iface mcast" -o $@ ./$(@D)
|
||||
|
||||
$(WEAVEWAIT_NOMCAST_EXE):
|
||||
go build $(BUILD_FLAGS) -tags "osusergo netgo iface" -o $@ ./$(@D)
|
||||
|
||||
# These programs need a separate rule as they fail the netgo check in
|
||||
# the main build stanza due to not importing net package
|
||||
$(SIGPROXY_EXE) $(TEST_TLS_EXE) $(WEAVEWAIT_NOOP_EXE) $(RUNNER_EXE):
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
$(DOCKER_DISTRIB):
|
||||
curl -o $(DOCKER_DISTRIB) $(DOCKER_DISTRIB_URL)
|
||||
|
||||
tests:
|
||||
./tools/test -no-go-get -netgo -timeout 8m
|
||||
echo "mode: count" > profile.cov
|
||||
fail=0 ; \
|
||||
for dir in $$(find . -type f -name '*_test.go' | xargs -n1 dirname | sort -u); do \
|
||||
output=$$(mktemp cover.XXXXXXXXXX) ; \
|
||||
if ! go test -tags netgo -covermode=count -coverprofile=$$output $$dir ; then \
|
||||
fail=1 ; \
|
||||
fi ; \
|
||||
if [ -f $$output ]; then \
|
||||
tail -n +2 <$$output >>profile.cov; \
|
||||
rm $$output; \
|
||||
fi \
|
||||
done ; \
|
||||
exit $$fail
|
||||
go tool cover -html=profile.cov -o=coverage.html
|
||||
|
||||
lint:
|
||||
./tools/lint -nocomment -notestpackage
|
||||
$(PUBLISH): publish_%:
|
||||
$(SUDO) docker tag -f $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
$(SUDO) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
$(SUDO) docker push $(DOCKERHUB_USER)/$*:latest
|
||||
|
||||
endif
|
||||
publish: $(PUBLISH)
|
||||
|
||||
# This rule makes sure the build image is up-to-date.
|
||||
# It also makes sure the multiarch hooks are reqistered in the kernel so the QEMU emulation works
|
||||
$(BUILD_UPTODATE): build/* tools/.git
|
||||
$(SUDO) docker build -t $(BUILD_IMAGE) build/
|
||||
$(SUDO) docker tag $(BUILD_IMAGE) $(BUILD_IMAGE):$(shell tools/image-tag)
|
||||
$(SUDO) docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
touch $@
|
||||
|
||||
# Creates the Dockerfile.your-user-here file from the template
|
||||
# Also replaces all placeholders with real values
|
||||
# If the architecture is amd64, it deletes all CROSS_BUILD lines
|
||||
# but otherwise, it only removes the "CROSS_BUILD_" placeholder and downloads QEMU
|
||||
%/Dockerfile.$(DOCKERHUB_USER): %/Dockerfile.template
|
||||
echo "DOCKERHUB_USER|$(DOCKERHUB_USER)|g;s|ARCH_EXT|$(ARCH_EXT)|g;s|ALPINE_BASEIMAGE|$(ALPINE_BASEIMAGE)|g;s|QEMUARCH|$(QEMUARCH)"
|
||||
sed -e "s|DOCKERHUB_USER|$(DOCKERHUB_USER)|g;s|ARCH_EXT|$(ARCH_EXT)|g;s|ALPINE_BASEIMAGE|$(ALPINE_BASEIMAGE)|g;s|QEMUARCH|$(QEMUARCH)|g" $^ > $@
|
||||
ifeq ($(ARCH),amd64)
|
||||
# When building "normally" for amd64, remove the whole line, it has no part in the amd64 image
|
||||
sed -i.bak "/CROSS_BUILD_/d" $@
|
||||
else
|
||||
# When cross-building, only the placeholder "CROSS_BUILD_" should be removed
|
||||
# Register /usr/bin/qemu-ARCH-static as the handler for ARM and ppc64le binaries in the kernel
|
||||
curl -sSL https://github.com/multiarch/qemu-user-static/releases/download/$(QEMU_VERSION)/x86_64_qemu-$(QEMUARCH)-static.tar.gz | tar -xz -C $(shell dirname $@)
|
||||
cd $(shell dirname $@) && sha256sum -c $(shell pwd)/build/shasums/qemu-$(QEMUARCH)-static.sha256sum
|
||||
sed -i.bak "s/CROSS_BUILD_//g" $@
|
||||
endif
|
||||
|
||||
# The targets below builds the weave images
|
||||
$(WEAVER_UPTODATE): prog/weaver/Dockerfile.$(DOCKERHUB_USER) $(WEAVER_EXE) weave $(WEAVEUTIL_EXE)
|
||||
cp $(WEAVEUTIL_EXE) prog/weaver/weaveutil
|
||||
cp weave prog/weaver/weave
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weaver/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVER_IMAGE) prog/weaver
|
||||
touch $@
|
||||
|
||||
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) prog/weaveexec/symlink $(SIGPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVER_UPTODATE)
|
||||
cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy
|
||||
cp $(WEAVEWAIT_EXE) prog/weaveexec/weavewait
|
||||
cp $(WEAVEWAIT_NOOP_EXE) prog/weaveexec/weavewait_noop
|
||||
cp $(WEAVEWAIT_NOMCAST_EXE) prog/weaveexec/weavewait_nomcast
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEEXEC_IMAGE) prog/weaveexec
|
||||
touch $@
|
||||
|
||||
# Builds Docker plugin.
|
||||
$(PLUGIN_UPTODATE): prog/net-plugin/launch.sh prog/net-plugin/config.json $(WEAVER_UPTODATE)
|
||||
-$(SUDO) docker rm -f $(PLUGIN_BUILD_IMG) 2>/dev/null
|
||||
$(SUDO) docker create --name=$(PLUGIN_BUILD_IMG) $(WEAVER_IMAGE) true
|
||||
rm -rf $(PLUGIN_WORK_DIR)
|
||||
mkdir $(PLUGIN_WORK_DIR)
|
||||
$(SUDO) docker export $(PLUGIN_BUILD_IMG) | tar -x -C $(PLUGIN_WORK_DIR)
|
||||
$(SUDO) docker rm -f $(PLUGIN_BUILD_IMG)
|
||||
cp prog/net-plugin/launch.sh $(PLUGIN_WORK_DIR)/home/weave/launch.sh
|
||||
-$(SUDO) docker plugin disable $(PLUGIN_IMAGE):$(WEAVE_VERSION) 2>/dev/null
|
||||
-$(SUDO) docker plugin rm $(PLUGIN_IMAGE):$(WEAVE_VERSION) 2>/dev/null
|
||||
$(SUDO) docker plugin create $(PLUGIN_IMAGE):$(WEAVE_VERSION) prog/net-plugin
|
||||
-$(SUDO) docker plugin disable $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) 2>/dev/null
|
||||
-$(SUDO) docker plugin rm $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) 2>/dev/null
|
||||
$(SUDO) docker plugin create $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) prog/net-plugin
|
||||
touch $@
|
||||
|
||||
$(WEAVEKUBE_UPTODATE): prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) prog/weave-kube/launch.sh $(KUBEUTILS_EXE) $(WEAVER_UPTODATE)
|
||||
cp $(KUBEUTILS_EXE) prog/weave-kube/
|
||||
$(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEKUBE_IMAGE) prog/weave-kube
|
||||
touch $@
|
||||
|
||||
$(WEAVENPC_UPTODATE): prog/weave-npc/Dockerfile.$(DOCKERHUB_USER) $(WEAVENPC_EXE) prog/weave-npc/ulogd.conf
|
||||
$(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weave-npc/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVENPC_IMAGE) prog/weave-npc
|
||||
touch $@
|
||||
|
||||
$(WEAVEDB_UPTODATE): prog/weavedb/Dockerfile
|
||||
$(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -t $(WEAVEDB_IMAGE) prog/weavedb
|
||||
touch $@
|
||||
|
||||
$(NETWORKTESTER_UPTODATE): test/images/network-tester/Dockerfile $(NETWORKTESTER_EXE)
|
||||
$(SUDO) docker build -t $(NETWORKTESTER_IMAGE) test/images/network-tester
|
||||
touch $@
|
||||
|
||||
$(WEAVE_EXPORT): $(IMAGES_UPTODATE) $(WEAVEDB_UPTODATE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@
|
||||
|
||||
tools/.git:
|
||||
git submodule update --init
|
||||
|
||||
# CODE FOR PUBLISHING THE IMAGES
|
||||
|
||||
# Push plugin
|
||||
plugin_publish: $(PLUGIN_UPTODATE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker plugin push $(PLUGIN_IMAGE):$(WEAVE_VERSION)
|
||||
# "$(NET_PLUGIN_LATEST)" means "stable release" here, so only push that if explicitly told to
|
||||
ifeq ($(UPDATE_LATEST),true)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker plugin push $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST)
|
||||
endif
|
||||
|
||||
# This target first runs "make publish" for each architecture
|
||||
# Then it pushes the manifest lists
|
||||
publish: $(addprefix sub-publish-,$(ALL_ARCHITECTURES)) $(WEAVEDB_UPTODATE)
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) $(PUSH_ML)
|
||||
ifeq ($(PUBLISH_WEAVEDB),true)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/weavedb:latest
|
||||
endif
|
||||
|
||||
sub-publish-%:
|
||||
$(MAKE) ARCH=$* DOCKER_HOST=$(DOCKER_HOST) clean-bin
|
||||
$(MAKE) ARCH=$* DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) $(IMAGES_UPTODATE)
|
||||
$(MAKE) ARCH=$* DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) publish-one-arch
|
||||
|
||||
publish-one-arch: $(PUBLISH)
|
||||
|
||||
# This rule handles the pushing of the built images
|
||||
$(PUBLISH): publish_%: $(IMAGES_UPTODATE)
|
||||
# Tag :latest with the real version
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*$(ARCH_EXT) $(DOCKERHUB_USER)/$*$(ARCH_EXT):$(WEAVE_VERSION)
|
||||
# Push the image with the arch suffix for arm, arm64 and ppc64le, and without suffix for amd64
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) push_$*$(ARCH_EXT)
|
||||
|
||||
ifeq ($(ARCH),amd64)
|
||||
# If the architecture is amd64, add the -amd64 suffix.
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION) $(DOCKERHUB_USER)/$*-amd64:$(WEAVE_VERSION)
|
||||
# If the version is latest, tag the -amd64 with latest as well
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*-amd64:$(WEAVE_VERSION) $(DOCKERHUB_USER)/$*-amd64:latest
|
||||
endif
|
||||
# Push the image with the -amd64-suffix so BINARY-ARCH-named images exist for all arches, so manifest lists may be made and ARCH is replaceable in scripts
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) push_$*-amd64
|
||||
endif
|
||||
|
||||
# This target pushes an image, and if UPDATE_LATEST is anything but false, also updates the latest tag
|
||||
# It takes one parameter, the image name
|
||||
push_%:
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:latest
|
||||
endif
|
||||
|
||||
# This target pushes a manifest list; it takes one parameter, the image name.
|
||||
# The variable UPDATE_LATEST controls whether it updates the versioned tag and/or the latest tag
|
||||
$(PUSH_ML): push_ml_%:
|
||||
ifneq ($(UPDATE_LATEST),latest-only)
|
||||
$(MANIFEST_TOOL_CMD) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:$(WEAVE_VERSION) --target $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
endif
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
# Push the manifest list to :latest as well
|
||||
$(MANIFEST_TOOL_CMD) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:latest --target $(DOCKERHUB_USER)/$*:latest
|
||||
endif
|
||||
|
||||
clean-work-dir:
|
||||
rm -rf $(PLUGIN_WORK_DIR)
|
||||
|
||||
clean-bin:
|
||||
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(IMAGES)
|
||||
find prog -type f -name "Dockerfile.*" -not -name "Dockerfile.template" -print | xargs rm -f
|
||||
find prog -type f -name "*qemu-*" -print | xargs rm -f
|
||||
go clean ./...
|
||||
rm -rf $(EXES) $(IMAGES_UPTODATE) $(WEAVEDB_UPTODATE) weave*.tar.gz .cache
|
||||
|
||||
clean: clean-bin clean-work-dir
|
||||
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(BUILD_IMAGE)
|
||||
rm -rf test/tls/*.pem test/coverage.* test/coverage $(BUILD_UPTODATE)
|
||||
clean:
|
||||
-$(SUDO) docker rmi $(WEAVER_IMAGE) $(WEAVEDNS_IMAGE) $(WEAVEEXEC_IMAGE)
|
||||
rm -f $(WEAVER_EXE) $(WEAVEDNS_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT)
|
||||
|
||||
build:
|
||||
$(SUDO) go clean -i net
|
||||
$(SUDO) go install -tags netgo std
|
||||
$(MAKE)
|
||||
|
||||
run-smoketests: all testrunner
|
||||
run-smoketests: all
|
||||
cd test && ./setup.sh && ./run_all.sh
|
||||
|
||||
integration-tests: all testrunner
|
||||
# Usage:
|
||||
# $ make \
|
||||
# NAME="<prefix used to name VMs and other resources>" \
|
||||
# PROVIDER="<provider among {vagrant|gcp|aws|do}>" \
|
||||
# NUM_HOSTS="<# test machines>" \
|
||||
# PLAYBOOK="<filename>" \
|
||||
# RUNNER_ARGS="<...>" \
|
||||
# TESTS="<...>" \ # Can be set to only run one or a few tests instead of the full test suite.
|
||||
# DOCKER_VERSION=<...> \
|
||||
# KUBERNETES_VERSION=<...> \
|
||||
# KUBERNETES_CNI_VERSION=<...> \
|
||||
# <...> # See also run-integration-test.sh for all variables and individual functions.
|
||||
# integration-tests
|
||||
#
|
||||
RUNNER_ARGS="-parallel" ./test/run-integration-tests.sh
|
||||
|
||||
# This target is only intended for use in Netlify CI environment for generating preview pages on feature branches and pull requests.
|
||||
# We need to obtain website repository (checked out under `site-build`) and place `site` directory into the context (`site-build/_weave_net_docs`).
|
||||
# We then run make in `site-build` and Netlify will publish the output (`site-build/_site`).
|
||||
netlify-site-preview:
|
||||
@mkdir -p site-build
|
||||
@curl --user $(WEBSITE_GITHUB_USER) --silent 'https://codeload.github.com/weaveworks/website-next/tar.gz/$(WEBSITE_BRANCH)' \
|
||||
| tar --extract --gunzip --directory site-build --strip 1
|
||||
@cp -r site site-build/_weave_net_docs
|
||||
@$(MAKE) -C site-build netlify_ensure_install
|
||||
@$(MAKE) -C site-build BUILD_ENV=netlify
|
||||
@@ -1,3 +1,3 @@
|
||||
Weave
|
||||
Copyright 2014-2016 Weaveworks Ltd.
|
||||
Copyright 2014-2015 Weaveworks Ltd.
|
||||
This product includes software developed at Weaveworks Ltd.
|
||||
@@ -1,41 +1,155 @@
|
||||
# Weave Net - Weaving Containers into Applications
|
||||
# Weave - the Docker network
|
||||
|
||||
[](https://circleci.com/gh/weaveworks/weave)
|
||||
[](https://coveralls.io/r/weaveworks/weave)
|
||||
[](https://goreportcard.com/report/github.com/weaveworks/weave)
|
||||
[](https://hub.docker.com/r/weaveworks/weave/)
|
||||
[](https://travis-ci.org/weaveworks/weave) [](https://circleci.com/gh/weaveworks/weave) [](https://coveralls.io/r/weaveworks/weave)
|
||||
|
||||
# About Weaveworks
|
||||
Weave creates a virtual network that connects Docker containers
|
||||
deployed across multiple hosts and enables their automatic discovery.
|
||||
|
||||
[Weaveworks](https://www.weave.works) is the company that delivers the most productive way for developers to connect, observe and control Docker containers.
|
||||

|
||||
|
||||
This repository contains [Weave Net](https://www.weave.works/products/weave-net/), the first product developed by Weaveworks, with over 8 million downloads to date. Weave Net enables you to get started with Docker clusters and portable apps in a fraction of the time required by other solutions.
|
||||
Applications use the network just as if the containers were all
|
||||
plugged into the same network switch, with no need to configure port
|
||||
mappings, links, etc. Services provided by application containers on
|
||||
the weave network can be made accessible to the outside world,
|
||||
regardless of where those containers are running. Similarly, existing
|
||||
internal systems can be exposed to application containers irrespective
|
||||
of their location.
|
||||
|
||||
## Weave Cloud:
|
||||

|
||||
|
||||
[Weave Cloud](https://www.weave.works/product/cloud/) is software-as-a-service that simplifies deployment, monitoring and management for containers and microservices. It extends and complements popular orchestrators, and enables developers and DevOps to realize faster deployments, insightful monitoring, visualization and networking.
|
||||
Weave can traverse firewalls and operate in partially connected
|
||||
networks. Traffic can be encrypted, allowing hosts to be connected
|
||||
across an untrusted network.
|
||||
|
||||
Weave Cloud is built using these Open Source projects: [Weave Scope](https://www.weave.works/products/weave-scope/), a powerful container visualization tool that automatically maps Docker containers and their interactions, [Weave Cortex](https://github.com/weaveworks/cortex), a horizontally-scalable version of Prometheus, and [Weave Flux](https://www.weave.works/products/weave-flux/), a continuous deployment tool that works with Kubernetes.
|
||||
With weave you can easily construct applications consisting of
|
||||
multiple containers, running anywhere.
|
||||
|
||||
# Weave Net
|
||||
Weave works alongside Docker's existing (single host) networking
|
||||
capabilities, so these can continue to be used by containers.
|
||||
|
||||
Weave Net creates a virtual network that connects Docker containers across multiple hosts and enables their automatic discovery. With Weave Net, portable microservices-based applications consisting of multiple containers can run anywhere: on one host, multiple hosts or even across cloud providers and data centers. Applications use the network just as if the containers were all plugged into the same network switch, without having to configure port mappings, ambassadors or links.
|
||||
## Installation
|
||||
|
||||
Services provided by application containers on the Weave network can be exposed to the outside world, regardless of where they are running. Similarly, existing internal systems can be opened to accept connections from application containers irrespective of their location.
|
||||
Ensure you are running Linux (kernel 3.8 or later) and have Docker
|
||||
(version 1.3.1 or later) installed. Then install weave with
|
||||
|
||||
## <a name="help"></a>Getting help
|
||||
sudo wget -O /usr/local/bin/weave \
|
||||
https://github.com/weaveworks/weave/releases/download/latest_release/weave
|
||||
sudo chmod a+x /usr/local/bin/weave
|
||||
|
||||
If you have any questions about, feedback for or a problem with Weave Net:
|
||||
CoreOS users see [here](https://github.com/fintanr/weave-gs/blob/master/coreos-simple/user-data) for an example of installing weave using cloud-config.
|
||||
|
||||
- Read [the Weave Net docs](https://www.weave.works/docs/net/latest/overview).
|
||||
- Invite yourself to the <a href="https://slack.weave.works/" target="_blank">Weave Users Slack</a>.
|
||||
- Ask a question on the [#net-dev](https://weave-community.slack.com/messages/net-dev/) slack channel.
|
||||
- Join the <a href="https://www.meetup.com/pro/Weave/"> Weave User Group </a> and get invited to online talks, hands-on training and meetups in your area.
|
||||
- [File an issue](https://github.com/weaveworks/weave/issues/new).
|
||||
Weave respects the environment variable `DOCKER_HOST`, so you can run
|
||||
it locally to control a weave network on a remote host.
|
||||
|
||||
Your feedback is always welcome!
|
||||
## Quick Start Screencast
|
||||
|
||||
## Further Information
|
||||
<a href="http://youtu.be/k6r7yuSr0hE" alt="Click to watch the screencast" target="_blank">
|
||||
<img src="/docs/hello-screencast.png" />
|
||||
</a>
|
||||
|
||||
* [Installing Weave Net](https://www.weave.works/docs/net/latest/install/installing-weave)
|
||||
* [Weave Net Product Overview](https://www.weave.works/products/weave-net)
|
||||
## Example
|
||||
|
||||
Say you have docker running on two hosts, accessible to each other as
|
||||
`$HOST1` and `$HOST2`, and want to deploy an application consisting of
|
||||
two containers, one on each host.
|
||||
|
||||
On `$HOST1` run
|
||||
|
||||
host1$ weave launch
|
||||
host1$ C=$(weave run 10.2.1.1/24 -t -i ubuntu)
|
||||
|
||||
The first line starts the weave router, in a container. This needs to
|
||||
be done once on each host. The required docker image for the weave
|
||||
router container is downloaded automatically. There is also a `weave
|
||||
setup` command for downloading this and other images required for
|
||||
weave operation; this is a strictly optional step which is especially
|
||||
useful for automated installation of weave and ensures that any
|
||||
subsequent weave commands do not encounter delays due to image
|
||||
downloading.
|
||||
|
||||
The second line runs our application container. We give it an IP
|
||||
address and network, in
|
||||
[CIDR notation](http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation).
|
||||
`weave run` invokes `docker run -d` with all the parameters following
|
||||
the IP address and network. So we could be launching any container
|
||||
this way; here we just take a stock ubuntu container and launch a
|
||||
shell in it. There's also a `weave start` command, which invokes
|
||||
`docker start` for starting existing containers.
|
||||
|
||||
If our application consists of more than one container on this host we
|
||||
simply launch them with a variation on that second line.
|
||||
|
||||
The IP addresses and netmasks can be anything you like, but make sure
|
||||
they don't conflict with any IP ranges in use on the hosts or IP
|
||||
addresses of external services the hosts or containers need to connect
|
||||
to. The same IP range must be used everywhere, and the individual IP
|
||||
addresses must, of course, be unique.
|
||||
|
||||
We repeat similar steps on `$HOST2`...
|
||||
|
||||
host2$ weave launch $HOST1
|
||||
host2$ C=$(weave run 10.2.1.2/24 -t -i ubuntu)
|
||||
|
||||
The only difference, apart from the choice of IP address for the
|
||||
application container, is that we tell our weave that it should peer
|
||||
with the weave on `$HOST1` (specified as the IP address or hostname,
|
||||
and optional `:port`, by which `$HOST2` can reach it). NB: if there is
|
||||
a firewall between `$HOST1` and `$HOST2`, you must open the weave port
|
||||
(6783 by default; this can be overriden by setting `WEAVE_PORT`) for
|
||||
TCP and UDP.
|
||||
|
||||
Note that we could instead have told the weave on `$HOST1` to connect to
|
||||
`$HOST2`, or told both about each other. Order does not matter here;
|
||||
weave automatically (re)connects to peers when they become
|
||||
available. Also, we can tell weave to connect to multiple peers by
|
||||
supplying multiple addresses, separated by spaces. And we can
|
||||
[add peers dynamically](http://docs.weave.works/weave/latest_release/features.html#dynamic-topologies).
|
||||
|
||||
Now that we've got everything set up, let's see whether our containers
|
||||
can talk to each other...
|
||||
|
||||
On `$HOST1`...
|
||||
|
||||
host1$ docker attach $C
|
||||
root@28841bd02eff:/# ping -c 1 -q 10.2.1.2
|
||||
PING 10.2.1.2 (10.2.1.2): 48 data bytes
|
||||
--- 10.2.1.2 ping statistics ---
|
||||
1 packets transmitted, 1 packets received, 0% packet loss
|
||||
round-trip min/avg/max/stddev = 1.048/1.048/1.048/0.000 ms
|
||||
|
||||
Similarly, on `$HOST2`...
|
||||
|
||||
host2$ docker attach $C
|
||||
root@f76829496120:/# ping -c 1 -q 10.2.1.1
|
||||
PING 10.2.1.1 (10.2.1.1): 48 data bytes
|
||||
--- 10.2.1.1 ping statistics ---
|
||||
1 packets transmitted, 1 packets received, 0% packet loss
|
||||
round-trip min/avg/max/stddev = 1.034/1.034/1.034/0.000 ms
|
||||
|
||||
So there we have it, two containers on separate hosts happily talking
|
||||
to each other.
|
||||
|
||||
## Find out more
|
||||
|
||||
* [Documentation homepage](http://docs.weave.works/weave/latest_release/)
|
||||
* [Features](http://docs.weave.works/weave/latest_release/features.html)
|
||||
* [Automatic Discovery with WeaveDNS](http://docs.weave.works/weave/latest_release/weavedns.html)
|
||||
* [Troubleshooting](http://docs.weave.works/weave/latest_release/troubleshooting.html)
|
||||
* [Building](http://docs.weave.works/weave/latest_release/building.html)
|
||||
* [How it works](http://docs.weave.works/weave/latest_release/how-it-works.html)
|
||||
|
||||
## Contact Us
|
||||
|
||||
Found a bug, want to suggest a feature, or have a question?
|
||||
[File an issue](https://github.com/weaveworks/weave/issues), or email
|
||||
help@weave.works. When reporting a bug, please include which version of
|
||||
weave you are running, as shown by `weave version`.
|
||||
|
||||
Follow weave on Twitter:
|
||||
[@weaveworks](https://twitter.com/weaveworks).
|
||||
|
||||
Read the Weave blog:
|
||||
[Weaveblog](http://weaveblog.com/).
|
||||
|
||||
IRC:
|
||||
[#weavenetwork](https://botbot.me/freenode/weavenetwork/)
|
||||
@@ -1,17 +0,0 @@
|
||||
# Use of vendored code in Weave Net
|
||||
|
||||
Weave Net is licensed under the [Apache 2.0 license](LICENSE).
|
||||
|
||||
Some vendored code is under different licenses though, all of them ship the
|
||||
entire license text they are under.
|
||||
|
||||
- https://github.com/weaveworks/go-checkpoint
|
||||
can be found in the ./vendor/ directory, is under MPL-2.0.
|
||||
|
||||
- Pulled in by dependencies are
|
||||
https://github.com/hashicorp/golang-lru (MPL-2.0)
|
||||
https://github.com/hashicorp/go-cleanhttp (MPL-2.0)
|
||||
https://github.com/opencontainers/go-digest (docs are under CC by-sa 4.0)
|
||||
|
||||
[One file used in tests](COPYING.LGPL-3) is under LGPL-3, that's why we ship
|
||||
the license text in this repository.
|
||||
Vendored
+23
-18
@@ -1,36 +1,41 @@
|
||||
require File.expand_path(File.join(File.dirname(__FILE__), 'vagrant-common.rb'))
|
||||
VAGRANTFILE_API_VERSION = "2"
|
||||
|
||||
require './vagrant-common.rb'
|
||||
|
||||
vm_ip = "172.16.0.3" # arbitrary private IP
|
||||
|
||||
pkgs = %w(
|
||||
lxc-docker
|
||||
aufs-tools
|
||||
build-essential
|
||||
ethtool
|
||||
iputils-arping
|
||||
libpcap-dev
|
||||
git
|
||||
mercurial
|
||||
bc
|
||||
)
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
|
||||
config.vm.box = VAGRANT_IMAGE
|
||||
config.vm.box = "ubuntu/ubuntu-14.10-amd64"
|
||||
config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/utopic/current/utopic-server-cloudimg-amd64-vagrant-disk1.box"
|
||||
|
||||
config.vm.network "private_network", ip: vm_ip
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
vb.memory = 2048
|
||||
configure_nat_dns(vb)
|
||||
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "off"]
|
||||
vb.customize ["modifyvm", :id, "--natdnsproxy1", "off"]
|
||||
end
|
||||
|
||||
# Disable default Vagrant shared folder, which we don't need:
|
||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
# Keep Weave Net sources' in sync:
|
||||
config.vm.synced_folder ".", "/home/vagrant/src/github.com/weaveworks/weave"
|
||||
# Create a convenience symlink to $HOME/src/github.com/weaveworks/weave
|
||||
config.vm.provision :shell, :inline => 'ln -sf ~vagrant/src/github.com/weaveworks/weave ~vagrant/'
|
||||
|
||||
# Set SSH keys up to be able to run smoke tests straightaway:
|
||||
config.vm.provision "file", source: "~/.vagrant.d/insecure_private_key", destination: "/home/vagrant/src/github.com/weaveworks/weave/test/insecure_private_key"
|
||||
# Grant permissions on sources:
|
||||
config.vm.provision :shell, :inline => 'sudo chown -R vagrant:vagrant ~vagrant/src', :privileged => false
|
||||
install_build_deps config.vm, pkgs
|
||||
install_go_toochain config.vm
|
||||
tweak_user_env config.vm
|
||||
tweak_docker_daemon config.vm
|
||||
cleanup config.vm
|
||||
|
||||
config.vm.provision 'ansible' do |ansible|
|
||||
ansible.playbook = 'tools/config_management/setup_weave-net_dev.yml'
|
||||
ansible.extra_vars = {
|
||||
go_version: GO_VERSION
|
||||
}.merge(ansibleize(get_dependencies_version_from_file_and_env()))
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func (client *Client) DNSDomain() (string, error) {
|
||||
return client.httpVerb("GET", "/domain", nil)
|
||||
}
|
||||
|
||||
func (client *Client) RegisterWithDNS(ID string, fqdn string, ip string) error {
|
||||
data := url.Values{}
|
||||
data.Add("fqdn", fqdn)
|
||||
_, err := client.httpVerb("PUT", fmt.Sprintf("/name/%s/%s", ID, ip), data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *Client) DeregisterWithDNS(ID string, ip string) error {
|
||||
_, err := client.httpVerb("DELETE", fmt.Sprintf("/name/%s/%s", ID, ip), nil)
|
||||
return err
|
||||
}
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Special token used in place of a container identifier when:
|
||||
// - the caller does not know the container ID, or
|
||||
// - the caller is unsure whether IPAM was previously told the container ID.
|
||||
//
|
||||
// While addresses are typically stored under their container IDs, when this
|
||||
// token is used, the address will be stored under its own string, something
|
||||
// which should be kept in mind if the entry needs to be removed at any point.
|
||||
const NoContainerID string = "_"
|
||||
|
||||
func (client *Client) ipamOp(ID string, op string, values url.Values) (*net.IPNet, error) {
|
||||
ip, err := client.httpVerb(op, fmt.Sprintf("/ip/%s", ID), values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseIP(ip)
|
||||
}
|
||||
|
||||
func ipamValues(checkAlive bool) url.Values {
|
||||
values := make(url.Values)
|
||||
if checkAlive {
|
||||
values.Set("check-alive", "true")
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// returns an IP for the ID given, allocating a fresh one if necessary
|
||||
func (client *Client) AllocateIP(ID string, checkAlive bool) (*net.IPNet, error) {
|
||||
return client.ipamOp(ID, "POST", ipamValues(checkAlive))
|
||||
}
|
||||
|
||||
func (client *Client) AllocateIPInSubnet(ID string, subnet *net.IPNet, checkAlive bool) (*net.IPNet, error) {
|
||||
ip, err := client.httpVerb("POST", fmt.Sprintf("/ip/%s/%s", ID, subnet), ipamValues(checkAlive))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseIP(ip)
|
||||
}
|
||||
|
||||
// returns an IP for the ID given, or nil if one has not been
|
||||
// allocated
|
||||
func (client *Client) LookupIP(ID string) (*net.IPNet, error) {
|
||||
return client.ipamOp(ID, "GET", nil)
|
||||
}
|
||||
|
||||
// Claim a specific IP on behalf of the ID
|
||||
func (client *Client) ClaimIP(ID string, cidr *net.IPNet, checkAlive bool) error {
|
||||
_, err := client.httpVerb("PUT", fmt.Sprintf("/ip/%s/%s", ID, cidr), ipamValues(checkAlive))
|
||||
return err
|
||||
}
|
||||
|
||||
// release all IPs owned by an ID
|
||||
func (client *Client) ReleaseIPsFor(ID string) error {
|
||||
_, err := client.httpVerb("DELETE", fmt.Sprintf("/ip/%s", ID), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// release all IP space owned by a peer
|
||||
func (client *Client) RmPeer(peerName string) (string, error) {
|
||||
result, err := client.httpVerb("DELETE", fmt.Sprintf("/peer/%s", peerName), nil)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (client *Client) DefaultSubnet() (*net.IPNet, error) {
|
||||
cidr, err := client.httpVerb("GET", fmt.Sprintf("/ipinfo/defaultsubnet"), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, ipnet, err := net.ParseCIDR(cidr)
|
||||
return ipnet, err
|
||||
}
|
||||
|
||||
func parseIP(body string) (*net.IPNet, error) {
|
||||
ip, ipnet, err := net.ParseCIDR(string(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ipnet.IP = ip
|
||||
return ipnet, nil
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Expose calls the router to assign the given IP addr to the weave bridge.
|
||||
func (client *Client) Expose(ipAddr *net.IPNet) error {
|
||||
_, err := client.httpVerb("POST", fmt.Sprintf("/expose/%s", ipAddr), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReplacePeers replace the current set of peers
|
||||
func (client *Client) ReplacePeers(peers []string) error {
|
||||
_, err := client.httpVerb("POST", "/connect", url.Values{"replace": {"true"}, "peer": peers})
|
||||
return err
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
WeaveHTTPHost = "127.0.0.1"
|
||||
WeaveHTTPPort = 6784
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
baseURL string
|
||||
log Logger
|
||||
}
|
||||
|
||||
func (client *Client) httpVerb(verb string, url string, values url.Values) (string, error) {
|
||||
url = client.baseURL + url
|
||||
client.log.Debugf("weave %s to %s with %v", verb, url, values)
|
||||
var body io.Reader
|
||||
if values != nil {
|
||||
body = strings.NewReader(values.Encode())
|
||||
}
|
||||
req, err := http.NewRequest(verb, url, body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if values != nil {
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
rbody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNoContent {
|
||||
return string(rbody), nil
|
||||
}
|
||||
return "", errors.New(resp.Status + ": " + string(rbody))
|
||||
}
|
||||
|
||||
func NewClient(addr string, log Logger) *Client {
|
||||
host := WeaveHTTPHost
|
||||
port := fmt.Sprintf("%d", WeaveHTTPPort)
|
||||
switch parts := strings.Split(addr, ":"); len(parts) {
|
||||
case 0:
|
||||
case 1:
|
||||
if parts[0] != "" {
|
||||
host = parts[0]
|
||||
}
|
||||
case 2:
|
||||
if parts[0] != "" {
|
||||
host = parts[0]
|
||||
}
|
||||
if parts[1] != "" {
|
||||
port = parts[1]
|
||||
}
|
||||
default:
|
||||
return &Client{baseURL: fmt.Sprintf("http://%s", addr), log: log}
|
||||
}
|
||||
return &Client{baseURL: fmt.Sprintf("http://%s:%s", host, port), log: log}
|
||||
}
|
||||
|
||||
func (client *Client) Connect(remote string) error {
|
||||
_, err := client.httpVerb("POST", "/connect", url.Values{"peer": {remote}})
|
||||
return err
|
||||
}
|
||||
|
||||
// IsReady returns true if the API server is up and running
|
||||
// (note it returns StatusServiceUnavailable until all parts are ready,
|
||||
// but callers of this function are expected to be ok with a partial service)
|
||||
func (client *Client) IsReady() bool {
|
||||
resp, err := http.Get(client.baseURL + "/status")
|
||||
return err == nil &&
|
||||
(resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusServiceUnavailable)
|
||||
}
|
||||
|
||||
// WaitAPIServer waits until the API server is ready to serve.
|
||||
func (client *Client) WaitAPIServer(n int) {
|
||||
for i := 0; i < n && !client.IsReady(); i++ {
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
Infof(string, ...interface{})
|
||||
Debugf(string, ...interface{})
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
docker login -u $DOCKER_USER -p $DOCKER_PASS
|
||||
make clean-bin
|
||||
# CircleCI doesn't let us install the qemu module to do ARM docker builds
|
||||
make publish ALL_ARCHITECTURES=amd64 UPDATE_LATEST=latest-only SUDO= BUILD_IN_CONTAINER=false
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$SRCDIR/bin/circle-env"
|
||||
cd "$SRCDIR/test"
|
||||
./run-integration-tests.sh destroy
|
||||
@@ -1,11 +0,0 @@
|
||||
# Set environment variables here because CircleCI can't expand $FOO in 'env' section
|
||||
|
||||
export PATH="$PATH:$HOME/.local/bin:/opt/google-cloud-sdk/bin"
|
||||
export CLOUDSDK_CORE_DISABLE_PROMPTS=1
|
||||
# Base name of VMs for integration tests:
|
||||
export NAME=test-$CIRCLE_BUILD_NUM-$CIRCLE_NODE_INDEX
|
||||
export TEST_VMS_PROV_AND_CONF_LOCK_FILE=$CIRCLE_ARTIFACTS/test_vms_provisioned_and_configured.txt
|
||||
export TEST_VMS_READY_LOCK_FILE=$CIRCLE_ARTIFACTS/test_vms_ready.txt
|
||||
# Google Cloud Platorm images for integration tests:
|
||||
export USE_IMAGE=1
|
||||
export DISK_NAME_PREFIX=test-$CIRCLE_BUILD_NUM-0
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Signal failures in lock file, in order to fail fast:
|
||||
function signal_failure() {
|
||||
echo "KO" >"$TEST_VMS_READY_LOCK_FILE"
|
||||
exit 1
|
||||
}
|
||||
trap signal_failure ERR
|
||||
|
||||
source "$(dirname "$0")/wait_for.sh"
|
||||
|
||||
[ -n "$SECRET_KEY" ] || {
|
||||
echo "Cannot run smoke tests: no secret key"
|
||||
exit 1
|
||||
}
|
||||
|
||||
source "$SRCDIR/bin/circle-env"
|
||||
|
||||
# Provisioning from image typically take 90 seconds,
|
||||
# but if we are creating a GCP image, it can take more than 10 minutes:
|
||||
wait_for 900 "$TEST_VMS_PROV_AND_CONF_LOCK_FILE"
|
||||
|
||||
# Upload built binaries to testing VMs:
|
||||
cd "$SRCDIR/test" # Ensures we generate Terraform state files in the right folder, for later use by integration tests.
|
||||
./run-integration-tests.sh setup
|
||||
echo "OK" >"$TEST_VMS_READY_LOCK_FILE"
|
||||
echo "Test VMs now ready. $(date)."
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$(dirname "$0")/wait_for.sh"
|
||||
|
||||
[ -n "$SECRET_KEY" ] || {
|
||||
echo "Cannot run smoke tests: no secret key"
|
||||
exit 1
|
||||
}
|
||||
|
||||
source "$SRCDIR/bin/circle-env"
|
||||
|
||||
# Provisioning from image typically take 90 seconds,
|
||||
# but if we are creating a GCP image, it can take more than 10 minutes:
|
||||
wait_for 900 "$TEST_VMS_READY_LOCK_FILE"
|
||||
|
||||
# Run integration tests:
|
||||
export COVERAGE=true
|
||||
export WEAVE_NET_SANITY_CHECKS_FILES="$CIRCLE_ARTIFACTS/weave_net_sanity_check_*.log"
|
||||
cd "$SRCDIR/test" # Ensures we generate code coverage files in the right folder, and use the Terraform state of previously provisionned VMs.
|
||||
./run-integration-tests.sh test
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
# This scipt lints go files for common errors.
|
||||
#
|
||||
# Its runs gofmt and go vet, and optionally golint and
|
||||
# gocyclo, if they are installed.
|
||||
#
|
||||
# With no arguments, it lints the current files staged
|
||||
# for git commit. Or you can pass it explicit filenames
|
||||
# (or directories) and it will lint them.
|
||||
#
|
||||
# To use this script automatically, run:
|
||||
# ln -s ../../bin/lint .git/hooks/pre-commit
|
||||
|
||||
set -eu
|
||||
|
||||
function lint_go {
|
||||
filename="$1"
|
||||
result=0
|
||||
|
||||
if [ -n "$(gofmt -l "${filename}")" ]; then
|
||||
result=1
|
||||
echo "${filename}: run gofmt -w ${filename}!"
|
||||
fi
|
||||
|
||||
go tool vet "${filename}" || result=$?
|
||||
|
||||
# golint is completely optional. If you don't like it
|
||||
# don't have it installed.
|
||||
if type golint >/dev/null 2>&1; then
|
||||
# golint doesn't set an exit code it seems
|
||||
lintoutput=$(golint "${filename}")
|
||||
if [ "$lintoutput" != "" ]; then
|
||||
result=1
|
||||
echo "$lintoutput"
|
||||
fi
|
||||
fi
|
||||
|
||||
# gocyclo is completely optional. If you don't like it
|
||||
# don't have it installed. Also never blocks a commit,
|
||||
# it just warns.
|
||||
if type gocyclo >/dev/null 2>&1; then
|
||||
gocyclo -over 25 "${filename}" | while read line; do
|
||||
echo "${filename}": higher than 25 cyclomatic complexity - "${line}"
|
||||
done
|
||||
fi
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
function lint {
|
||||
filename="$1"
|
||||
ext="${filename##*\.}"
|
||||
|
||||
# Don't lint this script
|
||||
if [ "${filename}" = "bin/lint" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
case "$ext" in
|
||||
go) lint_go "${filename}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function lint_files {
|
||||
lint_result=0
|
||||
while read filename; do
|
||||
lint "${filename}" || lint_result=1
|
||||
done
|
||||
exit $lint_result
|
||||
}
|
||||
|
||||
function list_files {
|
||||
if [ $# -gt 0 ]; then
|
||||
find "$@" -type f
|
||||
else
|
||||
git diff --cached --name-only
|
||||
fi
|
||||
}
|
||||
|
||||
list_files "$@" | lint_files
|
||||
+15
-29
@@ -14,8 +14,7 @@ set -e
|
||||
START=${FIRST_WEAVE:-1}
|
||||
COUNT=${NUM_WEAVES:-28}
|
||||
FINISH=$((START+COUNT-1))
|
||||
|
||||
SUBNET=192.168.7
|
||||
PORT=6783
|
||||
|
||||
weavedir=$(dirname $0)/..
|
||||
|
||||
@@ -24,45 +23,32 @@ random_mac() {
|
||||
# In the first byte of the MAC, the 'multicast' bit should be
|
||||
# clear and 'locally administered' bit should be set. All other
|
||||
# bits should be random.
|
||||
od -txC -An -N6 /dev/urandom | ( read a b c d e f && printf "%02x:$b:$c:$d:$e:$f" $((0x$a & ~1 | 2)) )
|
||||
}
|
||||
|
||||
weave() {
|
||||
i=$1
|
||||
shift
|
||||
WEAVE_CONTAINER_NAME=weave$i WEAVE_HTTP_ADDR=$SUBNET.$i:6784 $weavedir/weave "$@"
|
||||
od -txC -An -N6 /dev/urandom | sed 's|^ ||;s| |:|;s|[0-3]:|2:|;s|[4-7]:|6:|;s|[89ab]:|a:|;s|[c-f]:|e:|;s| |:|g'
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
launch)
|
||||
shift 1
|
||||
echo $(date -R) launching
|
||||
ip link show multiweave >/dev/null 2>&1 || sudo ip link add name multiweave type bridge
|
||||
for i in $(seq $START $FINISH); do
|
||||
IP=$SUBNET.$i
|
||||
sudo ip addr add $IP/24 dev multiweave
|
||||
weave $i launch --datapath '' --iface '' --no-dns --host $IP \
|
||||
--name $(random_mac) --nickname weave$i "$@"
|
||||
WEAVE_CONTAINER_NAME=weave$i WEAVE_PORT=$((5000+i)) $weavedir/weave launch -iface '' -name $(random_mac) -nickname weave$i -port $PORT "$@"
|
||||
done
|
||||
echo $(date -R) connecting
|
||||
[ $START -gt 1 ] || START=2
|
||||
if [ $START -ne 1 ] ; then
|
||||
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$((START-1)))
|
||||
PREV="${status#true }"
|
||||
fi
|
||||
for i in $(seq $START $FINISH); do
|
||||
TO=$SUBNET.$((i-1))
|
||||
echo connecting weave$i to $TO
|
||||
weave $i connect $TO
|
||||
if [ -n "$PREV" ] ; then
|
||||
echo connecting weave$i to $PREV
|
||||
WEAVE_CONTAINER_NAME=weave$i $weavedir/weave connect $PREV
|
||||
fi
|
||||
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$i)
|
||||
PREV="${status#true }"
|
||||
done
|
||||
echo $(date -R) done
|
||||
;;
|
||||
stop)
|
||||
for i in $(seq $FINISH -1 $START); do
|
||||
IP=$SUBNET.$i
|
||||
for i in $(seq $START $FINISH); do
|
||||
echo Stopping weave$i
|
||||
weave $i stop
|
||||
sudo ip addr del $IP/24 dev multiweave || true
|
||||
WEAVE_CONTAINER_NAME=weave$i $weavedir/weave stop
|
||||
done
|
||||
[ -z "$(ip -4 -o addr show multiweave)" ] && sudo ip link del multiweave
|
||||
;;
|
||||
*)
|
||||
weave "$@"
|
||||
;;
|
||||
esac
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Signal failures in lock file, in order to fail fast:
|
||||
function signal_failure() {
|
||||
echo "KO" >"$TEST_VMS_PROV_AND_CONF_LOCK_FILE"
|
||||
exit 1
|
||||
}
|
||||
trap signal_failure ERR
|
||||
|
||||
function install_terraform() {
|
||||
curl -fsS https://releases.hashicorp.com/terraform/0.8.5/terraform_0.8.5_linux_amd64.zip | gunzip >terraform && chmod +x terraform && sudo mv terraform /usr/bin
|
||||
}
|
||||
|
||||
function install_ansible() {
|
||||
sudo apt-get update || true
|
||||
sudo apt-get install -qq -y python-pip python-dev libffi-dev libssl-dev \
|
||||
&& pip install --user -U setuptools cffi \
|
||||
&& pip install --user ansible
|
||||
}
|
||||
|
||||
[ -n "$SECRET_KEY" ] || {
|
||||
echo "Cannot run smoke tests: no secret key"
|
||||
exit 1
|
||||
}
|
||||
|
||||
source "$SRCDIR/bin/circle-env"
|
||||
|
||||
install_terraform
|
||||
install_ansible
|
||||
|
||||
# Only attempt to create GCP image in first container, wait for it to be created otherwise:
|
||||
[ "$CIRCLE_NODE_INDEX" != "0" ] && export CREATE_IMAGE=0
|
||||
|
||||
# Provision and configure testing VMs:
|
||||
cd "$SRCDIR/test" # Ensures we generate Terraform state files in the right folder, for later use by integration tests.
|
||||
./run-integration-tests.sh configure
|
||||
echo "OK" >"$TEST_VMS_PROV_AND_CONF_LOCK_FILE"
|
||||
echo "Test VMs now provisioned and configured. $(date)."
|
||||
+67
-226
@@ -10,53 +10,15 @@ RELEASE_DESCRIPTION=${RELEASE_DESCRIPTION:-"Weaving Containers into Applications
|
||||
|
||||
PWD=`pwd`
|
||||
|
||||
infer_release_type() {
|
||||
if echo $1 | grep -qE '^v[0-9]+\.[0-9]+\.0+$' ; then
|
||||
echo MAINLINE
|
||||
elif echo $1 | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$' ; then
|
||||
echo BRANCH
|
||||
else
|
||||
echo PRERELEASE
|
||||
fi
|
||||
}
|
||||
|
||||
setup() {
|
||||
# Ensure we have exactly one annotated tag pointing at HEAD
|
||||
HEAD_TAGS=$(git tag --points-at HEAD)
|
||||
case $(echo $HEAD_TAGS | wc -w) in
|
||||
1)
|
||||
if [ $HEAD_TAGS != "latest_release" ] ; then
|
||||
LATEST_TAG=$HEAD_TAGS
|
||||
else
|
||||
echo "Cannot determine version - latest_release points at HEAD" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
0)
|
||||
echo "Cannot determine version - no tags point at HEAD" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "Cannot determine version - multiple tags point at HEAD:" >&2
|
||||
for TAG in $HEAD_TAGS ; do
|
||||
echo -e "\t$TAG" >&2
|
||||
done
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
RELEASE_TYPE=$(infer_release_type $LATEST_TAG)
|
||||
echo "== Inferred release type $RELEASE_TYPE from tag $LATEST_TAG"
|
||||
|
||||
LATEST_TAG_SHA=$(git rev-parse $LATEST_TAG)
|
||||
LATEST_TAG_COMMIT_SHA=$(git rev-list -1 $LATEST_TAG)
|
||||
LATEST_RELEASE_SHA=$(git rev-parse latest_release)
|
||||
LATEST_RELEASE_COMMIT_SHA=$(git rev-list -1 latest_release)
|
||||
if [ "$RELEASE_TYPE" != 'PRERELEASE' ] ; then
|
||||
VERSION=${LATEST_TAG#v}
|
||||
else
|
||||
VERSION=${LATEST_TAG}
|
||||
## Get the new version number from the most recent tag
|
||||
if ! LATEST_TAG=$(git describe --abbrev=0 --match='v*' 2>/dev/null) ; then
|
||||
echo "Could not find an annotated 'v*' version tag." >&2
|
||||
exit 1
|
||||
fi
|
||||
LATEST_TAG_SHA=$(git rev-parse $LATEST_TAG)
|
||||
LATEST_RELEASE_SHA=$(git rev-parse latest_release)
|
||||
VERSION=${LATEST_TAG#v}
|
||||
# NB does not check that this tag is on master
|
||||
RELEASE_DIR=./releases/$LATEST_TAG
|
||||
}
|
||||
@@ -77,8 +39,8 @@ build() {
|
||||
cd $RELEASE_DIR
|
||||
|
||||
## Check that the top changelog entry is this version
|
||||
if ! latest_changelog=$(grep -oP '(?<=^## Release ).*' -m1 ./CHANGELOG.md) || \
|
||||
! [ `echo "$latest_changelog" = "$VERSION"` ]; then
|
||||
if ! latest_changelog=$(grep -o "## Release [0-9].*" -m1 ./CHANGELOG.md) || \
|
||||
! [ `echo "$latest_changelog" | grep -o '[0-9][^ ]*'` == "$VERSION" ]; then
|
||||
echo -e "\u2757 Latest changelog entry \"$latest_changelog\" does not match the release version $VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -88,11 +50,7 @@ build() {
|
||||
|
||||
## Inject the version numbers and build the distributables
|
||||
## (library versions?)
|
||||
sed -i.bak "/SCRIPT_VERSION=/ c\SCRIPT_VERSION=\"$VERSION\"" ./weave
|
||||
sed -i.bak -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset-k8s-1.6.yaml
|
||||
sed -i.bak -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset-k8s-1.7.yaml
|
||||
sed -i.bak -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset-k8s-1.8.yaml
|
||||
sed -i.bak -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset-k8s-1.9.yaml
|
||||
sed -i "/SCRIPT_VERSION=/ c\SCRIPT_VERSION=\"$VERSION\"" ./weave
|
||||
make SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER
|
||||
|
||||
if make tests; then
|
||||
@@ -103,7 +61,7 @@ build() {
|
||||
fi
|
||||
|
||||
## Run tests with the distributables, including version check
|
||||
v=$(./prog/weaver/weaver --version | grep -oP '(?<=^weave router ).*')
|
||||
v=$(./weaver/weaver --version | grep -o '[0-9].*')
|
||||
if ! [ "$v" == "$VERSION" ]; then
|
||||
echo "Version of distributable "$v" does not match release version $VERSION" >&2
|
||||
exit 1
|
||||
@@ -113,13 +71,24 @@ build() {
|
||||
echo '** Release artefacts in' $RELEASE_DIR
|
||||
}
|
||||
|
||||
draft() {
|
||||
publish() {
|
||||
setup
|
||||
|
||||
cd $PWD/$RELEASE_DIR
|
||||
|
||||
echo "== Sanity checks"
|
||||
|
||||
if ! which github-release >/dev/null; then
|
||||
echo "Please install git-release:" >&2
|
||||
echo -e "\tgo get github.com/aktau/github-release" >&2
|
||||
echo "and create a git token per https://github.com/aktau/github-release" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ $(git rev-list -1 $LATEST_TAG) == $(git rev-list -1 latest_release) ]; then
|
||||
echo -e "\u2757 The tag latest_release does not point to the same commit as $LATEST_TAG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Check that the tag exists by looking at github
|
||||
if ! curl -sSf https://api.github.com/repos/$GITHUB_USER/weave/git/tags/$LATEST_TAG_SHA >/dev/null 2>&1; then
|
||||
echo -e "\u2757 Tag $LATEST_TAG is not on GitHub, or is not the same as the local tag" >&2
|
||||
@@ -129,9 +98,19 @@ draft() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Check that the 'latest_release' tag exists by looking at github
|
||||
if ! curl -sSf https://api.github.com/repos/$GITHUB_USER/weave/git/tags/$LATEST_RELEASE_SHA >/dev/null 2>&1; then
|
||||
echo -e "\u2757 Tag latest_release is not on GitHub, or is not the same as the local tag" >&2
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/tags" >&2
|
||||
echo "You may need to" >&2
|
||||
echo -e "\tgit tag -af latest_release"
|
||||
echo -e "\tgit push -f git@github.com:$GITHUB_USER/weave latest_release"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "\u2713 Tag $LATEST_TAG exists in GitHub repo $GITHUB_USER/weave"
|
||||
|
||||
## Check that the version does not already exist by looking at github
|
||||
## Check that the version doesn't already exist by looking at github
|
||||
## releases
|
||||
if github-release info --user $GITHUB_USER --repo weave --tag $LATEST_TAG >/dev/null 2>&1; then
|
||||
echo -e "\u2757 Release $LATEST_TAG already exists on GitHub" >&2
|
||||
@@ -141,13 +120,13 @@ draft() {
|
||||
|
||||
echo '** Sanity checks OK for publishing tag' $LATEST_TAG as $DOCKERHUB_USER/weave:$VERSION
|
||||
|
||||
RELEASE_ARGS="--draft"
|
||||
if [ "$RELEASE_TYPE" = 'PRERELEASE' ] ; then
|
||||
RELEASE_ARGS="$RELEASE_ARGS --pre-release"
|
||||
fi
|
||||
echo "== Tagging and publishing images on docker hub as user $DOCKERHUB_USER"
|
||||
make SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER publish
|
||||
|
||||
echo "== Creating GitHub release $RELEASE_ARGS $RELEASE_NAME $VERSION"
|
||||
github-release release $RELEASE_ARGS \
|
||||
echo "== Creating GitHub release $RELEASE_NAME $VERSION"
|
||||
# This cannot be done as a draft because of a bug in
|
||||
# github-release: https://github.com/aktau/github-release/issues/7
|
||||
github-release release \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG \
|
||||
@@ -161,190 +140,52 @@ draft() {
|
||||
--name "weave" \
|
||||
--file "./weave"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG \
|
||||
--name "weave-daemonset-k8s-1.6.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.6.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG \
|
||||
--name "weave-daemonset-k8s-1.7.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.7.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG \
|
||||
--name "weave-daemonset-k8s-1.8.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.8.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG \
|
||||
--name "weave-daemonset-k8s-1.9.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.9.yaml"
|
||||
|
||||
echo "** Draft $TYPE $RELEASE_NAME $VERSION created at"
|
||||
echo "** Published release $RELEASE_NAME $VERSION"
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/$LATEST_TAG"
|
||||
}
|
||||
|
||||
publish() {
|
||||
setup
|
||||
cd $PWD/$RELEASE_DIR
|
||||
|
||||
IS_MAINLINE=false
|
||||
if [ "$RELEASE_TYPE" = 'MAINLINE' ] ; then
|
||||
IS_MAINLINE=true
|
||||
if github-release info --user $GITHUB_USER --repo weave \
|
||||
--tag latest_release >/dev/null 2>&1; then
|
||||
github-release delete \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release
|
||||
fi
|
||||
|
||||
if [ "$RELEASE_TYPE" = 'PRERELEASE' ] ; then
|
||||
echo "== Building and pushing plugin"
|
||||
make UPDATE_LATEST=false SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER plugin_publish
|
||||
echo "** Plugin built and pushed"
|
||||
github-release release \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "$RELEASE_NAME latest ($VERSION)" \
|
||||
--description "$RELEASE_DESCRIPTION"
|
||||
|
||||
echo "== Tagging and pushing images on docker hub as user $DOCKERHUB_USER"
|
||||
make UPDATE_LATEST=$IS_MAINLINE SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER publish
|
||||
echo "** Docker images tagged and pushed"
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave" \
|
||||
--file "./weave"
|
||||
|
||||
echo "== Publishing pre-release on GitHub"
|
||||
|
||||
github-release publish \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG
|
||||
|
||||
echo "** Pre-release $RELEASE_NAME $VERSION published at"
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/$LATEST_TAG"
|
||||
else
|
||||
echo "== Sanity checks"
|
||||
if ! [ "$LATEST_TAG_COMMIT_SHA" == "$LATEST_RELEASE_COMMIT_SHA" ] ; then
|
||||
echo -e "\u2757 The tag latest_release does not point to the same commit as $LATEST_TAG" >&2
|
||||
echo "You may need to" >&2
|
||||
echo -e "\tgit tag -af latest_release $LATEST_TAG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Check that the 'latest_release' tag exists by looking at github
|
||||
if ! curl -sSf https://api.github.com/repos/$GITHUB_USER/weave/git/tags/$LATEST_RELEASE_SHA >/dev/null 2>&1; then
|
||||
echo -e "\u2757 Tag latest_release is not on GitHub, or is not the same as the local tag" >&2
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/tags" >&2
|
||||
echo "You may need to" >&2
|
||||
echo -e "\tgit push -f git@github.com:$GITHUB_USER/weave latest_release" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo '** Sanity checks OK for publishing tag' $LATEST_TAG as $DOCKERHUB_USER/weave:$VERSION
|
||||
|
||||
echo "== Building and pushing plugin"
|
||||
make UPDATE_LATEST=true SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER plugin_publish
|
||||
echo "** Plugin built and pushed"
|
||||
|
||||
echo "== Tagging and pushing images on docker hub as user $DOCKERHUB_USER"
|
||||
make PUBLISH_WEAVEDB=$IS_MAINLINE UPDATE_LATEST=$IS_MAINLINE SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER publish
|
||||
echo "** Docker images tagged and pushed"
|
||||
|
||||
echo "== Publishing release on GitHub"
|
||||
|
||||
github-release publish \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG
|
||||
|
||||
if github-release info --user $GITHUB_USER --repo weave \
|
||||
--tag latest_release >/dev/null 2>&1; then
|
||||
github-release delete \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release
|
||||
fi
|
||||
|
||||
github-release release \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "$RELEASE_NAME latest ($VERSION)" \
|
||||
--description "[Release Notes](https://github.com/$GITHUB_USER/weave/releases/$LATEST_TAG)"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave" \
|
||||
--file "./weave"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave-daemonset-k8s-1.6.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.6.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave-daemonset-k8s-1.7.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.7.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave-daemonset-k8s-1.8.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.8.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave-daemonset-k8s-1.9.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.9.yaml"
|
||||
|
||||
echo "** Release $RELEASE_NAME $VERSION published at"
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/$LATEST_TAG"
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/latest_release"
|
||||
fi
|
||||
echo "** Published $RELEASE_NAME as latest_release at"
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/latest_release"
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Usage:"
|
||||
echo -e "\t./bin/release build"
|
||||
echo "-- Build artefacts for the latest version tag"
|
||||
echo -e "\t./bin/release draft"
|
||||
echo "-- Create draft release with artefacts in GitHub"
|
||||
echo -e "\t./bin/release publish"
|
||||
echo "-- Publish the GitHub release and update DockerHub"
|
||||
exit 1
|
||||
echo "-- Publish the artefacts for the latest tag to GitHub and DockerHub"
|
||||
}
|
||||
|
||||
# Ensure required tooling is installed
|
||||
if ! which github-release >/dev/null; then
|
||||
echo "Please install git-release:" >&2
|
||||
echo -e "\tgo get github.com/weaveworks/github-release" >&2
|
||||
echo "and create a git token per https://github.com/weaveworks/github-release" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
[ $# -eq 0 ] && usage
|
||||
COMMAND=$1
|
||||
shift
|
||||
|
||||
case "$COMMAND" in
|
||||
case "$1" in
|
||||
build)
|
||||
build "$@"
|
||||
;;
|
||||
draft)
|
||||
draft "$@"
|
||||
build
|
||||
;;
|
||||
publish)
|
||||
publish "$@"
|
||||
publish
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command \"$COMMAND\""
|
||||
echo "Unknown command \"$1\""
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/bin/sh
|
||||
|
||||
## This is the shape of unencrypted `do-setup-ci-secrets`
|
||||
##
|
||||
## cat > /tmp/boto_gsutil_cert.pem << EOF
|
||||
## -----BEGIN PRIVATE KEY-----
|
||||
## <...>
|
||||
## -----END PRIVATE KEY-----
|
||||
## EOF
|
||||
##
|
||||
## cat > ~/.boto << EOF
|
||||
## [Credentials]
|
||||
## gs_service_client_id = <...>
|
||||
## gs_service_key_file = /tmp/boto_gsutil_cert.pem
|
||||
##
|
||||
## [Boto]
|
||||
## https_validate_certificates = True
|
||||
##
|
||||
## [GSUtil]
|
||||
## content_language = en
|
||||
## default_api_version = 2
|
||||
## default_project_id = <...>
|
||||
## EOF
|
||||
|
||||
openssl base64 -d << EOF \
|
||||
| openssl aes-256-cbc -d -K $1 -iv $2 \
|
||||
-out bin/do-setup-ci-secrets
|
||||
mOYNzNsAdwva0LbB2YFMY9VgL0SITFu1bPrbOujyLtOrBNjmOYbzR5d/sG+ARMFr
|
||||
7NZ5OKcvMbItllz0n7TtNkzkCOMztMC870Wcx3nGJzbBephdcYwos4tBlp5bihTU
|
||||
AXgxLUiDT1broYAFKnbE1aZC/XyNxuLTN+nXJWIWeijaXTgvhkEEm3tab+z7ioCb
|
||||
tlg71W1BrUuocctAJAuVwH/uTZAaXaJGJj1s2S7F2DrFnG6sFlrp8rnCucPLWKnC
|
||||
4IwJFC5saR5gxK6wECffwmwbpeXVOoQ58OgzxoZTEyAr9nhVbaBt5UZz3tfHlx8W
|
||||
Rn5jHehACw5kg5eMtGPqMEksJ63LX4h3mfSTLJXJHEmOrzCRpFrigVrsbA408U2u
|
||||
8jJTHJmKrGfwBh5ZFzBg6NuV4VplOp+ZquJLgegv2WH/iu/ciopXqB2YDCdx5ZBi
|
||||
nHV2Puth7kjS4EMCKUGfeqpesjL9Djb8cygzv/J3ZBmLjNH9dqlSXWy2DcFZA1Iy
|
||||
pFTTHkVU3w5sNz3nYOuT2UPQvULBW0FUndDKX6olyVa0mlxPwGBP9N3yGg918/rw
|
||||
7rNV/r54lA334F5QMhDrVxar6sl9wfLA+Z+8qemBbNFlLog1uQGhuwWK2nFu1tRM
|
||||
9uyvQIq0oq5NHKpogWc7NEuk7CPe1JyfRIcbMVj8+ilmKjUPB2HPC43IDmKt9/56
|
||||
rY3IXAcxZ/ClGfGpeNHXqop9T/WhfaowponOZFcdGSUddG0OswqkTE4KIv71mgTF
|
||||
jXooMkTQBOG+gBnCe+8bCRdRCG3anxHX74za4dGD58cOJtl6DYWnjWuR91NWnxdT
|
||||
1Zr3iUe41txg9hCNY9LHo2QHGLPxe6pmNnSClQ1mcFhuY6NF8RmCX3HAGKJQkxJb
|
||||
vpLGbXTXjAIFgIYgLNgKP2RLvCif/LmSWgkvE3ZU9GXtCUbm6xgJodwOqXiI9uuO
|
||||
4H4+/vpGHnSFENiclkgMFrLG4yiMu8ArgmnCBzg9E2AoOX5zCrh/2ZRTU8Dk4mt7
|
||||
jUd5SKQLsjB9QtB+y3tRO7zY9RDvR6iBOPRsjx6t8EJMCfPb06KchfMJw+VGxvMM
|
||||
48jtmepREFnhd/8XDRGnw5bznjRuwohGik8BToltjIviMkkpifKWaLwlaOiDXaHh
|
||||
VBFNXnUKvOBwRMU+PZqv4+DP2qff1gSXnGKqA5GIURYrK9P7TqHHNTnmjBdfJLzc
|
||||
0x+W1DTiYhLT2vrLrkEpZ99o6qrg+lwvjat6IsJF8xoQEo4pLD/ZRPJ0WzoDyctT
|
||||
oWZ5rMsN1TzTvJrBrTAWIJyhIzZcuNwChMrU6qogMr0fd6TSxWhWn0sOJ5y8AaTR
|
||||
ej4JHx0qroS/9JPmgFYU61pX3c0ypdef/sSHWG23mcFzLTgwe2d4ty9VakypZy3T
|
||||
sXCl6EHVVXlUFWhY4zQCjdZpYJv/rtoKL1BL4fPE2NV5WTjCL2GNvaO5SfHR7pA0
|
||||
yQJR/iU+cS2n8UXsWNjyV+/qEn6+QXuyVHGLbxyH8EeqlliAbyHsc2rzVI8Xctzo
|
||||
W5DuIqxwHAkNNurhmx27ktIIQ6HhaCp028SWwvLttkbBBgF4v1mmd4u/2bKbKjUd
|
||||
dOaetaIUx1hU9OAeaePvVkeTDcc3mlFtPARCS9Fqj16hgJQ43onZyC0H1K2WAxmU
|
||||
GsmzjCHlTNquzFTuuQihYGZKfsWIh9z6HEOkvNJ3VEQ=
|
||||
EOF
|
||||
|
||||
exec sh bin/do-setup-ci-secrets
|
||||
Executable
+115
@@ -0,0 +1,115 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
# openssl enc -in do-setup-circleci-secrets.orig -out setup-circleci-secrets.orig -e -aes256 -pass stdin
|
||||
# openssl base64 < setup-circleci-secrets.orig
|
||||
|
||||
openssl base64 -d << EOF \
|
||||
| openssl enc \
|
||||
-out bin/do-setup-circleci-secrets \
|
||||
-d -aes256 -pass pass:$1
|
||||
U2FsdGVkX1/3QHnDgpZn0srp+GiEQBTlag0CBdsw6Bp6iaIKy6Jy8gFshrsmvl3+
|
||||
c+7hn1nqsBVpzVH0S5QA3Ex5nx9rl7/dcGoogsySFcwMmBFcauT0UTMK1bvX0Hqx
|
||||
QrshYSndUkww72KA7EsSnjR1BBJWm3AhCDOTIcnO6nQhig1Fc+SBEj5FGS5xTQ5Z
|
||||
/8hDsKf54Ms49vOO6QeE5w5ndZ+k7IvVJfq9Evhb7htD20RntDhR86foAwMnLV7R
|
||||
Wh9qAqI3gnRq0GmbeRKaHmB7pcLq9f0OdScqM/Tn1D+q0556fqJjBziXpZiGbxVs
|
||||
DxEp8zDJ8yNDAi0Wb2NXjoA79SXJ8Q7iTieaST4y2OmXQpY+Vw8RVDXxi6ERyVnH
|
||||
41OeMzyLm0qvCoMl32pe1sSG5DRCUiAm7wOtDTVvNDRqmGJYUgwwexyayecJI1wQ
|
||||
LlhYLJavUvw3UGDZOld8BaMQhfOKBVCdFrjmA4HuVxmOf3029qkgzOCSZiQVNqs+
|
||||
fvM6J05zBuk+GPy8YInvRkzfxum/x00vmUyGjMs/lHDFfrmYDFK3ux41EoVqvWLG
|
||||
S5uYxdZJFdR3Xsrs+sMr51XNBQGMmz6KoEEzFYyHs5fZgtdpRdPAuQS1m6FqgZyi
|
||||
wSz6+EKsALtmGorEp+CF8JN5YzKJCrKzNkg0fILBqsqmJZGP64lmX69950lRLNGM
|
||||
/l3KVR2zH31b4AnnzTcvldVSCKDXjaMw3JA2Ce/MK6XhOWOcpEv4oDt/62lbhUKP
|
||||
QUQTPbfpQRvVfBcoIrfAm3ghi/CZc2TDVEuD7DhasLTZD3r7LoK261BWC9O5Wubi
|
||||
vlXuFIF+uyrpTDf6EJKE/+XywtArppffMP6RkHu1Iqnd8JtsqPaIZsmMmZbjgb6j
|
||||
BX4ZLb5U9R7jYoSgIke4+u6geFcrMQ+Lny4r7MxXaS0Y1UJ6Jo1bwpYw1TBIobZc
|
||||
eQ30+xhbSeRC9vEw1NXI6qM2Ai2gIHtAZvEiBkBBXTG5Ld7N/mBAmDIJEzVRxh6F
|
||||
jRkB3dzESF6eeTBm61OQepwV3aW6lslMaf26drPGv2W2q+K/WOpQOejfggtHUgVs
|
||||
9SWqtn7hHYSpIASe1Ihi14HAWv3kPhQien0zY1DLKJWf4ilbRaICn1QSJjNuKwtl
|
||||
A2ZwEw7FFddFcsF4okHPkxEoCre3shq0DEw9UA1gnwEaEF1Q+i9cvLtQg56zK3+R
|
||||
MfTzFUv2K1z3j1s5tC6Z6LzL8j682EFOll7MkihqiIWh+po1zcNs6Ln0SkjB3v9T
|
||||
OFRYVJis9GqXYO9PUW1mI69udR4nXbKlBl5tWNyAxn9hufJHLSAmbGMwActKLRM5
|
||||
kROapKrN1Ra/sHUmhoSSDUK9AXeYKkqPXVMbhly9xC1Kya4hFaFokjRQIMLPmXdQ
|
||||
lP46RL3HOq0v1BkXg05poSwDm+BtitZkcIhOzcmeucUqn1whsz39y3VoOhJqQu2j
|
||||
0dUcj19Fue1CDZfwzACb9mLZr643h0yCIHQaVT7is/yiN2vEt0S0zynvOA1D+Xgy
|
||||
4bFdjqed9DgBShLwF/lh4/hur5iZPpVr9A8rt3Vq0k5A1tdjsZpXP3qv5jLc7lJv
|
||||
cAQxdVLEuv8WPwwTV/EQsoH7lpoEdjktw/gu0v5PkUgR/WBxEqJq4liTekViQ+8X
|
||||
BAmdm/FGWeIxSSEm0a8I+muiBpIuKjuQoJit1f0k+lLUohoVDqpDDfeYHh1TxXHE
|
||||
xSKAKNmFSc2XOr5Zg2mOInxeH7WoxH6iylXKADBgEgAQ4C55DoAPRjFNtGGz2iJl
|
||||
WkzhQOfgsDss1VxpPZ4SWDQO+xxuuEnYcCYdJ6Tyi5hAwZ0+RZmXx0g9BCK+5qzc
|
||||
Ffei066kCItBcayekwCs0i86r8JvDtsMUUkzEKDQTSWHpP8ypIZoz3P+Skdqcs6p
|
||||
IcZHy2fODIsClctRAsHOjZ9BnWSY2rqf1lysqvRfOkNTViTOUVx2hf+LuL+8rT6g
|
||||
K4GvS39Q+c9FYSMjCNxv1m37NfHJv2W7AlqVV95gMBEFB97M3388T7PfvmSLsyPv
|
||||
qAKDE7genTOycQvEX9oeEACxPo08ai2Mgs/YXCig0g0btZoScoKPelQKtD0HLpZ+
|
||||
j7ruaKFy4/vySDMostB3AKSkCE/SkGu/W9oI37n1guSYC/9yJjtTcwZULXAClfwt
|
||||
LyHvLAw4+7BdGJqDe9Tf8z4zqVLax+BcBHkW7pbP+pvxC6fhIA4PO8k5HwwAVlRw
|
||||
68lG6e3z9+Vst4zrFLewHJvkDRSOTSecs9h9SPOjReTK0ovlDzMHVakfUvqpk9mv
|
||||
nCj8SWaMPF70z138AuRuKN7X4exNXPBFnOcseKp/NpT1DyvgoHm/oIO65mq9vvo+
|
||||
42uSbyyegYiTdVK4SaXBc2hixkhaTteqdEy6GxfWqKCvHtYuRD2LyUSjZn/0Y6CG
|
||||
3wZapylKL1sxfH+b1gogv2wAA8KtZAfiTlMISuymwQr5RbOFma7UVMVecCa7XrNG
|
||||
fZwO8R+6++ru0efDzeC/gtLOItjn+yVfsbk2gfu0FikHIV1sogOjnsv3dl6IpH/s
|
||||
w6WbuK/yZhyrrmhIIueHF30ES512Peogf/Gxp8U/ZAmqBa7p4qsv93XWDR52IPYz
|
||||
yU1MtboLD02J39daBNrYZuO4Yfo/xAvU7Ijg2KIR9HS0bSWNuSFAZaZXlJ2bSag3
|
||||
8l0dZMx2fZa0FALxWvY2+nd1tWcH+Wo99RSxX+4TH3guCpe1x/58yKOKL4d1xRpu
|
||||
02nGaqerV9bHU6WjCUYTrrZurTp6yqivpSFCALDMhAU5Ihd/Qjz69zg4uZ7iqbNd
|
||||
WqIYJ3SAOei2tSfoOJbdh8mohotnMFFbupck3Fv+lXAXYDp0V/sSuK32MSeJxan1
|
||||
q3t0eenAh6zoBP0Tj53N8B9YmrgZ1i1AljQqYsAdb8N5Z3vecuzQa946U8H4axEc
|
||||
BL5fgmqSfsbj1EjJVUrw3cE9hgqeqAYBBvZsyW5zSAsLq/RJKYtBxDGADqr0TOOu
|
||||
TjVqfPObb1u9EAQj15ALBhhPORa2gBAK5gnhJWW5deNgA0UOYoWARFYfATuQvr1T
|
||||
9xTVbqzZ0VJR4n7X5JyxJo2tZ44QYVzA0fqX0lt+uRkarrkbZMgplKp4MJTFCp2N
|
||||
tLciWS0YSHPbKTm7svw5mROazUvEoj1N3SYrJT+Ai9UgSL1cA4FmQ5Jsi+GOeQbH
|
||||
7E4pzjeST4b1aATY9ZZSy4TzK+UNYBcCVqY1dN5Xnk0e5yPMnrkkcYI41s/6oOaW
|
||||
lQkp/yrHkvpzQWZa7vF2yWg54j3eX8YWx/TsLvmuJmSQqiIHfu9TXcAIPDispCpF
|
||||
gp4ZbYPZ8RDQLw3ROc+bZ9E0J8e+FHwgXEELsMLW9n4d3PTj9pLFej1vmBKa03o3
|
||||
g+JViNxb1gUPNmC6ADk4o7TQzBrjV2pmmuCza16kOmq6oAbJPOeCLtVWA5HJTX8d
|
||||
AuOzBLO37tWP457dNqoNDaCMik3LCy8cslZjAz02D6VG/QGOAXP9x8bJ3zg9N7vW
|
||||
EOn1BMtIeMI4yIxpC6HgotNWDNbUBWremAFWYQRgYQqo9L1SdW4hz6X8FqdlBFvK
|
||||
ml7sdUwjEy2tNW+aiZI4cgAlI/VvMWYr5kPfhvaUu1wGYeS1TD1B+DPcjcXpOGWo
|
||||
vB1f/uvGc61zZWD0ph9f3S9OnvEko5qV3VhxOnxYUZTrqlM7LVgy/sHrSaPdR7Eo
|
||||
nb4QGdi5wEGMxVgQlDGwOmEmS5nCaA0RcfHbkQV4SebRleocLB9qcSwj5JzPICAB
|
||||
sn1qeSv45LnGdTzUnCQErw1EzgrK1FXOknujjZ3GX5dM7a6Gag0E6cAxoQeC8Mh0
|
||||
aUWolyBo9aSXltn7Vnywoe4X3v96eVH6tUQtN3wt51LBlfSDTWOQDeXLFyW57oYL
|
||||
/ErbmJLaSHpANBcC9InI+rxNYePTTba7K8rdPDppmMEmdOXjaL+5aPay2Bay9K8T
|
||||
FUyZdoyrbzxFZYT9/YyVJV/ukVcT9ru3HpzLTg9Vgc/CyRfIvCZY/Gzh+pPUbRsR
|
||||
pHYDT09BFS+mjszTkcdbceweYXJvknXpajMiISrPUUkb9rwCTmLS4QR4y7I9zAgJ
|
||||
9U72mCZB9yhhzS17kyKAANihXvOFrMXCO7QTWZbtSkobMBS2SybDxYP3rM5b6824
|
||||
HQaY4MZW5qCndydfz4fa908XbPTDka9Nu3CZfI3fhGiiGDFWViotGEYE4i9BuGZJ
|
||||
vmtzII7zeIoHwM4t5fb5mYDd9H+Rkct1yATGEIFEx7cu1fj4Ht2177EzhdkF3Q6I
|
||||
Fccn43yGDW06nFvEjnjb+Lz4EvtGp6dDefUhNBohdzd4Rm2o+etKobm0lZ1xOX74
|
||||
2gOGBLfXS6PF+msNkWP76mqKE6hG0+R1iPrN06Arti+ZRBhNGJDp1pnOERl7IyB0
|
||||
hiLUqae7xVSMVRDeq4O04EUpMXh5ixs3RJXjthYILt7vX+6qskJC3rMuyThrsvMi
|
||||
Kk0wErDpzR5U7KFnM2fk4jY5by/0b/jHZ/PWFGnaFrXNOiwK/mV1RIJtg5FCrvMv
|
||||
xJN5Qo7o2ZYzUL+IBf5mP5O49FCnz3e++9r4ySY/K57ybhQft1l+cQPWS7xuvVAg
|
||||
wZGM9DPl9AhBus0I3V3XzfkcWWHkMynMRcfTnkBHrmKjMopIOggsq05JPalM9b0V
|
||||
NfwZngVvTFbQejbGjgQQQ+G7spav/icCIA8Iu8JPHaKZt2IX77L1xMcQOouqMrx8
|
||||
JCXnJ7zsuU+8W3GQGm70H2kcen4bf4beA8TSkFmbVsaqDNtGaG+ZopWO8ctZX3CP
|
||||
jFgIYgEGi137IWvs1KD5QNFHZI6l0JMzglmvcfnViRPDHLi+EQXAoL/98j/YzA9t
|
||||
a9L4xMO0oucovKkqYOS2cdFaQKVIUM9L5ibcbW7Tol4ugMo5ibIsTgjZoyFohG6t
|
||||
acg+h2wTTyxtrIapvOOBeAo9V/iQowTAPQf38LKQZ5tApksiCG1qvJerKALTMp1J
|
||||
yUHjTCPCPsaiXVVO0mfa9OPtzVvi/icLmlz7umk82mew13djedgMS3/ghq5aEHbg
|
||||
e7rtI5snFlm/GQnV6IaCDytb+jYxqQ0DZ9Mb0CHcMpnLyiBO6nzHfadpZYlrxrpx
|
||||
dPx9MVqfLavj1UVjKuVj4Mgec9J38IBBRGMJZ4MGhde2hrw/s2jVzG1BzaIzcvFa
|
||||
bWiJOkKKRC4BY9DBDYfae5c/obwzkxLmvkbWAVzoFXc+46K1IK6bbC2a4EerOAxc
|
||||
C7B3X8BucD8OtiPORwXgjctd9AJ7cXq0EEVGoZZ8jkILnklsK6RXsxrT4gSOGFpx
|
||||
cBU7TF4kHpH+o4KGQmjlFA2VYP1tXirjEep57LOHfL+zy+HiSXdsbexf6odkqLYA
|
||||
1RR0TzSkmqJYvP0GfHPMvp2xredx1kP934fhTIUcWvErrG3NPd/Dod4BzQye1nNK
|
||||
8Y8aFYF6fACcD4SFZ11ecK9DJmB+WXGlsxtO3w1LeEysny2lVkkH6HuMRIzPyXAM
|
||||
/sEpwh3hwPJosAh6vGM6JxuL7CXP8hC1oUvK91VR0hxeDcO/LgdcHtDnlNMqt1wC
|
||||
jvYt9Hg+3aY5GehBnQPRppnIMaGhiBrRC6HjBg8Umt6UTUr5PXNY2aia5wRcx4M6
|
||||
Tj0O0dAt8WWZ9ySgzOoq4SRoaAp6m4E7CpRNMgXH031OWEeHqtt8rsYqAcvcQLuW
|
||||
vIwts5hiXZOfgXyR6SBY3ANE1SjDY46na2G4oiG8FI7eAqYgehjHrXqCnGrzjefE
|
||||
V1eVcIr7DBYuM8wq7C9x+qdMir6RNV4MrVD+7rO/drivnd3MaqDHlG0LfMZo6GIK
|
||||
QZGIy0z1W5lOXm9x3RAz7/Ea11ynkZyPDDxBE5NRcV6JfukdjDrvsaQA/Qk2Wo7h
|
||||
PdUZjv17H4syMdap6tUDTc8Cohut6n1UxnDnhaAYEDGR3TgPqI5vEaJ+yoaM9itv
|
||||
b2I/K+0uK6GfWwca7PeTtYn92/5etO4czG99xEBFzw7tiEmBTA8KfseZIUe/i3fr
|
||||
9LO+rKEGalFB4mVTYyCKPYYx4hOIvjdCox/jEBTFyYUEhgGnZvArgw9XabvII7eM
|
||||
c1FmwnN2CM/MHUzM0Kw7cS/oaoRFcG/PqioO/krL2Bd+xThwSH4Zn+Y1UVO7aXNy
|
||||
90MOehHSc2nhN984Y2re3ffVsjPhC3KdWxr8jbho/Az65rUkYg64roiO2ZqdqRvf
|
||||
aYtX9fCe8N8SzgLTlWZt+l8sjY8HP2k9NoajphV1nG4iC/pX0ZJiVQ+VBRNaGhSl
|
||||
6IhM9gM1ZsI86cULJpOTAhNu1fZBv+Mq8IXYD8SqlNVwVtjvshtlzBN4InSGEKiO
|
||||
HM7coo7aDuu+Go5AEiIVMbR09jAa0VINsCVScfSbTFhlGRTrZr2DSO/s8WxbV+Mb
|
||||
EQAfZmyrLF71Z3MfIukg+ps5iuqFhZ+gJSXXSFwumpg=
|
||||
EOF
|
||||
|
||||
exec sh bin/do-setup-circleci-secrets
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# shellcheck disable=SC2155
|
||||
wait_for() {
|
||||
local timeout=$1
|
||||
local lock_file=$2
|
||||
for i in $(seq 1 "$timeout"); do
|
||||
if [ -f "$lock_file" ]; then
|
||||
local status="$(cat "$lock_file")"
|
||||
if [ "$status" == "OK" ]; then
|
||||
echo "[$i seconds]: $lock_file found and status: $status." && return
|
||||
else
|
||||
echo "[$i seconds]: $lock_file found and status: $status." && return 1
|
||||
fi
|
||||
fi
|
||||
if ! ((i % 10)); then echo "[$i seconds]: Waiting for $lock_file to be created..."; fi
|
||||
sleep 1
|
||||
done
|
||||
echo "Timed out waiting for test VMs to be ready." >&2
|
||||
return 1
|
||||
}
|
||||
+14
-75
@@ -1,82 +1,21 @@
|
||||
FROM golang:1.13.3-buster
|
||||
FROM ubuntu
|
||||
|
||||
# Support Raspberry Pi 2 and newer
|
||||
ENV GOARM 7
|
||||
RUN apt-get -y update && apt-get -y install --no-install-recommends build-essential git ca-certificates docker.io mercurial libpcap-dev curl make pkg-config gcc bison flex
|
||||
|
||||
# The names of the architectures that we're fetching cross-compilers for from the Debian packages
|
||||
ENV DEB_CROSSPLATFORMS \
|
||||
armhf \
|
||||
arm64 \
|
||||
ppc64el \
|
||||
s390x
|
||||
# When doing a build in a container, "apt-get update" happens twice,
|
||||
# which can be a very significant overhead for incremental builds.
|
||||
# And it's unnecessary, because if any of the prerequisites do change,
|
||||
# the right thing to do is to rebuild the container image. Rather
|
||||
# than suppressing the "apt-get update" commands explicitly, we clear
|
||||
# out sources.list so that 'apt-get update' doesn't do anything.
|
||||
RUN echo >/etc/apt/sources.list
|
||||
|
||||
# The go platforms we are supporting to cross-compile
|
||||
ENV GO_CROSSPLATFORMS \
|
||||
linux/arm \
|
||||
linux/arm64 \
|
||||
linux/ppc64le \
|
||||
linux/s390x
|
||||
ENV GO_VERSION 1.4.2
|
||||
RUN curl -sSL https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz | tar -C /usr/local -xz
|
||||
ENV PATH /usr/local/go/bin:$PATH
|
||||
|
||||
# The names of the gcc cross-compilers we have installed
|
||||
ENV GCC_CROSSCOMPILERS \
|
||||
arm-linux-gnueabihf \
|
||||
aarch64-linux-gnu \
|
||||
powerpc64le-linux-gnu \
|
||||
s390x-linux-gnu
|
||||
|
||||
# Install the build-essential and crossbuild-essential-ARCH packages
|
||||
RUN for platform in ${DEB_CROSSPLATFORMS}; do dpkg --add-architecture $platform; done \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y build-essential \
|
||||
&& for platform in ${DEB_CROSSPLATFORMS}; do apt-get install -y crossbuild-essential-${platform}; done \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install some required packages
|
||||
# libpcap is required because we're linking against its C libraries from the prog/weaver binary
|
||||
# flex and bison are required packages for compiling libpcap manually later
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
libpcap-dev \
|
||||
python-requests \
|
||||
time \
|
||||
file \
|
||||
flex \
|
||||
bison
|
||||
|
||||
RUN curl -fsSLo shfmt https://github.com/mvdan/sh/releases/download/v1.3.0/shfmt_v1.3.0_linux_amd64 && \
|
||||
echo "b1925c2c405458811f0c227266402cf1868b4de529f114722c2e3a5af4ac7bb2 shfmt" | sha256sum -c && \
|
||||
chmod +x shfmt && \
|
||||
mv shfmt /usr/bin
|
||||
|
||||
# Install common Go tools
|
||||
RUN go get \
|
||||
github.com/weaveworks/build-tools/cover \
|
||||
github.com/mattn/goveralls \
|
||||
golang.org/x/lint/golint \
|
||||
github.com/fzipp/gocyclo \
|
||||
github.com/fatih/hclfmt \
|
||||
github.com/client9/misspell/cmd/misspell
|
||||
|
||||
# Allow full write access to the Go folders for anyone
|
||||
RUN chmod -R a+w /usr/local/go
|
||||
|
||||
# The libpcap version from Debian packages is 1.6.2, matching that version here although newer versions of libpcap have been released
|
||||
# We have to cross-compile the libpcap library for the new architectures, that's what we're doing here
|
||||
ENV LIBPCAP_CROSS_VERSION=1.6.2
|
||||
RUN curl -sSL https://www.tcpdump.org/release/libpcap-${LIBPCAP_CROSS_VERSION}.tar.gz | tar -xz \
|
||||
&& cd libpcap-${LIBPCAP_CROSS_VERSION} \
|
||||
&& for crosscompiler in ${GCC_CROSSCOMPILERS}; do \
|
||||
CC=${crosscompiler}-gcc ac_cv_linux_vers=2 ./configure --host=${crosscompiler} --with-pcap=linux \
|
||||
&& make \
|
||||
&& export LIB_DIR="/usr/local/lib/${crosscompiler}-gcc" VER="$(cat ./VERSION)" MAJOR_VER="$(sed 's/\([0-9][0-9]*\)\..*/\1/' ./VERSION)" \
|
||||
&& mkdir -p ${LIB_DIR} \
|
||||
&& cp -f libpcap.a libpcap.so.${VER} ${LIB_DIR} \
|
||||
&& ln -sf libpcap.so.${VER} /usr/local/lib/libpcap.so.${MAJOR_VER} \
|
||||
&& ln -sf libpcap.so.${MAJOR_VER} /usr/local/lib/libpcap.so \
|
||||
&& make clean; done
|
||||
|
||||
# Install Docker
|
||||
RUN curl -fsSL https://get.docker.com | VERSION=18.06.0-ce /bin/sh
|
||||
# So that sudo does not trample on PATH
|
||||
RUN sed -ie '/secure_path/d' /etc/sudoers
|
||||
|
||||
COPY build.sh /
|
||||
ENTRYPOINT ["sh", "/build.sh"]
|
||||
+35
-19
@@ -1,28 +1,44 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
set -eu
|
||||
GOPATH=/home/go
|
||||
export GOPATH
|
||||
|
||||
WEAVE_SRC=$GOPATH/src/github.com/weaveworks/weave
|
||||
|
||||
if ! [ -e $WEAVE_SRC ] ; then
|
||||
cat 2>&1 <<EOF
|
||||
Nothing at ${WEAVE_SRC}. Please bind-mount the weave repo with the
|
||||
if [ $# -eq 0 ] ; then
|
||||
# No arguments. Expect that the weave repo will be bind-mounted
|
||||
# into $GOPATH
|
||||
if ! [ -e $WEAVE_SRC ] ; then
|
||||
cat 2>&1 <<EOF
|
||||
No container arguments supplied, and nothing at ${WEAVE_SRC}. Please
|
||||
either bind-mount the golang workspace containing weave with the
|
||||
docker run -v option, e.g.:
|
||||
|
||||
$ cd weave
|
||||
$ docker run -v $(pwd):/go/src/github.com/weaveworks/weave \\
|
||||
weaveworks/weave-build
|
||||
$ docker run -v <host gopath>:${GOPATH} \\
|
||||
-v /var/run/docker.sock:/var/run/docker.sock weaveworks/weave-build
|
||||
|
||||
Or supply git clone arguments to retrieve it, e.g.:
|
||||
|
||||
$ docker run -v /var/run/docker.sock:/var/run/docker.sock \\
|
||||
weaveworks/weave-build https://github.com/weaveworks/weave.git
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If we run make directly, any files created on the bind mount
|
||||
# will have awkward ownership. So we switch to a user with the
|
||||
# same user and group IDs as source directory. We have to set a
|
||||
# few things up so that sudo works without complaining later on.
|
||||
uid=$(stat --format="%u" $WEAVE_SRC)
|
||||
gid=$(stat --format="%g" $WEAVE_SRC)
|
||||
echo "weave:x:$uid:$gid::$WEAVE_SRC:/bin/sh" >>/etc/passwd
|
||||
echo "weave:*:::::::" >>/etc/shadow
|
||||
echo "weave ALL=(ALL) NOPASSWD: ALL" >>/etc/sudoers
|
||||
su weave -c "PATH=$PATH make -C $WEAVE_SRC build"
|
||||
else
|
||||
# There are arguments to pass to git-clone
|
||||
mkdir -p ${WEAVE_SRC%/*}
|
||||
git clone "$@" $WEAVE_SRC
|
||||
make -C $WEAVE_SRC build
|
||||
fi
|
||||
|
||||
# If we run make directly, any files created on the bind mount
|
||||
# will have awkward ownership. So we switch to a user with the
|
||||
# same user and group IDs as source directory. We have to set a
|
||||
# few things up so that sudo works without complaining later on.
|
||||
uid=$(stat --format="%u" $WEAVE_SRC)
|
||||
gid=$(stat --format="%g" $WEAVE_SRC)
|
||||
echo "weave:x:$uid:$gid::$WEAVE_SRC:/bin/sh" >>/etc/passwd
|
||||
echo "weave:*:::::::" >>/etc/shadow
|
||||
echo "weave ALL=(ALL) NOPASSWD: ALL" >>/etc/sudoers
|
||||
|
||||
su weave -c "PATH=$PATH make -C $WEAVE_SRC BUILD_IN_CONTAINER=false $*"
|
||||
@@ -1 +0,0 @@
|
||||
c02d861383d035ba020b361ef93bf14beb3964a42a6ae645b505b6148493e7ec qemu-aarch64-static
|
||||
@@ -1 +0,0 @@
|
||||
0ff63682b8868620d771aa812d3053dc3c579dc8633ce62d3d7ae7eee83beb06 qemu-arm-static
|
||||
@@ -1 +0,0 @@
|
||||
926f7f265332076ea4abf6e9586288376dbed2322c155b8d0cafbee614aaa7f2 qemu-ppc64le-static
|
||||
@@ -1 +0,0 @@
|
||||
bab3b4d04263479b3d9f85cf59bd0cad1f2b434f29856b2204f8288f2f3598e4 qemu-s390x-static
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
general:
|
||||
branches:
|
||||
ignore:
|
||||
- gh\-pages
|
||||
- /pull\/.*/
|
||||
|
||||
machine:
|
||||
services:
|
||||
- docker
|
||||
environment:
|
||||
GOPATH: /home/ubuntu:$GOPATH
|
||||
SRCDIR: /home/ubuntu/src/github.com/weaveworks/weave
|
||||
PATH: $PATH:$HOME/.local/bin:$HOME/google-cloud-sdk/bin/
|
||||
CLOUDSDK_CORE_DISABLE_PROMPTS: 1
|
||||
WEAVE_BUILD: $HOME/docker/weave-build.tar
|
||||
|
||||
dependencies:
|
||||
cache_directories:
|
||||
- "~/docker"
|
||||
override:
|
||||
- if [[ -e "$WEAVE_BUILD" ]]; then
|
||||
docker load -i $WEAVE_BUILD;
|
||||
else
|
||||
docker pull weaveworks/weave-build;
|
||||
mkdir -p $(dirname "$WEAVE_BUILD");
|
||||
docker save weaveworks/weave-build >$WEAVE_BUILD;
|
||||
fi
|
||||
post:
|
||||
- curl https://sdk.cloud.google.com | bash
|
||||
- bin/setup-circleci-secrets "$SECRET_PASSWORD"
|
||||
- mkdir -p $(dirname $SRCDIR)
|
||||
- cp -r $(pwd)/ $SRCDIR
|
||||
|
||||
test:
|
||||
override:
|
||||
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go weaveworks/weave-build:
|
||||
parallel: true
|
||||
- cd $SRCDIR/test; ./gce.sh setup:
|
||||
parallel: true
|
||||
- cd $SRCDIR/test; . ./gce.sh hosts; ./setup.sh:
|
||||
parallel: true
|
||||
- cd $SRCDIR/test; . ./gce.sh hosts; ./run_all.sh:
|
||||
parallel: true
|
||||
post:
|
||||
- cd $SRCDIR/test; ./gce.sh destroy:
|
||||
parallel: true
|
||||
@@ -1,7 +0,0 @@
|
||||
## Community Code of Conduct
|
||||
|
||||
Weaveworks follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||
may be reported by contacting a Weaveworks project maintainer, or
|
||||
Alexis Richardson alexis@weave.works.
|
||||
@@ -1,258 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
const (
|
||||
InitialInterval = 1 * time.Second
|
||||
MaxInterval = 20 * time.Second
|
||||
)
|
||||
|
||||
// An observer for container events
|
||||
type ContainerObserver interface {
|
||||
ContainerStarted(ident string)
|
||||
ContainerDied(ident string)
|
||||
ContainerDestroyed(ident string)
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*docker.Client
|
||||
}
|
||||
|
||||
type syncPair struct {
|
||||
stop chan struct{}
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
type pendingStarts map[string]*syncPair
|
||||
|
||||
// NewClient creates a new Docker client and checks we can talk to Docker
|
||||
func NewClient(apiPath string) (*Client, error) {
|
||||
if apiPath != "" && !strings.Contains(apiPath, "://") {
|
||||
apiPath = "tcp://" + apiPath
|
||||
}
|
||||
dc, err := docker.NewClient(apiPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := &Client{dc}
|
||||
|
||||
return client, client.checkWorking()
|
||||
}
|
||||
|
||||
func NewVersionedClient(apiPath string, apiVersionString string) (*Client, error) {
|
||||
if !strings.Contains(apiPath, "://") {
|
||||
apiPath = "tcp://" + apiPath
|
||||
}
|
||||
dc, err := docker.NewVersionedClient(apiPath, apiVersionString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := &Client{dc}
|
||||
|
||||
return client, client.checkWorking()
|
||||
}
|
||||
|
||||
func NewVersionedClientFromEnv(apiVersionString string) (*Client, error) {
|
||||
dc, err := docker.NewVersionedClientFromEnv(apiVersionString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := &Client{dc}
|
||||
|
||||
return client, client.checkWorking()
|
||||
}
|
||||
|
||||
func (c *Client) checkWorking() error {
|
||||
_, err := c.Version()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) Info() string {
|
||||
env, err := c.Version()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Docker API error: %s", err)
|
||||
}
|
||||
return fmt.Sprintf("Docker API on %s: %v", c.Endpoint(), env)
|
||||
}
|
||||
|
||||
func (c *Client) DockerVersion() string {
|
||||
if env, err := c.Version(); err == nil {
|
||||
if v, found := env.Map()["Version"]; found {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// AddObserver adds an observer for docker events
|
||||
func (c *Client) AddObserver(ob ContainerObserver) error {
|
||||
go func() {
|
||||
pending := make(pendingStarts)
|
||||
retryInterval := InitialInterval
|
||||
for {
|
||||
// Use a buffered chan so the client library can run ahead of the listener
|
||||
// - Docker will drop an event if it is not collected quickly enough.
|
||||
events := make(chan *docker.APIEvents, 1024)
|
||||
if err := c.AddEventListener(events); err != nil {
|
||||
c.errorf("Unable to add listener to Docker API: %s - retrying in %ds", err, retryInterval/time.Second)
|
||||
} else {
|
||||
start := time.Now()
|
||||
for event := range events {
|
||||
switch event.Status {
|
||||
case "start":
|
||||
pending.finish(event.ID)
|
||||
pending.start(event.ID, c, ob)
|
||||
case "die":
|
||||
pending.finish(event.ID)
|
||||
ob.ContainerDied(event.ID)
|
||||
case "destroy":
|
||||
pending.finish(event.ID)
|
||||
ob.ContainerDestroyed(event.ID)
|
||||
}
|
||||
}
|
||||
if time.Since(start) > retryInterval {
|
||||
retryInterval = InitialInterval
|
||||
}
|
||||
c.errorf("Event listener channel closed - retrying subscription in %ds", retryInterval/time.Second)
|
||||
}
|
||||
time.Sleep(retryInterval)
|
||||
retryInterval = retryInterval * 3 / 2
|
||||
if retryInterval > MaxInterval {
|
||||
retryInterval = MaxInterval
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Docker sends a 'start' event before it has attempted to start the
|
||||
// container. Delay notifying the observer until the container has a
|
||||
// pid, or we are told to stop when a 'die' event arrives.
|
||||
//
|
||||
// Note we always deliver the event, even if the container seems to
|
||||
// have gone away.
|
||||
func (pending pendingStarts) start(id string, c *Client, ob ContainerObserver) {
|
||||
sync := syncPair{make(chan struct{}), make(chan struct{})}
|
||||
pending[id] = &sync
|
||||
go func() {
|
||||
defer close(sync.done)
|
||||
defer ob.ContainerStarted(id)
|
||||
for {
|
||||
if container, err := c.InspectContainer(id); err != nil || container.State.Pid != 0 {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-sync.stop:
|
||||
return
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (pending pendingStarts) finish(id string) {
|
||||
if sync, found := pending[id]; found {
|
||||
close(sync.stop)
|
||||
<-sync.done
|
||||
delete(pending, id)
|
||||
}
|
||||
}
|
||||
|
||||
// AllContainerIDs returns all the IDs of Docker containers,
|
||||
// whether they are running or not.
|
||||
func (c *Client) AllContainerIDs() ([]string, error) {
|
||||
return c.containerIDs(true)
|
||||
}
|
||||
|
||||
// RunningContainerIDs returns all the IDs of the running
|
||||
// Docker containers.
|
||||
func (c *Client) RunningContainerIDs() ([]string, error) {
|
||||
return c.containerIDs(false)
|
||||
}
|
||||
|
||||
func (c *Client) containerIDs(all bool) ([]string, error) {
|
||||
containers, err := c.ListContainers(docker.ListContainersOptions{All: all})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ids []string
|
||||
for _, c := range containers {
|
||||
ids = append(ids, c.ID)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// IsContainerNotRunning returns true if we have checked with Docker that the ID is not running
|
||||
func (c *Client) IsContainerNotRunning(idStr string) bool {
|
||||
container, err := c.InspectContainer(idStr)
|
||||
if err == nil {
|
||||
return !container.State.Running || container.State.Restarting
|
||||
}
|
||||
if _, notThere := err.(*docker.NoSuchContainer); notThere {
|
||||
return true
|
||||
}
|
||||
c.errorf("Could not check container status: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// This is intended to find an IP address that we can reach the container on;
|
||||
// if it is on the Docker bridge network then that address; if on the host network
|
||||
// then localhost
|
||||
func (c *Client) GetContainerIP(nameOrID string) (string, error) {
|
||||
info, err := c.InspectContainer(nameOrID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if info.NetworkSettings.Networks != nil {
|
||||
if bridgeNetwork, ok := info.NetworkSettings.Networks["bridge"]; ok {
|
||||
return bridgeNetwork.IPAddress, nil
|
||||
} else if _, ok := info.NetworkSettings.Networks["host"]; ok {
|
||||
return "127.0.0.1", nil
|
||||
}
|
||||
} else if info.HostConfig.NetworkMode == "host" {
|
||||
return "127.0.0.1", nil
|
||||
}
|
||||
if info.NetworkSettings.IPAddress == "" {
|
||||
return "", errors.New("No IP address found for container " + nameOrID)
|
||||
}
|
||||
return info.NetworkSettings.IPAddress, nil
|
||||
}
|
||||
|
||||
func (c *Client) EnsureNetwork(networkName, driver, subnet string, options map[string]interface{}) error {
|
||||
_, err := c.CreateNetwork(
|
||||
docker.CreateNetworkOptions{
|
||||
Name: networkName,
|
||||
CheckDuplicate: true,
|
||||
Driver: driver,
|
||||
IPAM: &docker.IPAMOptions{
|
||||
Driver: driver,
|
||||
Config: []docker.IPAMConfig{{Subnet: subnet}},
|
||||
},
|
||||
Options: options,
|
||||
})
|
||||
if err != docker.ErrNetworkAlreadyExists && err != nil {
|
||||
// Despite appearances to the contrary, CreateNetwork does
|
||||
// sometimes(always?) *not* return ErrNetworkAlreadyExists
|
||||
// when the network already exists. Hence we need to check for
|
||||
// this explicitly.
|
||||
if _, err2 := c.NetworkInfo(networkName); err2 != nil {
|
||||
return fmt.Errorf("unable to create network: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// logging
|
||||
|
||||
func (c *Client) errorf(fmt string, args ...interface{}) {
|
||||
common.Log.Errorf("[docker] "+fmt, args...)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package common
|
||||
|
||||
func CheckFatal(e error) {
|
||||
if e != nil {
|
||||
Error.Fatal(e)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckWarn(e error) {
|
||||
if e != nil {
|
||||
Warning.Println(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Assert test is true, panic otherwise
|
||||
func Assert(test bool) {
|
||||
if !test {
|
||||
panic("Assertion failure")
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type loggingHandler struct {
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
func (l *loggingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
Log.Debugf("[http] %s %s", r.Method, r.URL.RequestURI())
|
||||
l.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func LoggingHTTPHandler(h http.Handler) http.Handler {
|
||||
return &loggingHandler{next: h}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
func init() {
|
||||
InitLogging(ioutil.Discard, os.Stdout, os.Stdout, os.Stderr)
|
||||
}
|
||||
+25
-71
@@ -1,87 +1,41 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"os"
|
||||
)
|
||||
|
||||
type textFormatter struct {
|
||||
}
|
||||
const (
|
||||
standardLogFlags = log.Ldate | log.Ltime | log.Lmicroseconds
|
||||
)
|
||||
|
||||
// Based off logrus.TextFormatter, which behaves completely
|
||||
// differently when you don't want colored output
|
||||
func (f *textFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
b := &bytes.Buffer{}
|
||||
|
||||
levelText := strings.ToUpper(entry.Level.String())[0:4]
|
||||
timeStamp := entry.Time.Format("2006/01/02 15:04:05.000000")
|
||||
if len(entry.Data) > 0 {
|
||||
fmt.Fprintf(b, "%s: %s %-44s ", levelText, timeStamp, entry.Message)
|
||||
for k, v := range entry.Data {
|
||||
fmt.Fprintf(b, " %s=%v", k, v)
|
||||
}
|
||||
} else {
|
||||
// No padding when there's no fields
|
||||
fmt.Fprintf(b, "%s: %s %s", levelText, timeStamp, entry.Message)
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
// Largely taken from
|
||||
// http://www.goinggo.net/2013/11/using-log-package-in-go.html
|
||||
|
||||
var (
|
||||
standardTextFormatter = &textFormatter{}
|
||||
Debug *log.Logger
|
||||
Info *log.Logger
|
||||
Warning *log.Logger
|
||||
Error *log.Logger
|
||||
)
|
||||
|
||||
var (
|
||||
Log *logrus.Logger
|
||||
)
|
||||
func InitLogging(debugHandle io.Writer,
|
||||
infoHandle io.Writer,
|
||||
warningHandle io.Writer,
|
||||
errorHandle io.Writer) {
|
||||
|
||||
func init() {
|
||||
Log = logrus.New()
|
||||
Log.Formatter = standardTextFormatter
|
||||
Debug = log.New(debugHandle, "DEBUG: ", standardLogFlags)
|
||||
Info = log.New(infoHandle, "INFO: ", standardLogFlags)
|
||||
Warning = log.New(warningHandle, "WARNING: ", standardLogFlags)
|
||||
Error = log.New(errorHandle, "ERROR: ", standardLogFlags)
|
||||
}
|
||||
|
||||
func SetLogLevel(levelname string) {
|
||||
level, err := logrus.ParseLevel(levelname)
|
||||
if err != nil {
|
||||
Log.Fatal(err)
|
||||
func InitDefaultLogging(debug bool) {
|
||||
debugOut := ioutil.Discard
|
||||
if debug {
|
||||
debugOut = os.Stderr
|
||||
}
|
||||
Log.Level = level
|
||||
}
|
||||
|
||||
func CheckFatal(e error) {
|
||||
if e != nil {
|
||||
Log.Fatal(e)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckWarn(e error) {
|
||||
if e != nil {
|
||||
Log.Warnln(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Adaptor type to go from Logrus to something expecting a standard library log.Logger
|
||||
type logLogger struct {
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
// io.Writer interface
|
||||
func (l logLogger) Write(p []byte) (n int, err error) {
|
||||
ln := len(p)
|
||||
// Strip a trailing newline, because logrus formatter adds one
|
||||
if ln > 0 && p[ln-1] == '\n' {
|
||||
p = p[:ln-1]
|
||||
}
|
||||
l.logger.Info(string(p))
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func LogLogger() *log.Logger {
|
||||
return log.New(logLogger{Log}, "", 0)
|
||||
InitLogging(debugOut, os.Stdout, os.Stdout, os.Stderr)
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package odp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/weaveworks/go-odp/odp"
|
||||
)
|
||||
|
||||
// ODP admin functionality
|
||||
|
||||
func CreateDatapath(dpname string) (supported bool, err error) {
|
||||
dpif, err := odp.NewDpif()
|
||||
if err != nil {
|
||||
if odp.IsKernelLacksODPError(err) {
|
||||
return false, nil
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
defer dpif.Close()
|
||||
|
||||
dp, err := dpif.CreateDatapath(dpname)
|
||||
if err != nil {
|
||||
if !odp.IsDatapathNameAlreadyExistsError(err) {
|
||||
return true, err
|
||||
}
|
||||
if dp, err = dpif.LookupDatapath(dpname); err != nil {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
|
||||
// Pick an ephemeral port number to use in probing for vxlan
|
||||
// support.
|
||||
udpconn, err := net.ListenUDP("udp4", nil)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
defer udpconn.Close()
|
||||
|
||||
// we leave the UDP socket open, so creating a vxlan vport on
|
||||
// the same port number should fail. But that's fine: It's
|
||||
// still sufficient to probe for support.
|
||||
portno := uint16(udpconn.LocalAddr().(*net.UDPAddr).Port)
|
||||
vpid, err := dp.CreateVport(odp.NewVxlanVportSpec(fmt.Sprintf("vxlan-%d", portno), portno))
|
||||
if nlerr, ok := err.(odp.NetlinkError); ok {
|
||||
if syscall.Errno(nlerr) == syscall.EAFNOSUPPORT {
|
||||
dp.Delete()
|
||||
return false, fmt.Errorf("kernel does not have Open vSwitch VXLAN support")
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
dp.DeleteVport(vpid)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func DeleteDatapath(dpname string) error {
|
||||
dpif, err := odp.NewDpif()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dpif.Close()
|
||||
|
||||
dp, err := dpif.LookupDatapath(dpname)
|
||||
if err != nil {
|
||||
if odp.IsNoSuchDatapathError(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return dp.Delete()
|
||||
}
|
||||
|
||||
func AddDatapathInterface(dpname string, ifname string) error {
|
||||
return addDatapathInterface(dpname, ifname, false)
|
||||
}
|
||||
|
||||
func AddDatapathInterfaceIfNotExist(dpname string, ifname string) error {
|
||||
return addDatapathInterface(dpname, ifname, true)
|
||||
}
|
||||
|
||||
func addDatapathInterface(dpname, ifname string, ignoreIfExist bool) error {
|
||||
dpif, err := odp.NewDpif()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dpif.Close()
|
||||
|
||||
dp, err := dpif.LookupDatapath(dpname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = dp.CreateVport(odp.NewNetdevVportSpec(ifname))
|
||||
if ignoreIfExist && err == odp.NetlinkError(syscall.EEXIST) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func AllPids(procRoot string) ([]int, error) {
|
||||
fh, err := os.Open(procRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fh.Close()
|
||||
dirNames, err := fh.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pids := make([]int, len(dirNames))
|
||||
for _, dirName := range dirNames {
|
||||
pid, err := strconv.Atoi(dirName)
|
||||
if err != nil { // Only interested in numeric entries - skip /proc/stat, etc.
|
||||
continue
|
||||
}
|
||||
pids = append(pids, pid)
|
||||
}
|
||||
return pids, err
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// A subsystem/server/... that can be stopped or queried about the status with a signal
|
||||
type SignalReceiver interface {
|
||||
Stop() error
|
||||
Status() string
|
||||
}
|
||||
|
||||
func SignalHandlerLoop(ss ...SignalReceiver) {
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGUSR1)
|
||||
buf := make([]byte, 1<<20)
|
||||
for {
|
||||
switch <-sigs {
|
||||
case syscall.SIGINT:
|
||||
Info.Printf("=== received SIGINT ===\n*** exiting\n")
|
||||
for _, subsystem := range ss {
|
||||
subsystem.Stop()
|
||||
}
|
||||
return
|
||||
case syscall.SIGQUIT:
|
||||
stacklen := runtime.Stack(buf, true)
|
||||
Info.Printf("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n", buf[:stacklen])
|
||||
case syscall.SIGUSR1:
|
||||
for _, subsystem := range ss {
|
||||
Info.Printf("=== received SIGUSR1 ===\n*** status...\n%s\n*** end\n", subsystem.Status())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
type ContainerObserver interface {
|
||||
ContainerDied(ident string) error
|
||||
}
|
||||
|
||||
func checkError(err error, apiPath string) {
|
||||
if err != nil {
|
||||
Error.Fatalf("[updater] Unable to connect to Docker API on %s: %s",
|
||||
apiPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
func Start(apiPath string, ob ContainerObserver) error {
|
||||
client, err := docker.NewClient(apiPath)
|
||||
checkError(err, apiPath)
|
||||
|
||||
env, err := client.Version()
|
||||
checkError(err, apiPath)
|
||||
|
||||
events := make(chan *docker.APIEvents)
|
||||
err = client.AddEventListener(events)
|
||||
checkError(err, apiPath)
|
||||
|
||||
Info.Printf("[updater] Using Docker API on %s: %v", apiPath, env)
|
||||
|
||||
go func() {
|
||||
for event := range events {
|
||||
handleEvent(ob, event, client)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleEvent(ob ContainerObserver, event *docker.APIEvents, client *docker.Client) error {
|
||||
switch event.Status {
|
||||
case "die":
|
||||
id := event.ID
|
||||
ob.ContainerDied(id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package common
|
||||
|
||||
import "strings"
|
||||
|
||||
// Assert test is true, panic otherwise
|
||||
func Assert(test bool) {
|
||||
if !test {
|
||||
panic("Assertion failure")
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorMessages(errors []error) string {
|
||||
var result []string
|
||||
for _, err := range errors {
|
||||
result = append(result, err.Error())
|
||||
}
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type WaitGroup struct {
|
||||
wg sync.WaitGroup
|
||||
count int32
|
||||
}
|
||||
|
||||
// Increase the count of things waiting and return a closure which calls Done
|
||||
func (w *WaitGroup) Add() func() {
|
||||
atomic.AddInt32(&w.count, 1)
|
||||
w.wg.Add(1)
|
||||
return func() {
|
||||
w.wg.Done()
|
||||
atomic.AddInt32(&w.count, -1)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WaitGroup) IsDone() bool {
|
||||
return atomic.LoadInt32(&w.count) == 0
|
||||
}
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
type DB interface {
|
||||
Load(string, interface{}) (bool, error)
|
||||
Save(string, interface{}) error
|
||||
}
|
||||
|
||||
type BoltDB struct {
|
||||
db *bolt.DB
|
||||
}
|
||||
|
||||
var (
|
||||
versionIdent = []byte("version")
|
||||
persistenceVersion = []byte{1, 0} // major.minor
|
||||
topBucket = []byte("top")
|
||||
)
|
||||
|
||||
const (
|
||||
NameIdent = "peername"
|
||||
fileName = "data.db"
|
||||
)
|
||||
|
||||
func Pathname(dbPrefix string) string {
|
||||
return dbPrefix + fileName
|
||||
}
|
||||
|
||||
func NewBoltDB(dbPrefix string) (*BoltDB, error) {
|
||||
dbPathname := Pathname(dbPrefix)
|
||||
db, err := bolt.Open(dbPathname, 0660, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[boltDB] Unable to open %s: %s", dbPathname, err)
|
||||
}
|
||||
err = db.Update(checkVersion(false))
|
||||
return &BoltDB{db: db}, err
|
||||
}
|
||||
|
||||
func NewBoltDBReadOnly(dbPrefix string) (*BoltDB, error) {
|
||||
options := bolt.Options{Timeout: time.Millisecond * 50, ReadOnly: true}
|
||||
dbPathname := Pathname(dbPrefix)
|
||||
db, err := bolt.Open(dbPathname, 0660, &options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[boltDB] Unable to open %s: %s", dbPathname, err)
|
||||
}
|
||||
err = db.View(checkVersion(true))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[boltDB] Cannot use persistence file %s: %s", dbPathname, err)
|
||||
}
|
||||
return &BoltDB{db: db}, nil
|
||||
}
|
||||
|
||||
func checkVersion(readOnly bool) func(tx *bolt.Tx) error {
|
||||
return func(tx *bolt.Tx) error {
|
||||
if top := tx.Bucket(topBucket); top == nil {
|
||||
if readOnly {
|
||||
return fmt.Errorf("no top bucket")
|
||||
}
|
||||
top, err := tx.CreateBucket(topBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := top.Put(versionIdent, persistenceVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if checkVersion := top.Get(versionIdent); checkVersion != nil {
|
||||
if checkVersion[0] != persistenceVersion[0] {
|
||||
return fmt.Errorf("mismatched version %x", checkVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *BoltDB) Load(ident string, data interface{}) (bool, error) {
|
||||
found := true
|
||||
err := d.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(topBucket)
|
||||
v := b.Get([]byte(ident))
|
||||
if v == nil {
|
||||
found = false
|
||||
return nil
|
||||
}
|
||||
reader := bytes.NewReader(v)
|
||||
decoder := gob.NewDecoder(reader)
|
||||
err := decoder.Decode(data)
|
||||
return err
|
||||
})
|
||||
return found, err
|
||||
}
|
||||
|
||||
func (d *BoltDB) Save(ident string, data interface{}) error {
|
||||
return d.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(topBucket)
|
||||
buf := new(bytes.Buffer)
|
||||
enc := gob.NewEncoder(buf)
|
||||
if err := enc.Encode(data); err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Put([]byte(ident), buf.Bytes())
|
||||
})
|
||||
}
|
||||
|
||||
func (d *BoltDB) Close() error {
|
||||
return d.db.Close()
|
||||
}
|
||||
+14
-14
@@ -34,7 +34,7 @@ Router:
|
||||
the known locations of peers as they're discovered and is informed
|
||||
when connections we have made die. It proactively tries to create
|
||||
connections to remote peers we're not connected to using a random
|
||||
exponential backoff to regulate the period between connection
|
||||
binary exponential backoff to regulate the period between connection
|
||||
attempts.
|
||||
|
||||
- Local Peer
|
||||
@@ -66,23 +66,23 @@ Router:
|
||||
simply responds to traffic received from the remote peer via TCP
|
||||
5. We register this connection with the local peer
|
||||
6a. If we initiated this connection then we now start sending fast
|
||||
heartbeats to the remote peer so that the remote peer can
|
||||
determine what address/port it should use to send UDP back to
|
||||
us. To do this, we spawn off a "forwarder" thread to send
|
||||
heartbeats, monitor incoming heartbeats, and some other auxiliary
|
||||
duties. It also consumes frames to be encapsulated and send via
|
||||
UDP from two channels, for DF and non-DF cases. In the non-DF
|
||||
case, it can just send the packets out of the UDP Listener
|
||||
socket. In the DF case, it needs its own socket so that it can do
|
||||
PMTU discovery easily. To do this, it uses a Raw IP socket (IP has
|
||||
no ports, so there's no collision issue with the UDP Listener
|
||||
socket) and so it must add UDP headers itself.
|
||||
6b. If we did not initiate this connection then the UDP Listener
|
||||
heartbeats to the remote peer so that the remote peer can determine what
|
||||
address/port it should use to send UDP back to us. To do this, we
|
||||
spawn off two further threads which are the forwarder
|
||||
loops. These receive frames which are to be sent to the remote
|
||||
peer. One of them sends frames without the DF flag set. To do this
|
||||
it just sends the packets out of the UDP Listener socket. The
|
||||
other needs to send frames with the DF flag set and needs its own
|
||||
socket so that it can do PMTU discovery easily. To do this, it
|
||||
uses a Raw IP socket (IP has no ports, so there's no collision
|
||||
issue with the UDP Listener socket) and so it must add UDP headers
|
||||
itself.
|
||||
6b. If we did not initiate this connection then the UDP Listener
|
||||
should start receiving fast heartbeats from the remote peer. From
|
||||
those it should be able to identify the local connection via the
|
||||
local peer. It will tell the local connection (communicating to
|
||||
the actor thread) about the UDP address of the remote peer. The
|
||||
local connection will then start its forwarder thread as
|
||||
local connection will then start its forwarder threads as
|
||||
described in 6a, and start sending fast heartbeats. We send to the
|
||||
remote peer via TCP a ConnectionEstablished message. The remote
|
||||
peer receives this (on the TCP receiver process), tells the
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
# Weave Net Benchmarks
|
||||
|
||||
## 2017-02-16: Fast Datapath Encryption
|
||||
|
||||
Results: https://www.weave.works/weave-net-performance-fast-datapath/
|
||||
|
||||
* Two `c3.8xlarge` instances running Ubuntu 16.04 LTS (`ami-d8f4deab`) in
|
||||
`eu-west-1` region of AWS.
|
||||
* 10 Gigabit [Enhanced Networking](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html).
|
||||
* `iperf 3.0.11`.
|
||||
|
||||
### fastdp
|
||||
|
||||
* Encrypted:
|
||||
|
||||
```
|
||||
host1$ WEAVE_MTU=8916 weave launch --password=foobar
|
||||
host1$ docker $(weave config) run --rm -ti --name server networkstatic/iperf3 -s
|
||||
|
||||
host2$ WEAVE_MTU=8916 weave launch --password=foobar host1
|
||||
host2$ docker $(weave config) run --rm -ti networkstatic/iperf3 -c server
|
||||
```
|
||||
|
||||
* Non-encrypted:
|
||||
|
||||
```
|
||||
host1$ WEAVE_MTU=8950 weave launch
|
||||
host1$ docker $(weave config) run --rm -ti --name server networkstatic/iperf3 -s
|
||||
|
||||
host2$ WEAVE_MTU=8950 weave launch --password=foobar host1
|
||||
host2$ docker $(weave config) run --rm -ti networkstatic/iperf3 -c server
|
||||
```
|
||||
|
||||
### sleeve
|
||||
|
||||
* Encrypted:
|
||||
|
||||
```
|
||||
host1$ WEAVE_NO_FASTDP=1 weave launch --password=foobar
|
||||
host1$ docker $(weave config) run --rm -ti --name server networkstatic/iperf3 -s
|
||||
|
||||
host2$ WEAVE_NO_FASTDP=1 weave launch --password=foobar host1
|
||||
host2$ docker $(weave config) run --rm -ti networkstatic/iperf3 -c server
|
||||
```
|
||||
|
||||
* Non-encrypted:
|
||||
|
||||
```
|
||||
host1$ WEAVE_NO_FASTDP=1 weave launch
|
||||
host1$ docker $(weave config) run --rm -ti --name server networkstatic/iperf3 -s
|
||||
|
||||
host2$ WEAVE_NO_FASTDP=1 weave launch --password=foobar host1
|
||||
host2$ docker $(weave config) run --rm -ti networkstatic/iperf3 -c server
|
||||
```
|
||||
|
||||
### host
|
||||
|
||||
* Non-encrypted:
|
||||
|
||||
```
|
||||
host1$ iperf -s
|
||||
host2$ iperf -c host1
|
||||
```
|
||||
|
||||
* Encrypted:
|
||||
|
||||
```
|
||||
host1$ export KEY1="0x466454f2b1a770f8f872f9afbc35ebeac57e00fc11ac86ed1f82716f010b20f0cf532274"
|
||||
host1$ export KEY2="0x3531151241a770f8f872f9afbc35ebeac57e00fc11ac86ed1f82716f010b20f0cf532274"
|
||||
host1# ip xfrm state add src ${IP1} dst ${IP2} proto esp spi 0x4c856ffc replay-window 256 flag esn reqid 0 mode transport aead 'rfc4106(gcm(aes))' ${KEY1} 128
|
||||
host1# ip xfrm state add src ${IP2} dst ${IP1} proto esp spi 0x9b0830bc replay-window 256 flag esn reqid 0 mode transport aead 'rfc4106(gcm(aes))' ${KEY2} 128
|
||||
host1# ip xfrm policy add src ${IP1}/32 dst ${IP2}/32 dir out tmpl src ${IP1} dst ${IP2} proto esp spi 0x4c856ffc reqid 0 mode transport
|
||||
host1$ iperf -s
|
||||
|
||||
host2$ export KEY1="0x466454f2b1a770f8f872f9afbc35ebeac57e00fc11ac86ed1f82716f010b20f0cf532274"
|
||||
host2$ export KEY2="0x3531151241a770f8f872f9afbc35ebeac57e00fc11ac86ed1f82716f010b20f0cf532274"
|
||||
host2# ip xfrm state add src ${IP2} dst ${IP1} proto esp spi 0x9b0830bc reqid 0 replay-window 256 flag esn mode transport aead 'rfc4106(gcm(aes))' ${KEY2} 128
|
||||
host2# ip xfrm state add src ${IP1} dst ${IP2} proto esp spi 0x4c856ffc reqid 0 replay-window 256 flag esn mode transport aead 'rfc4106(gcm(aes))' ${KEY1} 128
|
||||
host2# ip xfrm policy add src ${IP2}/32 dst ${IP1}/32 dir out tmpl src ${IP1} dst ${IP2} proto esp spi 0x9b0830bc reqid 0 mode transport
|
||||
host2$ iperf -c host1
|
||||
```
|
||||
+2
-2
@@ -125,8 +125,8 @@ VM2> nc 192.168.122.2 6783
|
||||
If all is well, you should now be able to launch weave on VM1
|
||||
then on VM2:
|
||||
|
||||
VM1> weaver --iface veth6 --name vm1
|
||||
VM2> weaver --iface veth6 --name vm2 192.168.122.2
|
||||
VM1> weaver -iface veth6 -name vm1
|
||||
VM2> weaver -iface veth6 -name vm2 192.168.122.2
|
||||
|
||||
Once the connection is up and running, you should be able to connect
|
||||
from VM1/ns2 to VM2/ns2 and vice versa:
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
# Entrypoints
|
||||
|
||||
This document describes the various ways that weave as a binary gets launched, and where and how it processes options.
|
||||
|
||||
The goal is to enable maintainers to modify functionality without having to reverse engineer the code to find all of the places you can start a weave binary and what it depends upon.
|
||||
|
||||
## Components
|
||||
### weaver
|
||||
|
||||
`weaver` is the primary binary: it sets up the weave bridge to which containers are attached, manages IP address allocation, serves DNS requests and does many more things.
|
||||
|
||||
Since version 2.0, `weaver` also bundles the Docker Plugin (legacy version) and Docker API Proxy which used to be shipped as separate containers.
|
||||
|
||||
### weaveutil
|
||||
weaveutil is a binary which provides a number of functions for managing an existing weave network. It gets information about the network, attaches and detaches containers, etc.
|
||||
|
||||
For the majority of interactions with an existing weave network, you will launch `weaveutil` in some manner or another.
|
||||
|
||||
Almost all options can be passed as `--option` to `weaveutil`. These options are created in `prog/weaveutil/main.go` with a table mapping each command to a dedicated golang function.
|
||||
|
||||
In addition to operating in normal mode, `weaveutil` has several additional operating modes. Before processing any commands, `weaveutil` checks the filename with which it was called.
|
||||
|
||||
* If it was `weave-ipam` it delegates responsibility to the `cniIPAM()` function in `prog/weaveutil/cni.go`, which, in turn, calls the standard CNI plugin function `cni.PluginMain()`, passing it weave's IPAM implementation from `plugin/ipam`.
|
||||
* If it was `weave-net` it delegates responsibility to the `cniNet()` function in `prog/weaveutil/cni.go`, which, in turn, calls the standard CNI plugin function `cni.PluginMain()`, passing it weave's net plugin implementation from `plugin/net`.
|
||||
|
||||
Finally, `weaveutil` is called by `weaver` when it needs to operate in a different network namespace.
|
||||
Go programs cannot safely switch namespaces; see this [issue](http://github.com/vishvananda/netns/issues/17).
|
||||
The call chain is `weaver`->`nsenter`->`weaveutil`.
|
||||
|
||||
### weave
|
||||
Wrapping `weaver` and `weaveutil` is `weave`, a `sh` script that provides help information and calls `weaveutil` as relevant.
|
||||
|
||||
### weave-kube
|
||||
weave-kube is an image with the weave binaries and a wrapper script installed. It is responsible for:
|
||||
|
||||
1. Setting up the weave network
|
||||
2. Connecting to peers
|
||||
3. Copy the weave CNI-compatible binary plugin `weaveutil` to `/opt/cni/bin/weave-net` and weave config in `/etc/cni/net.d/10-weave.conflist`
|
||||
4. Running the weave network policy controller (weave-npc) to implement kubernetes' `NetworkPolicy`
|
||||
|
||||
Once installation is complete, each network-relevant change in a container leads `kubelet` to:
|
||||
|
||||
1. Set certain environment variables to configure the CNI plugin, mostly related to the container ID
|
||||
2. Launch `/opt/cni/bin/weave-net`
|
||||
3. Pass the CNI config - the contents of `/etc/cni/net.d/10-weave.conflist` to `weave-net`
|
||||
|
||||
Thus, when each container is changed, and `kubelet` calls weave as a CNI plugin, it really just is launching `weaveutil` as `weave-net`.
|
||||
|
||||
The installation and setup of all of the above - and therefore the entrypoint to the weave-kube image - is the script `prog/weave-kube/launch.sh`. `launch.sh` does the following:
|
||||
|
||||
1. Read configuration variables from the environment. When the documentation for `weave-kube` describes configuring the weave network by changing the environment variables in the daemonset in the `.yml` file, `launch.sh` reads these environment variables.
|
||||
2. Set up the config file at `/etc/cni/net.d/10-weave.conflist`
|
||||
3. Run the `weave` initialization script
|
||||
4. Run `weaver` with the correct configuration passed as command-line options
|
||||
|
||||
|
||||
## Adding Options
|
||||
To add new options to how weave should run with each invocation, you would do the following:
|
||||
|
||||
1. determine to which `weave` command you want to add the option(s). `weave` normally is launched as `weave <command> <options> ...`.
|
||||
2. Add the option to `weave` script help for each `weave` command you wish to make it available. As of this writing, all of the commands and their options are listed in the function `usage_no_exist()`.
|
||||
3. Add the option to `weave` script option processing for the `weave` command, under the `case $COMMAND in` in the main `weave` script.
|
||||
4. In the function for the `weave` command, determine if the command should be passed on to `weaveutil` or `weaver`. Pass the option on to `weaveutil` or `weaver`, as appropriate, in the format `--option`, in the function for the `weave` command.
|
||||
5. Add a command-line option `--option` to `weaveutil` or `weaver` as appropriate.
|
||||
6. If the option can be configured for CNI:
|
||||
* have `prog/weave-kube/launch.sh` read it as an environment variable and set inside
|
||||
* set a default for the environment variable in `prog/weave-kube/weave-daemonset-k8s-N.N.yaml`
|
||||
* if it should be set via `weaver` globally on its one-time initialization invocation, pass it on in `launch.sh`
|
||||
* if it needs to be set via `weaveutil` on each invocation of `weave-net`, have `launch.sh` save it as an option in the CNI config file `/etc/cni/net.d/10-weave.conflist` and then have the CNI code in `plugin/net/cni.go` in `weaveutil` read it and use where appropriate
|
||||
7. Document it!
|
||||
@@ -1,155 +0,0 @@
|
||||
This document describes implementation details of the fast datapath encryption.
|
||||
|
||||
# Overview
|
||||
|
||||
At the high level, we use the ESP protocol ([RFC 2406][esp]) in the Transport
|
||||
mode. Each packet is encrypted with AES in GCM mode ([RFC 4106][aesgcm]), with
|
||||
32 byte key and 4 byte salt. This combo provides the following security
|
||||
properties:
|
||||
|
||||
* Data confidentiality.
|
||||
* Data origin authentication.
|
||||
* Integrity.
|
||||
* Anti-replay.
|
||||
* Limited traffic flow confidentiality as fast datapath VXLAN packets are fully
|
||||
encrypted.
|
||||
|
||||
## Notation
|
||||
|
||||
* `SAin`: IPsec security association for inbound connections.
|
||||
* `SAout`: IPsec security association for outbound connections.
|
||||
* `SPout`: IPsec security policy for outbound connections. Used to match
|
||||
outbound flows to SAout.
|
||||
|
||||
## SA Key Derivation
|
||||
|
||||
For each connection direction, a different AES-GCM key and salt is used.
|
||||
The pairs are derived with HKDF ([RFC 5869][hkdf]):
|
||||
|
||||
```
|
||||
SAin[KeyAndSalt] = HKDF(sha256, ikm=sessionKey, salt=nonceIn, info=localPeerName)
|
||||
SAout[KeyAndSalt] = HKDF(sha256, ikm=sessionKey, salt=nonceOut, info=remotePeerName)
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
* the mutual `sessionKey` is derived by the [Mesh][mesh] library during
|
||||
the control plane connection establishment between the local peer and
|
||||
the remote peer.
|
||||
* `nonceIn` and `nonceOut` are randomly generated 32byte nonces which
|
||||
are exchanged over the encrypted control plane channel.
|
||||
|
||||
## SPI
|
||||
|
||||
A directional secure connection between two peers is identified with SPI.
|
||||
|
||||
The kernel requires the pair of SPI and dst IP to be unique among security
|
||||
associations. Thus, to avoid collisions, we generate outbound SPI on a remote
|
||||
peer.
|
||||
|
||||
## Connection Establishment
|
||||
|
||||
```
|
||||
Peer A Peer B
|
||||
-----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
fastdp.fwd.Confirm():
|
||||
install iptables blocking rules, fastdp.fwd.Confirm():
|
||||
nonce_BA = rand(), install iptables blocking rules,
|
||||
{key,salt}_BA = hkdf(sessionKey, nonce_BA, A), nonce_AB = rand(),
|
||||
spi_BA = allocspi(), {key,salt}_AB = hkdf(sessionKey, nonce_AB, B),
|
||||
create SA_BA(B<-A, spi_BA, key_BA, salt_BA), spi_AB = allocspi(),
|
||||
send InitSARemote(spi_BA, nonce_BA). --> create SA_AB(A<-B, spi_AB, key_AB, salt_AB),
|
||||
<-- send InitSARemote(spi_AB, nonce_AB).
|
||||
|
||||
|
||||
--> recv InitSARemote(spi_BA, nonce_BA):
|
||||
recv InitSARemote(spi_AB, nonce_AB): <-- {key,salt}_BA = hkdf(sessionKey, nonce_BA, A),
|
||||
{key,salt}_AB = hkdf(sessionKey, nonce_AB, B), create SA_BA(B<-A, spi_BA, key_BA, salt_BA),
|
||||
create SA_AB(A<-B, spi_AB, key_AB, salt_AB), create SP_BA(B<-A, spi_BA),
|
||||
create SP_AB(A<-B, spi_AB), install marking rule.
|
||||
install marking rule.
|
||||
```
|
||||
|
||||
# Implementation Details
|
||||
|
||||
## XFRM
|
||||
|
||||
The implementation is based on the kernel IP packet transformation framework
|
||||
called XFRM. Unfortunately, docs are barely existing and the complexity of
|
||||
the framework is high. The best resource I found is Chapter 10 in
|
||||
"Linux Kernel Networking: Implementation and Theory" by Rami Rosen.
|
||||
|
||||
## iptables Rules
|
||||
|
||||
The kernel VXLAN driver does not set a dst port of a tunnel in the ip flow
|
||||
descriptor, thus XFRM policy lookup cannot match a policy (SPout) which includes
|
||||
the port. This makes impossible to encrypt only tunneled traffic between
|
||||
peers. To work around, we mark such outgoing packets with iptables and set
|
||||
the same mark in the policy selector (funnily enough, `iptables_mangle` module
|
||||
eventually sets the missing dst port in the flow descriptor). The challenge
|
||||
here is to pick a mark that it would not interfere with other networking
|
||||
applications before OUTPUT'ing a packet. For example, Kubernetes by default
|
||||
uses 1<<14 and 1<<15 marks and we choose 1<<17 (0x20000). Additionally,
|
||||
such workaround brings the requirement for at least the 4.2 kernel.
|
||||
|
||||
The marking rules are the following:
|
||||
|
||||
```
|
||||
iptables -t mangle -A OUTPUT -j WEAVE-IPSEC-OUT
|
||||
iptables -t mangle -A WEAVE-IPSEC-OUT -s ${LOCAL_PEER_IP} -d ${REMOTE_PEER_IP} \
|
||||
-p udp --dport ${TUNNEL_PORT} -j WEAVE-IPSEC-OUT-MARK
|
||||
iptables -t mangle -A WEAVE-IPSEC-OUT-MARK --set-xmark ${MARK} -j MARK
|
||||
```
|
||||
|
||||
As Linux does not implement [IP Security Levels][ipseclevel], we install
|
||||
additional iptables rules to prevent from accidentally sending unencrypted
|
||||
traffic between peers which have previously established the secure connection.
|
||||
|
||||
For inbound traffic, we mark each ESP packet with the mark and drop non-marked
|
||||
tunnel traffic:
|
||||
|
||||
```
|
||||
iptables -t mangle -A INPUT -j WEAVE-IPSEC-IN
|
||||
iptables -t mangle -A WEAVE-IPSEC-IN -s ${REMOTE_PEER_IP} -d ${LOCAL_PEER_IP} \
|
||||
-m esp --espspi ${SPIin} -j WEAVE-IPSEC-IN-MARK
|
||||
iptables -t mangle -A WEAVE-IPSEC-IN-MARK --set-xmark ${MARK} -j MARK
|
||||
|
||||
iptables -t filter -A INPUT -j WEAVE-IPSEC-IN
|
||||
iptables -t filter -A WEAVE-IPSEC-IN -s ${REMOTE_PEER_IP} -d ${LOCAL_PEER_IP} \
|
||||
-p udp --dport ${TUNNEL_PORT} -m mark ! --mark ${MARK} -j DROP
|
||||
```
|
||||
|
||||
For outbound traffic, we drop marked traffic which does not match any SPout:
|
||||
|
||||
```
|
||||
iptables -t filter -A OUTPUT ! -p esp -m policy --dir out --pol none \
|
||||
-m mark --mark ${MARK} -j DROP
|
||||
```
|
||||
|
||||
## ESN
|
||||
|
||||
To prevent from cycling SeqNo which makes replay attacks possible, we use
|
||||
64-bit extended sequence numbers known as [ESN](esn).
|
||||
|
||||
## MTU
|
||||
|
||||
In addition to the VXLAN overhead, the MTU calculation should take into account
|
||||
the ESP overhead which is 34-37 bytes (encrypted Payload is 4 bytes aligned) and
|
||||
consists of:
|
||||
|
||||
* 4 bytes (SPI).
|
||||
* 4 bytes (Sequence Number).
|
||||
* 8 bytes (ESP IV).
|
||||
* 1 byte (Pad Length).
|
||||
* 1 byte (NextHeader).
|
||||
* 16 bytes (ICV).
|
||||
* 0-3 bytes (Padding).
|
||||
|
||||
|
||||
[esp]: https://tools.ietf.org/html/rfc2406
|
||||
[aesgcm]: https://tools.ietf.org/html/rfc4106
|
||||
[hkdf]: https://tools.ietf.org/html/rfc5869
|
||||
[esn]: https://tools.ietf.org/html/rfc4304
|
||||
[ipseclevel]: https://tools.ietf.org/html/draft-mcdonald-simple-ipsec-api-01)
|
||||
[mesh]: https://github.com/weaveworks/mesh
|
||||
-170
@@ -1,170 +0,0 @@
|
||||
# Overlay Method Selection
|
||||
|
||||
Weave automatically selects the best working overlay implementation
|
||||
when connecting to a new peer. It does this by initiating both
|
||||
forwarders in parallel, eventually settling on the most preferred
|
||||
established forwarder at which point the other forwarder is shut down,
|
||||
assuming it had not failed already. In the event that the remaining
|
||||
forwarder fails at a later time (e.g. because of a heartbeat timeout)
|
||||
the control plane TCP connection will recycle allowing the selection
|
||||
algorithm to run again. By default fast datapath is preferred over
|
||||
Sleeve.
|
||||
|
||||
# Local Bridging
|
||||
|
||||
Unlike the weave bridge netdev used by Sleeve, the OVS datapath has no
|
||||
inherent bridging capability. Consequently fastdp implements an
|
||||
ethernet bridge in addition to the vxlan overlay, maintaining its own
|
||||
forwarding database by learning MAC addresses and dispatching
|
||||
broadcast, unicast & multicast traffic accordingly.
|
||||
|
||||
# Short Peer IDs
|
||||
|
||||
The router relies on having access to the source and destination peer
|
||||
when deciding how to forward packets. When the Sleeve overlay is in
|
||||
use this information is conveyed directly within the encapsulation by
|
||||
including the names of the peers in question, a solution not
|
||||
accommodated directly by the vxlan wire format. Fortunately vxlan does
|
||||
have a twenty four bit segment ID field in the header we can use to
|
||||
encode this data - the challenge is to identify peers uniquely with a
|
||||
twelve bit identifier instead of the seventeen bytes used by Sleeve.
|
||||
In practice this is achieved by peers adopting a random 'short ID' and
|
||||
resolving ownership collisions of same via the existing gossip
|
||||
mechanism.
|
||||
|
||||
# Heartbeats via vxlan
|
||||
|
||||
Heartbeats are implemented by sending an encapsulated ethernet frame
|
||||
with source and destination MAC addresses of `00:00:00:00:00:00` and a
|
||||
payload consisting of the connection UID and total ethernet frame
|
||||
length. The vxlan vport miss handler on the receiving side detects the
|
||||
all-zero addresses and acknowledges the heartbeat via the TCP control
|
||||
channel after validating the connection UID and frame length against
|
||||
their expected values.
|
||||
|
||||
# PMTU Discovery
|
||||
|
||||
The length of the heartbeat ethernet frame is set to the MTU of the
|
||||
datapath interface. In the event the vxlan packet is dropped or
|
||||
truncated, the heartbeat will not be acknowledged; this lack of
|
||||
acknowledgement will cause the peers to fall back to the Sleeve
|
||||
overlay, which has a more sophisticated dynamic mechanism for coping
|
||||
with low path MTUs.
|
||||
|
||||
To avoid triggering this fallback in typical deployments, the datapath
|
||||
interface is statically configured with an MTU of 1376 bytes allowing
|
||||
it to work with most underlay network provider MTUs, including GCE at
|
||||
1460 bytes (the eighty four byte difference accommodates the encrypted vxlan overhead).
|
||||
This value can be overridden by setting `WEAVE_MTU` at launch if
|
||||
necessary.
|
||||
|
||||
# Virtual Ports
|
||||
|
||||
There are three kinds of virtual port associated with the weave
|
||||
datapath:
|
||||
|
||||
* `internal` - one of these is created automatically, named after the
|
||||
datapath. It corresponds to the network interface of the same name
|
||||
that appears on the host when the datapath is created; it is the
|
||||
ingress/egress port for `weave expose`
|
||||
* `netdev` - one for each application container, corresponding to the
|
||||
host end of the veth pair
|
||||
* `vxlan` - one for each UDP port on which we are listening for vxlan
|
||||
encapsulated packets. Typically there is only one of these, but
|
||||
there may be more in a network in which peers do not all use the same
|
||||
port configuration
|
||||
|
||||
# Misses and Flow Creation
|
||||
|
||||
As mentioned above, the datapath has no inherent behaviour - any
|
||||
ingressing packet which does not match a flow is passed to a userspace
|
||||
miss handler. The miss handler is then responsible for a) instructing
|
||||
the datapath to take actions for the packet that caused the miss (for
|
||||
example by copying it to one or more ports) and optionally b)
|
||||
installing flow rules which will allow the datapath to act on similar
|
||||
packets in future without invoking the miss handler.
|
||||
|
||||
At a high level the fast datapath implementation can be viewed as a
|
||||
set of miss handlers that determine what actions the OVS datapath
|
||||
should take based on router topology information, together with some
|
||||
additional machinery that manages the expiry of resulting flows when
|
||||
that state changes.
|
||||
|
||||
When a miss handler is invoked, it has two pieces of context: a byte
|
||||
array containing the packet that triggered the miss, and a set of
|
||||
'flow keys' that have been extracted from the packet by the kernel
|
||||
module. The following flow keys are of interest to weave:
|
||||
|
||||
* `InPort` - the identifier of the ingress virtual port
|
||||
* `Ethernet` - the source and destination MAC addresses of the
|
||||
ethernet frame
|
||||
* `Tunnel` - tunnel identifier (see section on short peer IDs above),
|
||||
and source/dest IPv4 addresses. Only present for ingress via a vxlan
|
||||
vport
|
||||
|
||||
Crucially, the router must use this information alone to determine
|
||||
which actions to take - this allows the specification of a flow which
|
||||
matches these keys and instructs the kernel to take action
|
||||
automatically in future without further userspace involvement.
|
||||
|
||||
Two actions are of interest:
|
||||
|
||||
* `Output` - output the packet to the specified vport
|
||||
* `SetTunnel` - update the effective vxlan tunnel parameters. Only
|
||||
required prior to output from a vxlan vport
|
||||
|
||||
It is possible to have multiple actions in a flow, so the router can
|
||||
for example create a single rule that copies broadcast traffic to all
|
||||
'local' vports (save the ingress vport obviously) as well as to vxlan
|
||||
vports for onward peers as dictated by the routing topology.
|
||||
|
||||
Every flow specified by the router has the following characteristics:
|
||||
|
||||
* An `InPort` key matching the ingress vport
|
||||
* A `Tunnel` key if the ingress vport is of type vxlan
|
||||
* An `Ethernet` key matching the source and destination MAC
|
||||
* A list of `Output` (for internal and netdev vports) and
|
||||
`SetTunnel`+`Output` (for vxlan vports) actions
|
||||
|
||||
Under certain circumstances the miss handler will instruct the
|
||||
datapath to execute a set of actions for a packet without creating a
|
||||
corresponding flow:
|
||||
|
||||
* When broadcasting packets to discover the path to unknown
|
||||
destination MACs. This condition is transient in the overwhelming
|
||||
majority of cases and so the benefit of creating a flow is
|
||||
outweighed by the need to invalidate it once the MAC is learned
|
||||
* When the packet also needs to be forwarded to one or more peers via
|
||||
Sleeve. In this case the router needs to handle all subsequent
|
||||
matching packets as there is no OVS flow action to do it without
|
||||
userspace involvement
|
||||
|
||||
# Flow Invalidation
|
||||
|
||||
Once a flow has been created for a particular combination of keys, the
|
||||
miss handler will never again be invoked for matching packets. It is
|
||||
therefore extremely important that we detect events which invalidate
|
||||
existing flow actions:
|
||||
|
||||
* Addition of netdev vports to the datapath
|
||||
* Route invalidation (topology change)
|
||||
* Short peer ID collision
|
||||
|
||||
The response in each case is the same - delete all flows from the
|
||||
datapath, allowing them to be recreated taking into account the
|
||||
updated state.
|
||||
|
||||
In addition to these event based invalidations there is an expiry
|
||||
process that executes every five minutes. This process enumerates all
|
||||
flows in the datapath, removing any which have not been used since the
|
||||
last check; this cleans up:
|
||||
|
||||
* Flows referring to netdev vports which have been removed
|
||||
* Flows created by forwarders which have been stopped
|
||||
* Flows related to MAC addresses which have not communicated recently
|
||||
|
||||
Flows keyed on tunnel IPv4 address do not need to be cleared when a
|
||||
peer appears to change IP address due to NAT; this will cause a miss
|
||||
resulting in a new flow, and the old flow will expire naturally via
|
||||
the timer mechanism.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 276 KiB |
+18
-32
@@ -20,16 +20,11 @@ plugin) uses to request IP addresses.
|
||||
|
||||
The commands supported by the allocator via the http interface are:
|
||||
|
||||
- Allocate: request one IP address for a container in a subnet
|
||||
- Lookup: fetch the previously-allocated IP address for a container in a subnet
|
||||
- Allocate: request one IP address for a container
|
||||
- Free: return an IP address that is currently allocated
|
||||
- Claim: request a specific IP address for a container (e.g. because
|
||||
it is already using that address)
|
||||
|
||||
Each http request either specifies a subnet, or if no subnet is
|
||||
specified this is taken as a request to allocate in a pre-defined
|
||||
default subnet.
|
||||
|
||||
The allocator also watches via the Docker event mechanism: if a
|
||||
container dies then all IP addresses allocated to that container are
|
||||
freed.
|
||||
@@ -53,42 +48,25 @@ freed.
|
||||
|
||||
When a peer owns some range(s), it can allocate freely from within
|
||||
those ranges to containers on the same machine. If it runs out of
|
||||
space in a subnet (all owned ranges are full), it will ask another
|
||||
peer for space:
|
||||
space (all owned ranges are full), it will ask another peer for space:
|
||||
- it picks a peer to ask at random, weighted by the amount of space
|
||||
owned by each peer in the subnet
|
||||
owned by each peer
|
||||
- if the target peer decides to give up space, it unicasts a message
|
||||
back to the asker with the newly-updated ring.
|
||||
- if the target peer has no space, it unicasts a message back to the
|
||||
asker with its current copy of the ring, on the basis that the
|
||||
requestor must have acted on out-of-date information.
|
||||
- it will continue to ask for space until it receives some, or its
|
||||
copy of the ring tells it all peers are full in that subnet.
|
||||
|
||||
### Data persistence
|
||||
|
||||
Key IPAM data is saved to disk, in a [BoltDB](https://github.com/boltdb/bolt)
|
||||
file, stored within a [data volume container](https://docs.docker.com/engine/userguide/containers/dockervolumes/#creating-and-mounting-a-data-volume-container)
|
||||
named `weavedb`.
|
||||
|
||||
This file is used to persist data for various Weave Net components; for IPAM it contains:
|
||||
|
||||
* the division of the IP allocation range amongst peers, and
|
||||
* allocation of addresses to containers on the local peer,
|
||||
|
||||
so that it is immediately available when the peer restarts.
|
||||
copy of the ring tells it all peers are full.
|
||||
|
||||
### Claiming an address
|
||||
|
||||
If a Weave process is restarted,
|
||||
|
||||
- if `weavedb` is present, then it loads persisted IPAM data from there, as
|
||||
described, in the previous section;
|
||||
- else, it learns from other peers which ranges it used to own.
|
||||
|
||||
Weave Net then consults the allocator to know which individual IP addresses
|
||||
are assigned to containers, and therefore avoid giving the same address out in
|
||||
subsequent allocation requests.
|
||||
If a Weave process is restarted, in most cases it will hear from
|
||||
another peer which ranges it used to own, but it needs to know which
|
||||
individual IP addresses are assigned to containers in order to avoid
|
||||
giving the same address out in subsequent allocation requests. The
|
||||
weave script invokes the `claim` command in `populate_ipam` to do
|
||||
this.
|
||||
|
||||
When the Allocator is told to claim an address, there are four
|
||||
possibilities:
|
||||
@@ -226,3 +204,11 @@ reached the peer that does the `rmpeer`, then we get an inconsistent
|
||||
ring. We could make it safe, up to partitions, by inquiring of every
|
||||
live peer what its view of the dead peer is, and making sure we use
|
||||
the latest info.)
|
||||
|
||||
## Limitations
|
||||
|
||||
Nothing is persisted. If one peer restarts it should receive gossip
|
||||
from other peers showing what it used to own, and in that case it can
|
||||
recover quite well. If, however, there are no peers left alive, or
|
||||
this peer cannot establish network communication with those that are,
|
||||
then it cannot recover.
|
||||
@@ -1,33 +0,0 @@
|
||||
# Source IP addr of packet sent to k8s Service
|
||||
|
||||
## Setup
|
||||
|
||||
* Weave Net: v2.2.1.
|
||||
* Kubernetes: v1.10.0.
|
||||
* kube-proxy: iptables/ipvs mode with `--masquerade-all=false` and `--cluster-cidr` unspecified(default kubeadm options).
|
||||
* Hosts: `h1` and `h2`.
|
||||
|
||||
## Source IP
|
||||
|
||||
\# | Src | Target | Dst | Src IP | `-j WEAVE-NPC`
|
||||
---|-------------|-------------------|--------|------------------|--------------
|
||||
1 | Pod_h1 | ip(Pod_h2) | Pod_h2 | ip(Pod_h1) | OK
|
||||
2 | h1 | ClusterIP | Pod_h1 | ip(weave_h1) | **NOK**
|
||||
3 | h1 | ClusterIP | Pod_h2 | ip(weave_h1) | OK
|
||||
4 | Pod_h1 | ClusterIP | Pod_h1 | ip(Pod_h1) | OK
|
||||
5 | Pod_h1 | ClusterIP | Pod_h2 | ip(weave_h1) | OK
|
||||
6 | h1 | ip(h1):NodePort | Pod_h1 | ip(weave_h1) | **NOK**
|
||||
7 | h1 | ip(h1):NodePort | Pod_h2 | ip(weave_h1) | OK
|
||||
8 | h1 | ip(h2):NodePort | Pod_h1 | ip(weave_h2) | OK
|
||||
9 | h1 | ip(h2):NodePort | Pod_h2 | ip(weave_h2) | ??? Can't reproduce
|
||||
10 | Pod_h1 | ip(h1):NodePort | Pod_h1 | ip(weave_h1) | **NOK**
|
||||
11 | Pod_h1 | ip(h1):NodePort | Pod_h2 | ip(weave_h1) | OK
|
||||
12 | Pod_h1 | ip(h2):NodePort | Pod_h1 | ip(weave_h2) | OK
|
||||
13 | Pod_h1 | ip(h2):NodePort | Pod_h2 | ip(weave_h2) | OK
|
||||
|
||||
Remarks:
|
||||
|
||||
* **Src IP** is of a packet which is captured on the weave bridge.
|
||||
* **-j WEAVE-NPC** - whether a packet enters the `filter/WEAVE-NPC` iptables chain (OK = NetworkPolicy is enforced as required).
|
||||
* **Pod_h1** - a Pod running on the `h1` host.
|
||||
* **ip(weave_h1)** - IP addr of the weave bridge on the `h1` host.
|
||||
@@ -1,198 +0,0 @@
|
||||
# Release Process
|
||||
## Prerequisites
|
||||
|
||||
* Up-to-date versions of `git`, `go` etc
|
||||
* Install GitHub release tool `go get github.com/weaveworks/github-release`
|
||||
* Install manifest tool `go get github.com/estesp/manifest-tool`
|
||||
* Create a [github token for
|
||||
github-release](https://help.github.com/articles/creating-an-access-token-for-command-line-use/); select the `repo` OAuth scope; set and export `$GITHUB_TOKEN` with this value
|
||||
|
||||
## Release Types
|
||||
|
||||
The release script behaves differently depending on the kind of
|
||||
release you are doing. There are three types:
|
||||
|
||||
* **Mainline** - a release (typically from master) with version tag
|
||||
`vX.Y.Z` where Z is zero (e.g. `v2.1.0`)
|
||||
* **Branch** - a bugfix release (typically from a branch) with version tag
|
||||
`vX.Y.Z` where Z is non-zero (e.g `v2.1.1`)
|
||||
* **Prerelease** - a release from an arbitrary branch with an arbitrary
|
||||
version tag (e.g. `feature-preview-20150904`)
|
||||
|
||||
N.B. the release script _infers the release type from the format of the
|
||||
version tag_. Ensure your tag is in the correct format and the desired
|
||||
behaviours for that type of release will be obtained from the script.
|
||||
|
||||
## Build Phase
|
||||
### Update CHANGELOG.md
|
||||
|
||||
* Checkout the branch from which you wish to release
|
||||
* Choose a version tag (see above) henceforth referred to as `$TAG`.
|
||||
* Add a changelog entry for the new tag at the top of `CHANGELOG.md`.
|
||||
The first line must be a markdown header of the form `## Release
|
||||
$TAG` for **Prerelease** builds, `## Release ${TAG#v}` otherwise.
|
||||
|
||||
Commit the changelog update:
|
||||
|
||||
git commit -m "Add release $TAG" CHANGELOG.md
|
||||
|
||||
### Create Version Tag
|
||||
|
||||
Next you must tag the changelog commit with `$TAG`
|
||||
|
||||
git tag -a -m "Release $TAG" $TAG
|
||||
|
||||
### Execute Build
|
||||
|
||||
You are now ready to perform the build. If you have skipped the
|
||||
previous steps (e.g. because you're doing a rebuild), you must ensure
|
||||
that `HEAD` points to the tagged commit. You may then execute
|
||||
|
||||
bin/release build
|
||||
|
||||
This has the following effects:
|
||||
|
||||
* `git tag --points-at HEAD` is used to determine `$TAG` (hence the
|
||||
`HEAD` requirement)
|
||||
* Your *local* repository is cloned into `releases/$TAG`
|
||||
* `CHANGELOG.md` is checked to ensure it has an entry for `$TAG`
|
||||
* Distributables injected with `$TAG` are built
|
||||
* Tests are executed
|
||||
|
||||
## Draft Phase
|
||||
### Push Version Tag Upstream
|
||||
|
||||
First you must push your branch and version tag upstream, so that an
|
||||
associated GitHub release may be created:
|
||||
|
||||
git push git@github.com:weaveworks/weave
|
||||
git push git@github.com:weaveworks/weave $TAG
|
||||
|
||||
N.B. if you're testing the release process, push to your fork
|
||||
instead!
|
||||
|
||||
### Create Draft Release
|
||||
|
||||
You're now ready to draft your release notes:
|
||||
|
||||
bin/release draft
|
||||
|
||||
This has the following effects:
|
||||
|
||||
* A [release](https://help.github.com/articles/about-releases) is
|
||||
created in GitHub for `$TAG`. This release is in the draft state, so
|
||||
it is only visible to contributors; for **Prerelease** builds the
|
||||
pre-release attribute will also be set
|
||||
* The `weave` script is uploaded as an attachment to the release
|
||||
|
||||
Navigate to https://github.com/weaveworks/weave/releases, 'Edit' the
|
||||
draft and input the release notes. When you are done make sure you
|
||||
'Save draft' (and not 'Publish release'!).
|
||||
|
||||
Once the release notes have passed review, proceed to the publish
|
||||
phase.
|
||||
|
||||
## Publish Phase
|
||||
### Move/Force Push `latest_release` Tag
|
||||
|
||||
This step must only be performed for **Mainline** and **Branch**
|
||||
releases:
|
||||
|
||||
git tag -af -m "Release $TAG" latest_release $TAG
|
||||
git push -f git@github.com:weaveworks/weave latest_release
|
||||
|
||||
The `latest_release` tag *must* point at `$TAG`, *not* at `HEAD` -
|
||||
the build script will complain otherwise.
|
||||
|
||||
N.B. if you're testing the release process, push to your fork
|
||||
instead!
|
||||
|
||||
### Publish Release & Distributable Artefacts
|
||||
|
||||
You can now publish the release and upload the remaining
|
||||
distributables to DockerHub:
|
||||
|
||||
bin/release publish
|
||||
|
||||
The effects of this step depend on the inferred release type. The
|
||||
following occurs for all types:
|
||||
|
||||
* Docker images are tagged `$TAG` and pushed to DockerHub
|
||||
* GitHub release moves from draft to published state
|
||||
|
||||
Additionally, for **Mainline** and **Branch** types:
|
||||
|
||||
* Release named `latest_release` is updated on GitHub
|
||||
|
||||
Finally, for **Mainline** releases only:
|
||||
|
||||
* Images tagged `latest` are updated on DockerHub
|
||||
|
||||
Now, you can validate whether images were published for all platforms:
|
||||
|
||||
# Required platforms for each image:
|
||||
grep '^ML_PLATFORMS=' Makefile
|
||||
|
||||
# Published platforms per image:
|
||||
for img in $(grep '^PUBLISH=' Makefile); do
|
||||
img="weaveworks/$(echo $img | cut -d_ -f2):${TAG#v}"
|
||||
platforms=$(manifest-tool \
|
||||
inspect --raw "$img" | \
|
||||
jq '.[].Platform | .os + "/" +.architecture' | \
|
||||
tr '\n' ' ')
|
||||
echo "$img: $platforms"
|
||||
done
|
||||
|
||||
|
||||
### Docker Store
|
||||
|
||||
Weave Net is [available in Docker Store](https://store.docker.com/plugins/weave-net-plugin) and should also be released there.
|
||||
|
||||
* Go to https://store.docker.com/ and log in.
|
||||
* Go to "[_Publish_](https://store.docker.com/publisher/center)".
|
||||
* Under "_My Products_", select "_owners (weaveworks)_". Weave Net should be listed among our products.
|
||||
* Click "_Actions_" > "_Edit Product_".
|
||||
* Go to "_Plans & Pricing_" > "_Free Tier_".
|
||||
* Under "_Source Repositories & Tags_", click on "_Add Source_", select "_net-plugin_" under "_Repository_" and the version to release under "_Tag_".
|
||||
* Under "_Resources_" > "_Installation Instructions_", update the lines containing `export NET_VERSION=<version>`.
|
||||
* Click "_Save_"
|
||||
* Click "_Submit For Review_". You should see a message like: "_Your product has been submitted for approval. [...] We'll be in touch with next steps soon!_".
|
||||
* You should receive an email saying:
|
||||
|
||||
> This email confirms that we received your submission on \<date\> of Weave Net to the Docker Store.
|
||||
>
|
||||
> We're reviewing your submission to ensure that it meets our [security guidelines](https://success.docker.com/Store#Security_and_Audit_Policies) and complies with our [best practices](https://success.docker.com/Store#Create_Great_Content). Don't worry! We'll let you know if there's anything you need to change before we publish your submission. You should hear back from us within the next 14 days.
|
||||
>
|
||||
> Thanks for submitting your content to the Docker Store!
|
||||
|
||||
* Hope Docker eventually performs the release or contacts us. If nothing happens within 14 days, contact their support team: publisher-support@docker.com.
|
||||
|
||||
### Finish up
|
||||
|
||||
* If not on master, merge branch into master and push to GitHub.
|
||||
* Close the [milestone](https://github.com/weaveworks/weave/milestones) in GitHub and create the next milestone
|
||||
* Update the `#weavenetwork` topic heading on freenode (requires 'chanops' permission)
|
||||
* For a mainline release vX.Y.0, create a release branch X.Y from the
|
||||
tag, push to GitHub and set [WEAVE_NET_REV](https://github.com/weaveworks/website-next/blob/master/Makefile)
|
||||
to X.Y via a PR - this will result in X.Y.0 site docs being published to https://www.weave.works
|
||||
* Add the new version of `weave-net` to the checkpoint system at
|
||||
https://checkpoint-api.weave.works/admin
|
||||
* File a PR to update the version of the daemonset at
|
||||
https://github.com/kubernetes/kops/tree/master/upup/models/cloudup/resources/addons/networking.weave
|
||||
and kops/upup/pkg/fi/cloudup/bootstrapchannelbuilder.go
|
||||
and kops/upup/pkg/fi/cloudup/tests/bootstrapchannelbuilder/weave/manifest.yaml
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
There's a few things that can go wrong.
|
||||
|
||||
* If the build is wonky, e.g., the tests don't pass, you can delete
|
||||
the directory in `./releases/`, fix whatever it is, move the
|
||||
version tag (which should still be only local) and have another go.
|
||||
* If the DockerHub pushes fail (which sadly seems to happen a lot),
|
||||
you can just run `./bin/release publish` again.
|
||||
* If you need to overwrite a release you can do so by manually
|
||||
deleting the GitHub version release and re-running the process.
|
||||
Please note that the DockerHub `latest` images, GitHub
|
||||
`latest_release` and download links may be in an inconsistent state
|
||||
until the overwrite is completed.
|
||||
@@ -1,83 +0,0 @@
|
||||
---
|
||||
title: WeaveDNS (service discovery) Design Notes
|
||||
layout: default
|
||||
---
|
||||
|
||||
# WeaveDNS (service discovery) Design Notes
|
||||
|
||||
The model is that each host has a service that is notified of
|
||||
hostnames and weave addresses for containers on the host. Like IPAM,
|
||||
this service is embedded within the router. It binds to
|
||||
the host bridge to answer DNS queries from local containers; for
|
||||
anything it can't answer, it uses the information in the host's
|
||||
/etc/resolv.conf to query an 'fallback' server.
|
||||
|
||||
The service is comprised of a DNS server, which answers all DNS queries
|
||||
from containers, and a in-memory database of hostnames and IPs. The
|
||||
database on each node contains a complete copy of the hostnames and IPs
|
||||
for every containers in the cluster.
|
||||
|
||||
For hostname queries in the local domain (default weave.local), the DNS
|
||||
server will consult the in-memory database. For reverse queries, we
|
||||
first consult the local database, and if not found we query the
|
||||
upstream server. For all other queries, we consult the upstream
|
||||
server.
|
||||
|
||||
Updates to the in-memory database are broadcast to other DNS servers
|
||||
within the cluster. The in-memory database only contains entries from
|
||||
connected DNS servers; if a DNS server becomes partitioned from the
|
||||
cluster, entries belonging to that server are removed from each node in
|
||||
the cluster. When the partitioned DNS server reconnects, the entries
|
||||
are re-broadcast around the cluster.
|
||||
|
||||
The DNS server also listens to the Docker event stream, and removes
|
||||
entries for containers when they die. Entries removed in this way are
|
||||
tombstoned, and the tombstone lazily broadcast around the cluster.
|
||||
After a short timeout the tombstones are independently removed from
|
||||
each host.
|
||||
|
||||
|
||||
## DNS server API
|
||||
|
||||
The DNS server accepts HTTP requests on the following URL (patterns)
|
||||
and methods:
|
||||
|
||||
`PUT /name/<identifier>/<ip-address>`
|
||||
|
||||
Put a record for an IP, bound to a host-scoped identifier (e.g., a
|
||||
container ID), in the DNS database. The request body must contain
|
||||
a `fqdn=foo.weave.local` key pair.
|
||||
|
||||
`DELETE /name/<identifier>/<ip-address>`
|
||||
|
||||
Remove a specific record for an IP and host-scoped identifier. The request
|
||||
body can optionally contain a `fqdn=foo.weave.local` key pair.
|
||||
|
||||
`DELETE /name/<identifier>`
|
||||
|
||||
Remove all records for the host-scoped identifier.
|
||||
|
||||
`GET /name/<fqdn>`
|
||||
|
||||
List of all IPs (in JSON format) for givne FQDN.
|
||||
|
||||
## DNS updater
|
||||
|
||||
The updater component uses the Docker remote API to monitor containers
|
||||
coming and going, and tells the DNS server to update its records via
|
||||
its HTTP interface. It does not need to be attached to the weave
|
||||
network.
|
||||
|
||||
The updater starts by subscribing to the events, and getting a list of
|
||||
the current containers. Any containers given a domain ending with
|
||||
".weave" are considered for inclusion in the name database.
|
||||
|
||||
When it sees a container start or stop, the updater checks the weave
|
||||
network attachment of the container, and updates the DNS server.
|
||||
|
||||
> How does it check the network attachment from within a container?
|
||||
|
||||
> Will it need to delay slightly so that `attach` has a chance to run?
|
||||
> Perhaps it could put containers on a watch list when it's noticed
|
||||
> them.
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
# Overview
|
||||
|
||||
# ipsets
|
||||
|
||||
The policy controller maintains a number of ipsets which are
|
||||
subsequently referred to by the iptables rules used to effect network
|
||||
policy specifications. These ipsets are created, modified and
|
||||
destroyed automatically in response to Pod, Namespace and
|
||||
NetworkPolicy object updates from the k8s API server:
|
||||
|
||||
* A `hash:ip` set per namespace, containing the IP addresses of all
|
||||
pods in that namespace for which default ingress is allowed
|
||||
* A `hash:ip` set per namespace, containing the IP addresses of all
|
||||
pods in that namespace for which default egress is allowed
|
||||
* A `list:set` per distinct (across all network policies in all
|
||||
namespaces) namespace selector mentioned in a network policy,
|
||||
containing the names of any of the above hash:ip sets whose
|
||||
corresponding namespace labels match the selector
|
||||
* A `hash:ip` set for each distinct (within the scope of the
|
||||
containing network policy's namespace) pod selector mentioned in a
|
||||
network policy, containing the IP addresses of all pods in the
|
||||
namespace whose labels match that selector
|
||||
* A `hash:net` set for each distinct (within the scope of the
|
||||
containing network policy's namespace) `except` list of CIDR's mentioned in
|
||||
the network policies
|
||||
|
||||
IPsets are implemented by the kernel module `xt_set`, without which
|
||||
weave-npc will not work.
|
||||
|
||||
ipset names are generated deterministically from a string
|
||||
representation of the corresponding label selector. Because ipset
|
||||
names are limited to 31 characters in length, this is done by taking a
|
||||
SHA hash of the selector string and then printing that out as a base
|
||||
85 string with a "weave-" prefix e.g.:
|
||||
|
||||
weave-k?Z;25^M}|1s7P3|H9i;*;MhG
|
||||
|
||||
Because pod selectors are scoped to a namespace, we need to make sure
|
||||
that if the same selector definition is used in different namespaces
|
||||
that we maintain distinct ipsets. Consequently, for such selectors the
|
||||
namespace name is prepended to the label selector string before
|
||||
hashing to avoid clashes.
|
||||
|
||||
# iptables chains
|
||||
|
||||
The policy controller maintains several iptables chains in response to
|
||||
changes to pods, namespaces and network policies.
|
||||
|
||||
## Static `WEAVE-NPC` chain
|
||||
|
||||
`WEAVE-NPC` chain contains static rules to ACCEPT traffic that is `RELATED,ESTABLISHED`
|
||||
and run `NEW` traffic through `WEAVE-NPC-DEFAULT` followed by `WEAVE-NPC-INGRESS` chains
|
||||
|
||||
Static configuration:
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC -m state --state RELATED,ESTABLISHED -j ACCEPT
|
||||
iptables -A WEAVE-NPC -m state --state NEW -j WEAVE-NPC-DEFAULT
|
||||
iptables -A WEAVE-NPC -m state --state NEW -j WEAVE-NPC-INGRESS
|
||||
```
|
||||
|
||||
## Dynamically maintained `WEAVE-NPC-DEFAULT` chain
|
||||
|
||||
The policy controller maintains a rule in this chain for every
|
||||
namespace whose ingress isolation policy is `DefaultAllow`. The
|
||||
purpose of this rule is simply to ACCEPT any traffic destined for such
|
||||
namespaces before it reaches the ingress chain.
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC-DEFAULT -m set --match-set $NSIPSET dst -j ACCEPT
|
||||
```
|
||||
|
||||
## Dynamically maintained `WEAVE-NPC-INGRESS` chain
|
||||
|
||||
For each namespace network policy ingress rule peer/port combination:
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC-INGRESS -p $PROTO [-m set --match-set $SRCSET] -m set --match-set $DSTSET --dport $DPORT -j ACCEPT
|
||||
```
|
||||
|
||||
## Static `WEAVE-NPC-EGRESS` chain
|
||||
|
||||
`WEAVE-NPC-EGRESS` chain contains static rules to ACCEPT traffic that is `RELATED,ESTABLISHED`
|
||||
and run `NEW` traffic through `WEAVE-NPC-EGRESS-DEFAULT` followed by `WEAVE-NPC-EGRESS-CUSTOM` chains
|
||||
|
||||
Static configuration:
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC-EGRESS -m state --state RELATED,ESTABLISHED -j ACCEPT
|
||||
iptables -A WEAVE-NPC-EGRESS -m state --state NEW -j WEAVE-NPC-EGRESS-DEFAULT
|
||||
iptables -A WEAVE-NPC-EGRESS -m state --state NEW -m mark ! --mark 0x40000/0x40000 -j WEAVE-NPC-EGRESS-CUSTOM
|
||||
iptables -A WEAVE-NPC-EGRESS -m mark ! --mark 0x40000/0x40000 -j DROP
|
||||
```
|
||||
|
||||
## Dynamically maintained `WEAVE-NPC-EGRESS-DEFAULT` chain
|
||||
|
||||
The policy controller maintains a rule in this chain for every
|
||||
namespace whose egress isolation policy is `DefaultAllow`. The
|
||||
purpose of this rule is simply to ACCEPT any traffic originating from such namespace before it reaches the egress chain.
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC-EGRESS-DEFAULT -m set --match-set $NSIPSET src -j WEAVE-NPC-EGRESS-ACCEPT
|
||||
iptables -A WEAVE-NPC-EGRESS-DEFAULT -m set --match-set $NSIPSET src -j RETURN
|
||||
```
|
||||
|
||||
## Static `WEAVE-NPC-EGRESS-ACCEPT` chain
|
||||
|
||||
`WEAVE-NPC-EGRESS-ACCEPTS` chain contains static rules to mark traffic
|
||||
|
||||
Static configuration:
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC-EGRESS-ACCEPT -j MARK --set-xmark 0x40000/0x40000
|
||||
```
|
||||
|
||||
## Dynamically maintained `WEAVE-NPC-EGRESS-CUSTOM` chain
|
||||
|
||||
For each namespace network policy egress rule peer/port combination:
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC-EGRESS-CUSTOM -p $PROTO [-m set --match-set $SRCSET] -m set --match-set $DSTSET --dport $DPORT -j ACCEPT
|
||||
```
|
||||
|
||||
|
||||
# Steering traffic into the policy engine
|
||||
|
||||
To direct traffic into the policy engine:
|
||||
|
||||
```
|
||||
iptables -A INPUT -i weave -j WEAVE-NPC-EGRESS
|
||||
iptables -A FORWARD -i weave -j WEAVE-NPC-EGRESS
|
||||
iptables -A FORWARD -o weave -j WEAVE-NPC
|
||||
```
|
||||
|
||||
Note this only affects traffic which egresses the bridge on a physical
|
||||
port which is not the Weave Net router - in other words, it is
|
||||
destined for an application container veth. The following traffic is
|
||||
affected:
|
||||
|
||||
* Traffic bridged between local application containers
|
||||
* Traffic bridged from the router to a local application container
|
||||
* Traffic originating from the internet destined for nodeports - this
|
||||
is routed via the FORWARD chain to a container pod IP after DNAT
|
||||
|
||||
The following traffic is NOT affected:
|
||||
|
||||
* Traffic bridged from a local application container to the router
|
||||
* Traffic originating from processes in the host network namespace
|
||||
(e.g. kubelet health checks)
|
||||
* Traffic routed from an application container to the internet
|
||||
|
||||
The above mechanism relies on the kernel module `br_netfilter` being
|
||||
loaded and enabled via `/proc/sys/net/bridge/bridge-nf-call-iptables`.
|
||||
|
||||
See these resources for helpful context:
|
||||
|
||||
* http://ebtables.netfilter.org/br_fw_ia/br_fw_ia.html
|
||||
* https://commons.wikimedia.org/wiki/File:Netfilter-packet-flow.svg
|
||||
@@ -0,0 +1,61 @@
|
||||
package address
|
||||
|
||||
import (
|
||||
"github.com/weaveworks/weave/common"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Using 32-bit integer to represent IPv4 address
|
||||
type Address uint32
|
||||
type Offset uint32
|
||||
|
||||
type Range struct {
|
||||
Start, End Address // [Start, End); Start <= End
|
||||
}
|
||||
|
||||
func ParseIP(s string) (Address, error) {
|
||||
if ip := net.ParseIP(s); ip == nil {
|
||||
return 0, &net.ParseError{Type: "IP Address", Text: s}
|
||||
} else {
|
||||
return FromIP4(ip), nil
|
||||
}
|
||||
}
|
||||
|
||||
// FromIP4 converts an ipv4 address to our integer address type
|
||||
func FromIP4(ip4 net.IP) (r Address) {
|
||||
for _, b := range ip4.To4() {
|
||||
r <<= 8
|
||||
r |= Address(b)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// IP4 converts our integer address type to an ipv4 address
|
||||
func (addr Address) IP4() (r net.IP) {
|
||||
r = make([]byte, net.IPv4len)
|
||||
for i := 3; i >= 0; i-- {
|
||||
r[i] = byte(addr)
|
||||
addr >>= 8
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (addr Address) String() string {
|
||||
return addr.IP4().String()
|
||||
}
|
||||
|
||||
func Add(addr Address, i Offset) Address {
|
||||
return addr + Address(i)
|
||||
}
|
||||
|
||||
func Subtract(a, b Address) Offset {
|
||||
common.Assert(a >= b)
|
||||
return Offset(a - b)
|
||||
}
|
||||
|
||||
func Min(a, b Offset) Offset {
|
||||
if a > b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
+16
-34
@@ -2,9 +2,7 @@ package ipam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
)
|
||||
|
||||
type allocateResult struct {
|
||||
@@ -14,10 +12,8 @@ type allocateResult struct {
|
||||
|
||||
type allocate struct {
|
||||
resultChan chan<- allocateResult
|
||||
ident string // a container ID, something like "weave:expose", or api.NoContainerID
|
||||
r address.CIDR // Subnet we are trying to allocate within
|
||||
isContainer bool // true if ident is a container ID
|
||||
hasBeenCancelled func() bool
|
||||
ident string
|
||||
}
|
||||
|
||||
// Try returns true if the request is completed, false if pending
|
||||
@@ -27,48 +23,34 @@ func (g *allocate) Try(alloc *Allocator) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if addrs := alloc.ownedInRange(g.ident, g.r.Range()); len(addrs) > 0 {
|
||||
// If we had heard that this container died, resurrect it
|
||||
delete(alloc.dead, g.ident) // delete is no-op if key not in map
|
||||
g.resultChan <- allocateResult{addrs[0].Addr, nil}
|
||||
// If we have previously stored an address for this container, return it.
|
||||
if addr, found := alloc.owned[g.ident]; found {
|
||||
g.resultChan <- allocateResult{addr, nil}
|
||||
return true
|
||||
}
|
||||
|
||||
if !alloc.universe.Range().Overlaps(g.r.Range()) {
|
||||
g.resultChan <- allocateResult{err: fmt.Errorf("range %s out of bounds: %s", g.r, alloc.universe)}
|
||||
return true
|
||||
}
|
||||
|
||||
alloc.establishRing()
|
||||
|
||||
if ok, addr := alloc.space.Allocate(g.r.HostRange()); ok {
|
||||
// If caller hasn't supplied a unique ID, file it under the IP address
|
||||
// which lets the caller then release the address using DELETE /ip/address
|
||||
if g.ident == api.NoContainerID {
|
||||
g.ident = addr.String()
|
||||
}
|
||||
alloc.debugln("Allocated", addr, "for", g.ident, "in", g.r)
|
||||
alloc.addOwned(g.ident, address.MakeCIDR(g.r, addr), g.isContainer)
|
||||
if ok, addr := alloc.space.Allocate(); ok {
|
||||
alloc.debugln("Allocated", addr, "for", g.ident)
|
||||
alloc.addOwned(g.ident, addr)
|
||||
g.resultChan <- allocateResult{addr, nil}
|
||||
return true
|
||||
}
|
||||
|
||||
// out of space
|
||||
donors := alloc.ring.ChoosePeersToAskForSpace(g.r.Addr, g.r.Range().End)
|
||||
for _, donor := range donors {
|
||||
if err := alloc.sendSpaceRequest(donor, g.r.Range()); err != nil {
|
||||
alloc.debugln("Problem asking peer", donor, "for space:", err)
|
||||
} else {
|
||||
alloc.debugln("Decided to ask peer", donor, "for space in range", g.r)
|
||||
break
|
||||
}
|
||||
if donor, err := alloc.ring.ChoosePeerToAskForSpace(); err == nil {
|
||||
alloc.debugln("Decided to ask peer", donor, "for space")
|
||||
alloc.sendRequest(donor, msgSpaceRequest)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *allocate) Cancel() {
|
||||
g.resultChan <- allocateResult{err: &errorCancelled{"Allocate", g.ident}}
|
||||
g.resultChan <- allocateResult{0, fmt.Errorf("Allocate request for %s cancelled", g.ident)}
|
||||
}
|
||||
|
||||
func (g *allocate) String() string {
|
||||
return fmt.Sprintf("Allocate for %s", g.ident)
|
||||
}
|
||||
|
||||
func (g *allocate) ForContainer(ident string) bool {
|
||||
|
||||
+259
-701
File diff suppressed because it is too large.
Load diff
+143
-365
@@ -7,12 +7,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/testing/gossip"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -21,86 +19,46 @@ const (
|
||||
testStart3 = "10.0.3.0"
|
||||
)
|
||||
|
||||
func returnFalse() bool { return false }
|
||||
|
||||
func (alloc *Allocator) SimplyAllocate(ident string, cidr address.CIDR) (address.Address, error) {
|
||||
return alloc.Allocate(ident, cidr, true, returnFalse)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) SimplyClaim(ident string, cidr address.CIDR) error {
|
||||
return alloc.Claim(ident, cidr, true, true, returnFalse)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) AnonymousClaim(cidr address.CIDR) error {
|
||||
return alloc.Claim(api.NoContainerID, cidr, false, true, returnFalse)
|
||||
}
|
||||
|
||||
func TestAllocFree(t *testing.T) {
|
||||
const (
|
||||
container1 = "abcdef"
|
||||
container2 = "baddf00d"
|
||||
container3 = "b01df00d"
|
||||
universe = "10.0.3.0/26"
|
||||
subnet1 = "10.0.3.0/28"
|
||||
subnet2 = "10.0.3.32/28"
|
||||
testAddr1 = "10.0.3.1"
|
||||
testAddr2 = "10.0.3.33"
|
||||
spaceSize = 62 // 64 IP addresses in /26, minus .0 and .63
|
||||
universe = "10.0.3.0/28"
|
||||
testAddr1 = "10.0.3.1" // first address allocated should be .1 because .0 is network addr
|
||||
spaceSize = 14 // 16 IP addresses in /28, minus .0 and .15
|
||||
)
|
||||
|
||||
alloc, subnet := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
|
||||
alloc := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
|
||||
defer alloc.Stop()
|
||||
cidr1, _ := address.ParseCIDR(subnet1)
|
||||
cidr2, _ := address.ParseCIDR(subnet2)
|
||||
|
||||
alloc.claimRingForTesting()
|
||||
addr1, err := alloc.SimplyAllocate(container1, cidr1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testAddr1, addr1.String(), "address")
|
||||
|
||||
addr2, err := alloc.SimplyAllocate(container1, cidr2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testAddr2, addr2.String(), "address")
|
||||
|
||||
addrs, err := alloc.Lookup(container1, subnet.Range())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []address.CIDR{address.MakeCIDR(cidr1, addr1), address.MakeCIDR(cidr2, addr2)}, addrs)
|
||||
addr1, _ := alloc.Allocate(container1, nil)
|
||||
wt.AssertEqualString(t, addr1.String(), testAddr1, "address")
|
||||
|
||||
// Ask for another address for a different container and check it's different
|
||||
addr1b, _ := alloc.SimplyAllocate(container2, cidr1)
|
||||
if addr1b.String() == testAddr1 {
|
||||
t.Fatalf("Expected different address but got %s", addr1b.String())
|
||||
addr2, _ := alloc.Allocate(container2, nil)
|
||||
if addr2.String() == testAddr1 {
|
||||
t.Fatalf("Expected different address but got %s", addr2.String())
|
||||
}
|
||||
|
||||
// Ask for the first container again and we should get the same addresses again
|
||||
addr1a, _ := alloc.SimplyAllocate(container1, cidr1)
|
||||
require.Equal(t, testAddr1, addr1a.String(), "address")
|
||||
addr2a, _ := alloc.SimplyAllocate(container1, cidr2)
|
||||
require.Equal(t, testAddr2, addr2a.String(), "address")
|
||||
// Ask for the first container again and we should get the same address again
|
||||
addr1a, _ := alloc.Allocate(container1, nil)
|
||||
wt.AssertEqualString(t, addr1a.String(), testAddr1, "address")
|
||||
|
||||
// Now delete the first container, and we should get its addresses back
|
||||
require.NoError(t, alloc.Delete(container1))
|
||||
addr3, _ := alloc.SimplyAllocate(container3, cidr1)
|
||||
require.Equal(t, testAddr1, addr3.String(), "address")
|
||||
addr4, _ := alloc.SimplyAllocate(container3, cidr2)
|
||||
require.Equal(t, testAddr2, addr4.String(), "address")
|
||||
// Now free the first one, and we should get it back when we ask
|
||||
wt.AssertSuccess(t, alloc.Free(container1))
|
||||
addr3, _ := alloc.Allocate(container3, nil)
|
||||
wt.AssertEqualString(t, addr3.String(), testAddr1, "address")
|
||||
|
||||
alloc.ContainerDied(container2)
|
||||
|
||||
// Resurrect
|
||||
addr1c, err := alloc.SimplyAllocate(container2, cidr1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, addr1b, addr1c, "address")
|
||||
|
||||
alloc.ContainerDied(container3)
|
||||
alloc.Encode() // sync up
|
||||
// Move the clock forward and clear out the dead container
|
||||
alloc.actionChan <- func() { alloc.now = func() time.Time { return time.Now().Add(containerDiedTimeout * 2) } }
|
||||
alloc.actionChan <- func() { alloc.removeDeadContainers() }
|
||||
require.Equal(t, address.Count(spaceSize+1), alloc.NumFreeAddresses(subnet.Range()))
|
||||
alloc.String() // force sync-up after async call
|
||||
wt.AssertEquals(t, alloc.space.NumFreeAddresses(), address.Offset(spaceSize))
|
||||
}
|
||||
|
||||
func TestBootstrap(t *testing.T) {
|
||||
common.InitDefaultLogging(false)
|
||||
const (
|
||||
donateSize = 5
|
||||
donateStart = "10.0.1.7"
|
||||
@@ -108,21 +66,21 @@ func TestBootstrap(t *testing.T) {
|
||||
peerNameString = "02:00:00:02:00:00"
|
||||
)
|
||||
|
||||
alloc1, subnet := makeAllocatorWithMockGossip(t, ourNameString, testStart1+"/22", 2)
|
||||
alloc1 := makeAllocatorWithMockGossip(t, ourNameString, testStart1+"/22", 2)
|
||||
defer alloc1.Stop()
|
||||
|
||||
// Simulate another peer on the gossip network
|
||||
alloc2, _ := makeAllocatorWithMockGossip(t, peerNameString, testStart1+"/22", 2)
|
||||
alloc2 := makeAllocatorWithMockGossip(t, peerNameString, testStart1+"/22", 2)
|
||||
defer alloc2.Stop()
|
||||
|
||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
||||
alloc1.OnGossipBroadcast(alloc2.encode())
|
||||
|
||||
alloc1.actionChan <- func() { alloc1.tryPendingOps() }
|
||||
alloc1.tryPendingOps()
|
||||
|
||||
ExpectBroadcastMessage(alloc1, nil) // alloc1 will try to form consensus
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
alloc1.Allocate("somecontainer", subnet, true, returnFalse)
|
||||
alloc1.Allocate("somecontainer", nil)
|
||||
done <- true
|
||||
}()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
@@ -130,37 +88,26 @@ func TestBootstrap(t *testing.T) {
|
||||
|
||||
CheckAllExpectedMessagesSent(alloc1, alloc2)
|
||||
|
||||
// Check that tryPendingOps doesn't cause anything to happen here
|
||||
alloc1.actionChan <- func() { alloc1.tryPendingOps() }
|
||||
alloc1.tryPendingOps()
|
||||
AssertNothingSent(t, done)
|
||||
|
||||
CheckAllExpectedMessagesSent(alloc1, alloc2)
|
||||
|
||||
// alloc2 receives paxos update and broadcasts its reply
|
||||
ExpectBroadcastMessage(alloc2, nil)
|
||||
alloc2.OnGossipBroadcast(alloc1.ourName, alloc1.Encode())
|
||||
alloc2.OnGossipBroadcast(alloc1.encode())
|
||||
|
||||
ExpectBroadcastMessage(alloc1, nil)
|
||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
||||
alloc1.OnGossipBroadcast(alloc2.encode())
|
||||
|
||||
CheckAllExpectedMessagesSent(alloc1, alloc2)
|
||||
|
||||
// at this point, both nodes have seen each other, but don't have a valid proposal
|
||||
// so there is another exchange of messages
|
||||
// both nodes will get consensus now so initialize the ring
|
||||
ExpectBroadcastMessage(alloc2, nil)
|
||||
alloc2.OnGossipBroadcast(alloc1.ourName, alloc1.Encode())
|
||||
|
||||
// we have consensus now so alloc1 will initialize the ring
|
||||
ExpectBroadcastMessage(alloc1, nil)
|
||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
||||
|
||||
CheckAllExpectedMessagesSent(alloc1, alloc2)
|
||||
|
||||
// alloc2 receives the ring and does not reply
|
||||
alloc2.OnGossipBroadcast(alloc1.ourName, alloc1.Encode())
|
||||
ExpectBroadcastMessage(alloc2, nil)
|
||||
alloc2.OnGossipBroadcast(alloc1.encode())
|
||||
|
||||
CheckAllExpectedMessagesSent(alloc1, alloc2)
|
||||
|
||||
alloc1.OnGossipBroadcast(alloc2.encode())
|
||||
// now alloc1 should have space
|
||||
|
||||
AssertSent(t, done)
|
||||
@@ -171,69 +118,26 @@ func TestBootstrap(t *testing.T) {
|
||||
func TestAllocatorClaim(t *testing.T) {
|
||||
const (
|
||||
container1 = "abcdef"
|
||||
container2 = "baddf00d"
|
||||
container3 = "b01df00d"
|
||||
universe = "10.0.3.0/24"
|
||||
testAddr1 = "10.0.3.5/24"
|
||||
testAddr2 = "10.0.4.2/24"
|
||||
testAddr4 = "10.0.2.3/23"
|
||||
subnet4 = "10.0.2.0/23"
|
||||
testPre = "10.0.3.1/24"
|
||||
universe = "10.0.3.0/30"
|
||||
testAddr1 = "10.0.3.1" // first address allocated should be .1 because .0 is network addr
|
||||
)
|
||||
|
||||
preAddr, _ := address.ParseCIDR(testPre)
|
||||
allocs, router, subnet := makeNetworkOfAllocators(2, universe, []PreClaim{{container1, true, preAddr}})
|
||||
defer stopNetworkOfAllocators(allocs, router)
|
||||
alloc := allocs[1]
|
||||
addr1, _ := address.ParseCIDR(testAddr1)
|
||||
alloc := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
|
||||
defer alloc.Stop()
|
||||
|
||||
// First claim should trigger "dunno, I'm going to wait"
|
||||
err := alloc.SimplyClaim(container3, addr1)
|
||||
require.NoError(t, err)
|
||||
alloc.claimRingForTesting()
|
||||
addr1, _ := alloc.Allocate(container1, nil)
|
||||
alloc.Allocate(container2, nil)
|
||||
|
||||
alloc.Prime()
|
||||
// Do an allocate on the other peer, which we will try to claim later
|
||||
addrx, err := allocs[0].Allocate(container1, subnet, true, returnFalse)
|
||||
require.NoError(t, err)
|
||||
// Should not get the address we pre-claimed
|
||||
require.NotEqual(t, addrx, preAddr)
|
||||
router.Flush()
|
||||
|
||||
// Now try the claim again
|
||||
err = alloc.SimplyClaim(container3, addr1)
|
||||
require.NoError(t, err)
|
||||
// Check we get this address back if we try an allocate
|
||||
addr3, _ := alloc.SimplyAllocate(container3, subnet)
|
||||
require.Equal(t, testAddr1, address.MakeCIDR(subnet, addr3).String(), "address")
|
||||
// one more claim should still work
|
||||
err = alloc.SimplyClaim(container3, addr1)
|
||||
require.NoError(t, err)
|
||||
// claim for a different container should fail
|
||||
err = alloc.SimplyClaim(container1, addr1)
|
||||
require.Error(t, err)
|
||||
// claim for an anonymous container, will assume it's the same one
|
||||
err = alloc.AnonymousClaim(addr1)
|
||||
require.NoError(t, err)
|
||||
// claiming the address allocated on the other peer should fail
|
||||
err = alloc.SimplyClaim(container1, address.MakeCIDR(subnet, addrx))
|
||||
require.Error(t, err, "claiming address allocated on other peer should fail")
|
||||
// claiming the pre-claimed address should fail on both peers
|
||||
err = alloc.SimplyClaim(container3, preAddr)
|
||||
require.Error(t, err, "claiming address allocated on other peer should fail")
|
||||
err = allocs[0].SimplyClaim(container3, preAddr)
|
||||
require.Error(t, err, "claiming address allocated on other peer should fail")
|
||||
// Check an address outside of our universe
|
||||
addr2, _ := address.ParseCIDR(testAddr2)
|
||||
err = alloc.SimplyClaim(container1, addr2)
|
||||
require.NoError(t, err)
|
||||
err = alloc.SimplyClaim(container3, addr2)
|
||||
require.Error(t, err)
|
||||
// Anonymous claim outside of our universe
|
||||
addr4, _ := address.ParseCIDR(testAddr4)
|
||||
subnet4cidr, _ := address.ParseCIDR(subnet4)
|
||||
require.NoError(t, alloc.AnonymousClaim(addr4))
|
||||
addr5, err := alloc.Allocate(api.NoContainerID, subnet4cidr, false, returnFalse)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, addr4.Start(), addr5)
|
||||
// Now free the first one, and try to claim it
|
||||
wt.AssertSuccess(t, alloc.Free(container1))
|
||||
t.Log(alloc)
|
||||
err := alloc.Claim(container3, addr1, nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
addr3, _ := alloc.Allocate(container3, nil)
|
||||
wt.AssertEqualString(t, addr3.String(), testAddr1, "address")
|
||||
}
|
||||
|
||||
func (alloc *Allocator) pause() func() {
|
||||
@@ -249,14 +153,18 @@ func (alloc *Allocator) pause() func() {
|
||||
}
|
||||
|
||||
func TestCancel(t *testing.T) {
|
||||
const cidr = "10.0.4.0/26"
|
||||
router := gossip.NewTestRouter(0.0)
|
||||
common.InitDefaultLogging(false)
|
||||
const (
|
||||
CIDR = "10.0.1.7/26"
|
||||
)
|
||||
|
||||
alloc1, subnet := makeAllocator("01:00:00:02:00:00", cidr, 2)
|
||||
alloc1.SetInterfaces(router.Connect(alloc1.ourName, alloc1))
|
||||
router := TestGossipRouter{make(map[router.PeerName]chan gossipMessage), 0.0}
|
||||
|
||||
alloc2, _ := makeAllocator("02:00:00:02:00:00", cidr, 2)
|
||||
alloc2.SetInterfaces(router.Connect(alloc2.ourName, alloc2))
|
||||
alloc1 := makeAllocator("01:00:00:02:00:00", CIDR, 2)
|
||||
alloc1.SetInterfaces(router.connect(alloc1.ourName, alloc1))
|
||||
|
||||
alloc2 := makeAllocator("02:00:00:02:00:00", CIDR, 2)
|
||||
alloc2.SetInterfaces(router.connect(alloc2.ourName, alloc2))
|
||||
alloc1.claimRingForTesting(alloc1, alloc2)
|
||||
alloc2.claimRingForTesting(alloc1, alloc2)
|
||||
|
||||
@@ -264,15 +172,15 @@ func TestCancel(t *testing.T) {
|
||||
alloc2.Start()
|
||||
|
||||
// tell peers about each other
|
||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
||||
alloc1.OnGossipBroadcast(alloc2.encode())
|
||||
|
||||
// Get some IPs, so each allocator has some space
|
||||
res1, _ := alloc1.Allocate("foo", subnet, true, returnFalse)
|
||||
common.Log.Debugf("res1 = %s", res1.String())
|
||||
res2, _ := alloc2.Allocate("bar", subnet, true, returnFalse)
|
||||
common.Log.Debugf("res2 = %s", res2.String())
|
||||
res1, _ := alloc1.Allocate("foo", nil)
|
||||
common.Debug.Printf("res1 = %s", res1.String())
|
||||
res2, _ := alloc2.Allocate("bar", nil)
|
||||
common.Debug.Printf("res2 = %s", res2.String())
|
||||
if res1 == res2 {
|
||||
require.FailNow(t, "Error: got same ips!")
|
||||
wt.Fatalf(t, "Error: got same ips!")
|
||||
}
|
||||
|
||||
// Now we're going to pause alloc2 and ask alloc1
|
||||
@@ -280,64 +188,24 @@ func TestCancel(t *testing.T) {
|
||||
unpause := alloc2.pause()
|
||||
|
||||
// Use up all the IPs that alloc1 owns, so the allocation after this will prompt a request to alloc2
|
||||
for i := 0; alloc1.NumFreeAddresses(subnet.HostRange()) > 0; i++ {
|
||||
alloc1.Allocate(fmt.Sprintf("tmp%d", i), subnet, true, returnFalse)
|
||||
for i := 0; alloc1.space.NumFreeAddresses() > 0; i++ {
|
||||
alloc1.Allocate(fmt.Sprintf("tmp%d", i), nil)
|
||||
}
|
||||
cancelChan := make(chan bool, 1)
|
||||
doneChan := make(chan bool)
|
||||
go func() {
|
||||
_, ok := alloc1.Allocate("baz", subnet, true,
|
||||
func() bool {
|
||||
select {
|
||||
case <-cancelChan:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
_, ok := alloc1.Allocate("baz", cancelChan)
|
||||
doneChan <- ok == nil
|
||||
}()
|
||||
|
||||
AssertNothingSent(t, doneChan)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
AssertNothingSent(t, doneChan)
|
||||
|
||||
cancelChan <- true
|
||||
unpause()
|
||||
if <-doneChan {
|
||||
require.FailNow(t, "Error: got result from Allocate")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelOnDied(t *testing.T) {
|
||||
const (
|
||||
cidr = "10.0.4.0/26"
|
||||
container1 = "abcdef"
|
||||
)
|
||||
|
||||
router := gossip.NewTestRouter(0.0)
|
||||
alloc1, subnet := makeAllocator("01:00:00:02:00:00", cidr, 2)
|
||||
alloc1.SetInterfaces(router.Connect(alloc1.ourName, alloc1))
|
||||
alloc1.Start()
|
||||
|
||||
doneChan := make(chan bool)
|
||||
f := func() {
|
||||
_, ok := alloc1.Allocate(container1, subnet, true, returnFalse)
|
||||
doneChan <- ok == nil
|
||||
}
|
||||
|
||||
// Attempt two allocations in parallel, to check that this is handled correctly
|
||||
go f()
|
||||
go f()
|
||||
|
||||
// Nothing should happen, because we declared the quorum as 2
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
AssertNothingSent(t, doneChan)
|
||||
|
||||
alloc1.ContainerDied(container1)
|
||||
|
||||
// Check that the two allocations both exit with an error
|
||||
if <-doneChan || <-doneChan {
|
||||
require.FailNow(t, "Error: got result from Allocate")
|
||||
wt.Fatalf(t, "Error: got result from Allocate")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,100 +214,82 @@ func TestGossipShutdown(t *testing.T) {
|
||||
container1 = "abcdef"
|
||||
container2 = "baddf00d"
|
||||
universe = "10.0.3.0/30"
|
||||
testAddr1 = "10.0.3.1" // first address allocated should be .1 because .0 is network addr
|
||||
)
|
||||
|
||||
alloc, subnet := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
|
||||
alloc := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
|
||||
defer alloc.Stop()
|
||||
|
||||
alloc.claimRingForTesting()
|
||||
alloc.SimplyAllocate(container1, subnet)
|
||||
addr1, _ := alloc.Allocate(container1, nil)
|
||||
wt.AssertEqualString(t, addr1.String(), testAddr1, "address")
|
||||
|
||||
alloc.Shutdown()
|
||||
|
||||
_, err := alloc.SimplyAllocate(container2, subnet) // trying to allocate after shutdown should fail
|
||||
require.False(t, err == nil, "no address")
|
||||
_, err := alloc.Allocate(container2, nil) // trying to allocate after shutdown should fail
|
||||
wt.AssertFalse(t, err == nil, "no address")
|
||||
|
||||
CheckAllExpectedMessagesSent(alloc)
|
||||
}
|
||||
|
||||
func TestNoFrag(t *testing.T) {
|
||||
const cidr = "10.0.4.0/22"
|
||||
resultChan := make(chan int)
|
||||
for i := 0; i < 100; i++ {
|
||||
allocs, router, subnet := makeNetworkOfAllocators(3, cidr)
|
||||
allocs[1].Allocate("foo", subnet, true, returnFalse)
|
||||
allocs[2].Allocate("bar", subnet, true, returnFalse)
|
||||
allocs[2].actionChan <- func() {
|
||||
resultChan <- len(allocs[2].ring.Entries)
|
||||
}
|
||||
require.True(t, <-resultChan < 6, "excessive ring fragmentation")
|
||||
stopNetworkOfAllocators(allocs, router)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransfer(t *testing.T) {
|
||||
const cidr = "10.0.4.0/22"
|
||||
allocs, router, subnet := makeNetworkOfAllocators(3, cidr)
|
||||
defer stopNetworkOfAllocators(allocs, router)
|
||||
alloc0 := allocs[0]
|
||||
alloc1 := allocs[1]
|
||||
alloc2 := allocs[2]
|
||||
const (
|
||||
cidr = "10.0.1.7/22"
|
||||
)
|
||||
allocs, router := makeNetworkOfAllocators(3, cidr)
|
||||
alloc1 := allocs[0]
|
||||
alloc2 := allocs[1]
|
||||
alloc3 := allocs[2] // This will be 'master' and get the first range
|
||||
|
||||
_, err := alloc1.Allocate("foo", subnet, true, returnFalse)
|
||||
require.True(t, err == nil, "Failed to get address")
|
||||
_, err := alloc2.Allocate("foo", nil)
|
||||
wt.AssertTrue(t, err == nil, "Failed to get address")
|
||||
|
||||
_, err = alloc2.Allocate("bar", subnet, true, returnFalse)
|
||||
require.True(t, err == nil, "Failed to get address")
|
||||
_, err = alloc3.Allocate("bar", nil)
|
||||
wt.AssertTrue(t, err == nil, "Failed to get address")
|
||||
|
||||
// simulation of periodic gossip
|
||||
alloc2.gossip.GossipBroadcast(alloc2.Gossip())
|
||||
router.Flush()
|
||||
alloc1.gossip.GossipBroadcast(alloc1.Gossip())
|
||||
router.Flush()
|
||||
|
||||
free1 := alloc1.NumFreeAddresses(subnet.Range())
|
||||
free2 := alloc2.NumFreeAddresses(subnet.Range())
|
||||
|
||||
router.RemovePeer(alloc1.ourName)
|
||||
router.RemovePeer(alloc2.ourName)
|
||||
alloc1.Stop()
|
||||
router.GossipBroadcast(alloc2.Gossip())
|
||||
router.GossipBroadcast(alloc3.Gossip())
|
||||
router.removePeer(alloc2.ourName)
|
||||
router.removePeer(alloc3.ourName)
|
||||
alloc2.Stop()
|
||||
router.Flush()
|
||||
alloc3.Stop()
|
||||
wt.AssertSuccess(t, alloc1.AdminTakeoverRanges(alloc2.ourName.String()))
|
||||
wt.AssertSuccess(t, alloc1.AdminTakeoverRanges(alloc3.ourName.String()))
|
||||
|
||||
require.Equal(t, free1+1, alloc0.AdminTakeoverRanges(alloc1.ourName.String()))
|
||||
require.Equal(t, free2+1, alloc0.AdminTakeoverRanges(alloc2.ourName.String()))
|
||||
router.Flush()
|
||||
wt.AssertEquals(t, alloc1.space.NumFreeAddresses(), address.Offset(1022))
|
||||
|
||||
require.Equal(t, address.Count(1024), alloc0.NumFreeAddresses(subnet.Range()))
|
||||
|
||||
_, err = alloc0.Allocate("foo", subnet, true, returnFalse)
|
||||
require.True(t, err == nil, "Failed to get address")
|
||||
alloc0.Stop()
|
||||
_, err = alloc1.Allocate("foo", nil)
|
||||
wt.AssertTrue(t, err == nil, "Failed to get address")
|
||||
alloc1.Stop()
|
||||
}
|
||||
|
||||
func TestFakeRouterSimple(t *testing.T) {
|
||||
const cidr = "10.0.4.0/22"
|
||||
allocs, router, subnet := makeNetworkOfAllocators(2, cidr)
|
||||
defer stopNetworkOfAllocators(allocs, router)
|
||||
common.InitDefaultLogging(false)
|
||||
const (
|
||||
cidr = "10.0.1.7/22"
|
||||
)
|
||||
allocs, _ := makeNetworkOfAllocators(2, cidr)
|
||||
defer stopNetworkOfAllocators(allocs)
|
||||
|
||||
alloc1 := allocs[0]
|
||||
//alloc2 := allocs[1]
|
||||
|
||||
_, err := alloc1.Allocate("foo", subnet, true, returnFalse)
|
||||
require.NoError(t, err, "Failed to get address")
|
||||
addr, _ := alloc1.Allocate("foo", nil)
|
||||
println("Got addr", addr)
|
||||
}
|
||||
|
||||
func TestAllocatorFuzz(t *testing.T) {
|
||||
common.InitDefaultLogging(false)
|
||||
const (
|
||||
firstpass = 1000
|
||||
secondpass = 10000
|
||||
nodes = 5
|
||||
secondpass = 5000
|
||||
nodes = 10
|
||||
maxAddresses = 1000
|
||||
concurrency = 5
|
||||
cidr = "10.0.4.0/22"
|
||||
concurrency = 10
|
||||
cidr = "10.0.1.7/22"
|
||||
)
|
||||
allocs, router, subnet := makeNetworkOfAllocators(nodes, cidr)
|
||||
defer stopNetworkOfAllocators(allocs, router)
|
||||
allocs, _ := makeNetworkOfAllocators(nodes, cidr)
|
||||
defer stopNetworkOfAllocators(allocs)
|
||||
|
||||
// Test state
|
||||
// For each IP issued we store the allocator
|
||||
@@ -448,7 +298,6 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
type result struct {
|
||||
name string
|
||||
alloc int32
|
||||
block bool
|
||||
}
|
||||
stateLock := sync.Mutex{}
|
||||
state := make(map[string]result)
|
||||
@@ -467,19 +316,28 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
return xs[:ls]
|
||||
}
|
||||
|
||||
bumpPending := func() bool {
|
||||
// Do a Allocate and check the address
|
||||
// is unique. Needs a unique container
|
||||
// name.
|
||||
allocate := func(name string) {
|
||||
stateLock.Lock()
|
||||
if len(addrs)+numPending >= maxAddresses {
|
||||
stateLock.Unlock()
|
||||
return false
|
||||
return
|
||||
}
|
||||
numPending++
|
||||
stateLock.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
noteAllocation := func(allocIndex int32, name string, addr address.Address) {
|
||||
//common.Log.Infof("Allocate: got address %s for name %s", addr, name)
|
||||
allocIndex := rand.Int31n(nodes)
|
||||
alloc := allocs[allocIndex]
|
||||
//common.Info.Printf("Allocate: asking allocator %d", allocIndex)
|
||||
addr, err := alloc.Allocate(name, nil)
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Could not allocate addr"))
|
||||
}
|
||||
|
||||
//common.Info.Printf("Allocate: got address %s for name %s", addr, name)
|
||||
addrStr := addr.String()
|
||||
|
||||
stateLock.Lock()
|
||||
@@ -490,31 +348,11 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
name, res.name))
|
||||
}
|
||||
|
||||
state[addrStr] = result{name, allocIndex, false}
|
||||
state[addrStr] = result{name, allocIndex}
|
||||
addrs = append(addrs, addrStr)
|
||||
numPending--
|
||||
}
|
||||
|
||||
// Do a Allocate and check the address
|
||||
// is unique. Needs a unique container
|
||||
// name.
|
||||
allocate := func(name string) {
|
||||
if !bumpPending() {
|
||||
return
|
||||
}
|
||||
|
||||
allocIndex := rand.Int31n(nodes)
|
||||
alloc := allocs[allocIndex]
|
||||
//common.Log.Infof("Allocate: asking allocator %d", allocIndex)
|
||||
addr, err := alloc.SimplyAllocate(name, subnet)
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Could not allocate addr"))
|
||||
}
|
||||
|
||||
noteAllocation(allocIndex, name, addr)
|
||||
}
|
||||
|
||||
// Free a random address.
|
||||
free := func() {
|
||||
stateLock.Lock()
|
||||
@@ -527,22 +365,14 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
addrIndex := rand.Int31n(int32(len(addrs)))
|
||||
addr := addrs[addrIndex]
|
||||
res := state[addr]
|
||||
if res.block {
|
||||
stateLock.Unlock()
|
||||
return
|
||||
}
|
||||
addrs = rm(addrs, addrIndex)
|
||||
delete(state, addr)
|
||||
stateLock.Unlock()
|
||||
|
||||
alloc := allocs[res.alloc]
|
||||
//common.Log.Infof("Freeing %s (%s) on allocator %d", res.name, addr, res.alloc)
|
||||
//common.Info.Printf("Freeing %s on allocator %d", addr, res.alloc)
|
||||
|
||||
oldAddr, err := address.ParseIP(addr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
require.NoError(t, alloc.Free(res.name, oldAddr))
|
||||
wt.AssertSuccess(t, alloc.Free(res.name))
|
||||
}
|
||||
|
||||
// Do a Allocate on an existing container & allocator
|
||||
@@ -552,41 +382,15 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
addrIndex := rand.Int31n(int32(len(addrs)))
|
||||
addr := addrs[addrIndex]
|
||||
res := state[addr]
|
||||
if res.block {
|
||||
stateLock.Unlock()
|
||||
return
|
||||
}
|
||||
res.block = true
|
||||
state[addr] = res
|
||||
stateLock.Unlock()
|
||||
alloc := allocs[res.alloc]
|
||||
|
||||
//common.Log.Infof("Asking for %s (%s) on allocator %d again", res.name, addr, res.alloc)
|
||||
//common.Info.Printf("Asking for %s on allocator %d again", addr, res.alloc)
|
||||
|
||||
newAddr, _ := alloc.SimplyAllocate(res.name, subnet)
|
||||
newAddr, _ := alloc.Allocate(res.name, nil)
|
||||
oldAddr, _ := address.ParseIP(addr)
|
||||
if newAddr != oldAddr {
|
||||
panic(fmt.Sprintf("Got different address for repeat request for %s: %s != %s", res.name, newAddr, oldAddr))
|
||||
}
|
||||
|
||||
stateLock.Lock()
|
||||
res.block = false
|
||||
state[addr] = res
|
||||
stateLock.Unlock()
|
||||
}
|
||||
|
||||
// Claim a random address for a unique container name - may not succeed
|
||||
claim := func(name string) {
|
||||
if !bumpPending() {
|
||||
return
|
||||
}
|
||||
allocIndex := rand.Int31n(nodes)
|
||||
addressIndex := rand.Int31n(int32(subnet.Size()))
|
||||
alloc := allocs[allocIndex]
|
||||
addr := address.Add(subnet.Addr, address.Offset(addressIndex))
|
||||
err := alloc.SimplyClaim(name, address.MakeCIDR(subnet, addr))
|
||||
if err == nil {
|
||||
noteAllocation(allocIndex, name, addr)
|
||||
panic(fmt.Sprintf("Got different address for repeat request"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -629,47 +433,21 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
// free a random addr
|
||||
free()
|
||||
|
||||
case 0.8 <= r && r < 0.95:
|
||||
case 0.8 <= r && r < 1.0:
|
||||
// ask for an existing name again, check we get same ip
|
||||
allocateAgain()
|
||||
|
||||
case 0.95 <= r && r < 1.0:
|
||||
name := fmt.Sprintf("second%d", iteration)
|
||||
claim(name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSpaceRequest(t *testing.T) {
|
||||
const (
|
||||
container1 = "cont-1"
|
||||
container2 = "cont-2"
|
||||
universe = "10.32.0.0/12"
|
||||
)
|
||||
allocs, router, subnet := makeNetworkOfAllocators(1, universe)
|
||||
defer stopNetworkOfAllocators(allocs, router)
|
||||
alloc1 := allocs[0]
|
||||
|
||||
addr, err := alloc1.Allocate(container1, subnet, true, returnFalse)
|
||||
require.Nil(t, err, "")
|
||||
// free it again so the donation splits the range neatly
|
||||
err = alloc1.Free(container1, addr)
|
||||
require.Nil(t, err, "")
|
||||
require.Equal(t, cidrRanges(universe), alloc1.OwnedRanges(), "")
|
||||
|
||||
// Start a new peer
|
||||
alloc2, _ := makeAllocator("02:00:00:02:00:00", universe, 2)
|
||||
alloc2.SetInterfaces(router.Connect(alloc2.ourName, alloc2))
|
||||
alloc2.Start()
|
||||
func TestGossipSkew(t *testing.T) {
|
||||
alloc1 := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", "10.0.1.0/22", 2)
|
||||
defer alloc1.Stop()
|
||||
alloc2 := makeAllocatorWithMockGossip(t, "02:00:00:02:00:00", "10.0.1.0/22", 2)
|
||||
alloc2.now = func() time.Time { return time.Now().Add(time.Hour * 2) }
|
||||
defer alloc2.Stop()
|
||||
alloc2.Allocate(container2, subnet, true, returnFalse)
|
||||
|
||||
// Test whether the universe has been split into two equal halves (GH #2009)
|
||||
require.Equal(t, cidrRanges("10.32.0.0/13"), alloc1.OwnedRanges(), "")
|
||||
require.Equal(t, cidrRanges("10.40.0.0/13"), alloc2.OwnedRanges(), "")
|
||||
}
|
||||
|
||||
func cidrRanges(s string) []address.Range {
|
||||
c, _ := address.ParseCIDR(s)
|
||||
return []address.Range{c.Range()}
|
||||
if _, err := alloc1.OnGossipBroadcast(alloc2.encode()); err == nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
+43
-120
@@ -3,152 +3,75 @@ package ipam
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
type claim struct {
|
||||
resultChan chan<- error
|
||||
tryCount int
|
||||
ident string // a container ID, something like "weave:expose", or api.NoContainerID
|
||||
cidr address.CIDR // single address being claimed
|
||||
isContainer bool // true if ident is a container ID
|
||||
noErrorOnUnknown bool // if false, error or block if we don't know; if true return ok but keep trying
|
||||
hasBeenCancelled func() bool
|
||||
}
|
||||
|
||||
const maxTryCount = 5
|
||||
|
||||
// Send an error (or nil for success) back to caller listening on resultChan
|
||||
func (c *claim) sendResult(result error) {
|
||||
// Make sure we only send a result once, since listener stops listening after that
|
||||
if c.resultChan != nil {
|
||||
c.resultChan <- result
|
||||
close(c.resultChan)
|
||||
c.resultChan = nil
|
||||
return
|
||||
}
|
||||
if result != nil {
|
||||
common.Log.Errorln("[allocator] " + result.Error())
|
||||
}
|
||||
ident string
|
||||
addr address.Address
|
||||
}
|
||||
|
||||
// Try returns true for success (or failure), false if we need to try again later
|
||||
func (c *claim) Try(alloc *Allocator) bool {
|
||||
if c.hasBeenCancelled != nil && c.hasBeenCancelled() {
|
||||
if (c.hasBeenCancelled)() {
|
||||
c.Cancel()
|
||||
return true
|
||||
}
|
||||
|
||||
c.tryCount++
|
||||
|
||||
addOwned := func() {
|
||||
if c.ident == api.NoContainerID {
|
||||
alloc.addOwned(c.cidr.Addr.String(), c.cidr, c.isContainer)
|
||||
} else {
|
||||
alloc.addOwned(c.ident, c.cidr, c.isContainer)
|
||||
}
|
||||
}
|
||||
|
||||
if !alloc.ring.Contains(c.cidr.Addr) {
|
||||
alloc.infof("Address %s claimed by %s - not in our range", c.cidr, c.ident)
|
||||
previousOwner := alloc.findOwner(c.cidr.Addr)
|
||||
switch {
|
||||
case previousOwner == "":
|
||||
addOwned()
|
||||
case previousOwner == c.ident: // already owned by this ID
|
||||
case c.ident == api.NoContainerID: // already owned by anonymous container
|
||||
// do nothing (no automatic fall-through in Go)
|
||||
case !alloc.dead[previousOwner].IsZero(): // already owned by dead container
|
||||
alloc.removeOwned(previousOwner, c.cidr.Addr)
|
||||
addOwned()
|
||||
default:
|
||||
c.sendResult(fmt.Errorf("address %s already in use by %s", c.cidr, previousOwner))
|
||||
}
|
||||
c.sendResult(nil)
|
||||
if !alloc.ring.Contains(c.addr) {
|
||||
// Address not within our universe; assume user knows what they are doing
|
||||
alloc.infof("Ignored address %s claimed by %s - not in our universe\n", c.addr, c.ident)
|
||||
c.resultChan <- nil
|
||||
return true
|
||||
}
|
||||
|
||||
alloc.establishRing()
|
||||
|
||||
// If we had heard that this container died, resurrect it
|
||||
delete(alloc.dead, c.ident) // (delete is no-op if key not in map)
|
||||
|
||||
switch owner := alloc.ring.Owner(c.cidr.Addr); owner {
|
||||
case alloc.ourName:
|
||||
// success
|
||||
case mesh.UnknownPeerName:
|
||||
// If our ring doesn't know, it must be empty.
|
||||
alloc.infof("Claim %s for %s: is in the range %s, but the allocator is not initialized yet; will try later.",
|
||||
c.cidr, c.ident, alloc.universe)
|
||||
if c.noErrorOnUnknown {
|
||||
c.sendResult(nil)
|
||||
}
|
||||
return false
|
||||
default:
|
||||
alloc.debugf("requesting address %s from other peer %s", c.cidr, owner)
|
||||
err := alloc.sendSpaceRequest(owner, address.NewRange(c.cidr.Addr, 1))
|
||||
if err != nil { // can't speak to owner right now
|
||||
if c.noErrorOnUnknown {
|
||||
alloc.infof("Claim %s for %s: %s; will try later.", c.cidr, c.ident, err)
|
||||
c.sendResult(nil)
|
||||
} else if c.tryCount > maxTryCount {
|
||||
// give up, tell the user they can't do this
|
||||
c.deniedBy(alloc, owner, err)
|
||||
return true
|
||||
}
|
||||
}
|
||||
// If our ring doesn't know, it must be empty. We will have initiated the
|
||||
// bootstrap of the ring, so wait until we find some owner for this
|
||||
// range (might be us).
|
||||
owner := alloc.ring.Owner(c.addr)
|
||||
if owner == router.UnknownPeerName {
|
||||
alloc.infof("Ring is empty; will try later.\n", c.addr, owner)
|
||||
return false
|
||||
}
|
||||
|
||||
if owner != alloc.ourName {
|
||||
name, found := alloc.nicknames[owner]
|
||||
if found {
|
||||
name = " (" + name + ")"
|
||||
}
|
||||
c.resultChan <- fmt.Errorf("address %s is owned by other peer %s%s", c.addr.String(), owner, name)
|
||||
return true
|
||||
}
|
||||
// We are the owner, check we haven't given it to another container
|
||||
existingIdent := alloc.findOwner(c.cidr.Addr)
|
||||
switch {
|
||||
case existingIdent == "":
|
||||
// Unused address, we try to claim it:
|
||||
if err := alloc.space.Claim(c.cidr.Addr); err == nil {
|
||||
alloc.debugln("Claimed", c.cidr, "for", c.ident)
|
||||
addOwned()
|
||||
c.sendResult(nil)
|
||||
} else {
|
||||
c.sendResult(err)
|
||||
}
|
||||
case (existingIdent == c.ident) || (c.ident == api.NoContainerID && existingIdent == c.cidr.Addr.String()):
|
||||
existingIdent := alloc.findOwner(c.addr)
|
||||
if existingIdent == c.ident {
|
||||
// same identifier is claiming same address; that's OK
|
||||
alloc.debugln("Re-Claimed", c.cidr, "for", c.ident)
|
||||
c.sendResult(nil)
|
||||
case existingIdent == c.cidr.Addr.String():
|
||||
// Address already allocated via api.NoContainerID name and current ID is a real container ID:
|
||||
c.sendResult(fmt.Errorf("address %s already in use", c.cidr))
|
||||
case c.ident == api.NoContainerID:
|
||||
// We do not know whether this is the same container or another one,
|
||||
// but we also cannot prove otherwise, so we let it reclaim the address:
|
||||
alloc.debugln("Re-Claimed", c.cidr, "for ID", c.ident, "having existing ID as", existingIdent)
|
||||
c.sendResult(nil)
|
||||
default:
|
||||
// Addr already owned by container on this machine
|
||||
c.sendResult(fmt.Errorf("address %s is already owned by %s", c.cidr.String(), existingIdent))
|
||||
c.resultChan <- nil
|
||||
return true
|
||||
}
|
||||
if existingIdent == "" {
|
||||
err := alloc.space.Claim(c.addr)
|
||||
if err != nil {
|
||||
c.resultChan <- err
|
||||
return true
|
||||
}
|
||||
alloc.addOwned(c.ident, c.addr)
|
||||
c.resultChan <- nil
|
||||
return true
|
||||
}
|
||||
// Addr already owned by container on this machine
|
||||
c.resultChan <- fmt.Errorf("address %s is already owned by %s", c.addr.String(), existingIdent)
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *claim) deniedBy(alloc *Allocator, owner mesh.PeerName, err error) {
|
||||
name, found := alloc.nicknames[owner]
|
||||
if found {
|
||||
name = " (" + name + ")"
|
||||
}
|
||||
reason := ""
|
||||
if err != nil {
|
||||
reason = fmt.Sprintf(" - %s", err)
|
||||
}
|
||||
c.sendResult(fmt.Errorf("address %s is owned by other peer %s%s%s", c.cidr.String(), owner, name, reason))
|
||||
func (c *claim) Cancel() {
|
||||
c.resultChan <- fmt.Errorf("Operation cancelled.")
|
||||
}
|
||||
|
||||
func (c *claim) Cancel() {
|
||||
c.sendResult(&errorCancelled{"Claim", c.ident})
|
||||
func (c *claim) String() string {
|
||||
return fmt.Sprintf("Claim %s -> %s", c.ident, c.addr.String())
|
||||
}
|
||||
|
||||
func (c *claim) ForContainer(ident string) bool {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package ipam
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
// Exists solely for the purpose of detecting a network in which some
|
||||
// peers are configured with IPAM on and some with IPAM off.
|
||||
type DummyAllocator struct {
|
||||
}
|
||||
|
||||
var (
|
||||
ErrMixedConfig = errors.New("Mixed IPAM/non-IPAM configuration not supported")
|
||||
)
|
||||
|
||||
func (alloc *DummyAllocator) OnGossipUnicast(sender router.PeerName, msg []byte) error {
|
||||
return ErrMixedConfig
|
||||
}
|
||||
|
||||
func (alloc *DummyAllocator) OnGossipBroadcast(msg []byte) (router.GossipData, error) {
|
||||
return nil, ErrMixedConfig
|
||||
}
|
||||
|
||||
func (alloc *DummyAllocator) OnGossip(msg []byte) (router.GossipData, error) {
|
||||
return nil, ErrMixedConfig
|
||||
}
|
||||
|
||||
func (alloc *DummyAllocator) Gossip() router.GossipData {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (alloc *DummyAllocator) Encode() []byte {
|
||||
return nil
|
||||
}
|
||||
+32
-168
@@ -1,196 +1,53 @@
|
||||
package ipam
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
)
|
||||
|
||||
func badRequest(w http.ResponseWriter, err error) {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
common.Log.Warningln("[allocator]:", err.Error())
|
||||
}
|
||||
|
||||
func parseCIDR(w http.ResponseWriter, cidrStr string, net bool) (address.CIDR, bool) {
|
||||
var cidr address.CIDR
|
||||
var err error
|
||||
if net {
|
||||
cidr, err = ParseCIDRSubnet(cidrStr)
|
||||
} else {
|
||||
cidr, err = address.ParseCIDR(cidrStr)
|
||||
}
|
||||
if err != nil {
|
||||
badRequest(w, err)
|
||||
return address.CIDR{}, false
|
||||
}
|
||||
return cidr, true
|
||||
}
|
||||
|
||||
func writeAddresses(w http.ResponseWriter, cidrs []address.CIDR) {
|
||||
for i, cidr := range cidrs {
|
||||
fmt.Fprint(w, cidr)
|
||||
if i < len(cidrs)-1 {
|
||||
w.Write([]byte{' '})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hasBeenCancelled(dockerCli *docker.Client, closedChan <-chan bool, ident string, checkAlive bool) func() bool {
|
||||
return func() bool {
|
||||
select {
|
||||
case <-closedChan:
|
||||
return true
|
||||
default:
|
||||
res := checkAlive && dockerCli != nil && dockerCli.IsContainerNotRunning(ident)
|
||||
checkAlive = false // we check only once; if the container dies later we learn about that through events
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cancellationErr(w http.ResponseWriter, err error) bool {
|
||||
if _, ok := err.(*errorCancelled); ok {
|
||||
common.Log.Infoln("[allocator]:", err.Error())
|
||||
fmt.Fprint(w, "cancelled")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (alloc *Allocator) handleHTTPAllocate(dockerCli *docker.Client, w http.ResponseWriter, ident string, checkAlive bool, subnet address.CIDR) {
|
||||
addr, err := alloc.Allocate(ident, subnet, checkAlive,
|
||||
hasBeenCancelled(dockerCli, w.(http.CloseNotifier).CloseNotify(), ident, checkAlive))
|
||||
if err != nil {
|
||||
if !cancellationErr(w, err) {
|
||||
badRequest(w, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "%s/%d", addr, subnet.PrefixLen)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) handleHTTPClaim(dockerCli *docker.Client, w http.ResponseWriter, ident string, cidr address.CIDR, checkAlive, noErrorOnUnknown bool) {
|
||||
err := alloc.Claim(ident, cidr, checkAlive, noErrorOnUnknown,
|
||||
hasBeenCancelled(dockerCli, w.(http.CloseNotifier).CloseNotify(), ident, checkAlive))
|
||||
if err != nil {
|
||||
if !cancellationErr(w, err) {
|
||||
badRequest(w, fmt.Errorf("Unable to claim: %s", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
w.WriteHeader(204)
|
||||
common.Warning.Println("[allocator]:", err.Error())
|
||||
}
|
||||
|
||||
// HandleHTTP wires up ipams HTTP endpoints to the provided mux.
|
||||
func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, dockerCli *docker.Client) {
|
||||
router.Methods("GET").Path("/ipinfo/defaultsubnet").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "%s", defaultSubnet)
|
||||
})
|
||||
|
||||
router.Methods("PUT").Path("/ip/{id}/{ip}/{prefixlen}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
if cidr, ok := parseCIDR(w, vars["ip"]+"/"+vars["prefixlen"], false); ok {
|
||||
ident := vars["id"]
|
||||
checkAlive := r.FormValue("check-alive") == "true"
|
||||
noErrorOnUnknown := r.FormValue("noErrorOnUnknown") == "true"
|
||||
alloc.handleHTTPClaim(dockerCli, w, ident, cidr, checkAlive, noErrorOnUnknown)
|
||||
}
|
||||
})
|
||||
|
||||
router.Methods("GET").Path("/ring").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
alloc.Prime()
|
||||
})
|
||||
|
||||
router.Methods("GET").Path("/ip/{id}/{ip}/{prefixlen}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
if subnet, ok := parseCIDR(w, vars["ip"]+"/"+vars["prefixlen"], true); ok {
|
||||
cidrs, err := alloc.Lookup(vars["id"], subnet.HostRange())
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
writeAddresses(w, cidrs)
|
||||
}
|
||||
})
|
||||
|
||||
router.Methods("GET").Path("/ip/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
addrs, err := alloc.Lookup(mux.Vars(r)["id"], defaultSubnet.HostRange())
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
writeAddresses(w, addrs)
|
||||
})
|
||||
|
||||
router.Methods("GET").Path("/ip").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
type mapping struct {
|
||||
ContainerID string `json:"containerid"`
|
||||
Addrs []string `json:"addrs"`
|
||||
}
|
||||
|
||||
type mappings struct {
|
||||
Owned []mapping `json:"owned"`
|
||||
}
|
||||
|
||||
resultChan := make(chan mappings)
|
||||
alloc.actionChan <- func() {
|
||||
ms := mappings{}
|
||||
for containerid, d := range alloc.owned {
|
||||
m := mapping{
|
||||
ContainerID: containerid,
|
||||
Addrs: []string{},
|
||||
}
|
||||
for _, addr := range d.Cidrs {
|
||||
m.Addrs = append(m.Addrs, addr.String())
|
||||
}
|
||||
ms.Owned = append(ms.Owned, m)
|
||||
}
|
||||
resultChan <- ms
|
||||
}
|
||||
ms := <-resultChan
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(ms); err != nil {
|
||||
common.Log.Warningln("[allocator]:", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
router.Methods("POST").Path("/ip/{id}/{ip}/{prefixlen}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
if subnet, ok := parseCIDR(w, vars["ip"]+"/"+vars["prefixlen"], true); ok {
|
||||
alloc.handleHTTPAllocate(dockerCli, w, vars["id"], r.FormValue("check-alive") == "true", subnet)
|
||||
}
|
||||
})
|
||||
|
||||
router.Methods("POST").Path("/ip/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
alloc.handleHTTPAllocate(dockerCli, w, vars["id"], r.FormValue("check-alive") == "true", defaultSubnet)
|
||||
})
|
||||
|
||||
router.Methods("DELETE").Path("/ip/{id}/{ip}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
func (alloc *Allocator) HandleHTTP(router *mux.Router) {
|
||||
router.Methods("PUT").Path("/ip/{id}/{ip}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
closedChan := w.(http.CloseNotifier).CloseNotify()
|
||||
vars := mux.Vars(r)
|
||||
ident := vars["id"]
|
||||
ipStr := vars["ip"]
|
||||
if ip, err := address.ParseIP(ipStr); err != nil {
|
||||
badRequest(w, err)
|
||||
return
|
||||
} else if err := alloc.Free(ident, ip); err != nil {
|
||||
badRequest(w, fmt.Errorf("Unable to free: %s", err))
|
||||
} else if err := alloc.Claim(ident, ip, closedChan); err != nil {
|
||||
badRequest(w, fmt.Errorf("Unable to claim: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(204)
|
||||
})
|
||||
|
||||
router.Methods("POST").Path("/ip/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
closedChan := w.(http.CloseNotifier).CloseNotify()
|
||||
ident := mux.Vars(r)["id"]
|
||||
newAddr, err := alloc.Allocate(ident, closedChan)
|
||||
if err != nil {
|
||||
badRequest(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s/%d", newAddr.String(), alloc.prefixLen)
|
||||
})
|
||||
|
||||
router.Methods("DELETE").Path("/ip/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ident := mux.Vars(r)["id"]
|
||||
if err := alloc.Delete(ident); err != nil {
|
||||
if err := alloc.Free(ident); err != nil {
|
||||
badRequest(w, err)
|
||||
return
|
||||
}
|
||||
@@ -198,11 +55,18 @@ func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CID
|
||||
w.WriteHeader(204)
|
||||
})
|
||||
|
||||
router.Methods("GET").Path("/ipinfo/tracker").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tracker := ""
|
||||
if alloc.tracker != nil {
|
||||
tracker = alloc.tracker.String()
|
||||
router.Methods("DELETE").Path("/peer").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
alloc.Shutdown()
|
||||
w.WriteHeader(204)
|
||||
})
|
||||
|
||||
router.Methods("DELETE").Path("/peer/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ident := mux.Vars(r)["id"]
|
||||
if err := alloc.AdminTakeoverRanges(ident); err != nil {
|
||||
badRequest(w, err)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, tracker)
|
||||
|
||||
w.WriteHeader(204)
|
||||
})
|
||||
}
|
||||
+76
-85
@@ -4,30 +4,22 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
func HTTPPost(t *testing.T, url string) string {
|
||||
resp, err := http.Post(url, "", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "http response")
|
||||
defer resp.Body.Close()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
return string(body)
|
||||
}
|
||||
|
||||
func HTTPGet(t *testing.T, url string) string {
|
||||
resp, err := http.Get(url)
|
||||
require.NoError(t, err)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
|
||||
defer resp.Body.Close()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
return string(body)
|
||||
@@ -38,76 +30,60 @@ func doHTTP(method string, url string) (resp *http.Response, err error) {
|
||||
return http.DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
func listenHTTP(alloc *Allocator, subnet address.CIDR) int {
|
||||
func listenHTTP(port int, alloc *Allocator) {
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
|
||||
io.WriteString(w, fmt.Sprintln(alloc))
|
||||
})
|
||||
alloc.HandleHTTP(router, subnet, nil)
|
||||
alloc.HandleHTTP(router)
|
||||
|
||||
httpListener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
common.Log.Fatal("Unable to create http listener: ", err)
|
||||
srv := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", port),
|
||||
Handler: router,
|
||||
}
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
common.Error.Fatal("Unable to create http listener: ", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
srv := &http.Server{Handler: router}
|
||||
if err := srv.Serve(httpListener); err != nil {
|
||||
common.Log.Fatal("Unable to serve http: ", err)
|
||||
}
|
||||
}()
|
||||
return httpListener.Addr().(*net.TCPAddr).Port
|
||||
}
|
||||
|
||||
func identURL(port int, containerID string) string {
|
||||
func allocURL(port int, containerID string) string {
|
||||
return fmt.Sprintf("http://localhost:%d/ip/%s", port, containerID)
|
||||
}
|
||||
|
||||
func allocURL(port int, cidr string, containerID string) string {
|
||||
return fmt.Sprintf("http://localhost:%d/ip/%s/%s", port, containerID, cidr)
|
||||
}
|
||||
|
||||
func TestHttp(t *testing.T) {
|
||||
var (
|
||||
containerID = "deadbeef"
|
||||
container2 = "baddf00d"
|
||||
container3 = "b01df00d"
|
||||
universe = "10.0.0.0/8"
|
||||
testCIDR1 = "10.0.3.8/29"
|
||||
testCIDR2 = "10.2.0.0/16"
|
||||
testAddr1 = "10.0.3.9/29"
|
||||
testAddr2 = "10.2.0.1/16"
|
||||
testAddr1 = "10.0.3.9"
|
||||
netSuffix = "/29"
|
||||
testCIDR1 = "10.0.3.8" + netSuffix
|
||||
)
|
||||
|
||||
alloc, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", universe, 1)
|
||||
cidr, _ := address.ParseCIDR(universe)
|
||||
port := listenHTTP(alloc, cidr)
|
||||
alloc := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 1)
|
||||
port := rand.Intn(10000) + 32768
|
||||
fmt.Println("Http test on port", port)
|
||||
go listenHTTP(port, alloc)
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // Allow for http server to get going
|
||||
|
||||
alloc.claimRingForTesting()
|
||||
// Ask the http server for a new address
|
||||
cidr1 := HTTPPost(t, allocURL(port, containerID))
|
||||
wt.AssertEqualString(t, cidr1, testAddr1+netSuffix, "address")
|
||||
|
||||
// Allocate an address in each subnet, and check we got what we expected
|
||||
cidr1a := HTTPPost(t, allocURL(port, testCIDR1, containerID))
|
||||
require.Equal(t, testAddr1, cidr1a, "address")
|
||||
cidr2a := HTTPPost(t, allocURL(port, testCIDR2, containerID))
|
||||
require.Equal(t, testAddr2, cidr2a, "address")
|
||||
// Now, make the same requests again to check the operation is idempotent
|
||||
check := HTTPGet(t, allocURL(port, testCIDR1, containerID))
|
||||
require.Equal(t, cidr1a, check, "address")
|
||||
check = HTTPGet(t, allocURL(port, testCIDR2, containerID))
|
||||
require.Equal(t, cidr2a, check, "address")
|
||||
// Ask the http server for another address and check it's different
|
||||
cidr2 := HTTPPost(t, allocURL(port, container2))
|
||||
wt.AssertFalse(t, cidr2 == testAddr1+netSuffix, "address")
|
||||
|
||||
// Ask the http server for a pair of addresses for another container and check they're different
|
||||
cidr1b := HTTPPost(t, allocURL(port, testCIDR1, container2))
|
||||
require.False(t, cidr1b == testAddr1, "address")
|
||||
cidr2b := HTTPPost(t, allocURL(port, testCIDR2, container2))
|
||||
require.False(t, cidr2b == testAddr2, "address")
|
||||
// Ask for the first container again and we should get the same address again
|
||||
cidr1a := HTTPPost(t, allocURL(port, containerID))
|
||||
wt.AssertEqualString(t, cidr1a, testAddr1+netSuffix, "address")
|
||||
|
||||
// Now free the first container, and we should get its addresses back when we ask
|
||||
doHTTP("DELETE", identURL(port, containerID))
|
||||
|
||||
cidr1c := HTTPPost(t, allocURL(port, testCIDR1, container3))
|
||||
require.Equal(t, testAddr1, cidr1c, "address")
|
||||
cidr2c := HTTPPost(t, allocURL(port, testCIDR2, container3))
|
||||
require.Equal(t, testAddr2, cidr2c, "address")
|
||||
// Now free the first one, and we should get it back when we ask
|
||||
doHTTP("DELETE", allocURL(port, containerID))
|
||||
cidr3 := HTTPPost(t, allocURL(port, container3))
|
||||
wt.AssertEqualString(t, cidr3, testAddr1+netSuffix, "address")
|
||||
|
||||
// Would like to shut down the http server at the end of this test
|
||||
// but it's complicated.
|
||||
@@ -120,54 +96,69 @@ func TestBadHttp(t *testing.T) {
|
||||
testCIDR1 = "10.0.0.0/8"
|
||||
)
|
||||
|
||||
alloc, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 1)
|
||||
alloc := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 1)
|
||||
defer alloc.Stop()
|
||||
cidr, _ := address.ParseCIDR(testCIDR1)
|
||||
port := listenHTTP(alloc, cidr)
|
||||
port := rand.Intn(10000) + 32768
|
||||
fmt.Println("BadHttp test on port", port)
|
||||
go listenHTTP(port, alloc)
|
||||
|
||||
alloc.claimRingForTesting()
|
||||
cidr1 := HTTPPost(t, allocURL(port, testCIDR1, containerID))
|
||||
cidr1 := HTTPPost(t, allocURL(port, containerID))
|
||||
parts := strings.Split(cidr1, "/")
|
||||
testAddr1 := parts[0]
|
||||
// Verb that's not handled
|
||||
resp, err := doHTTP("HEAD", fmt.Sprintf("http://localhost:%d/ip/%s/%s", port, containerID, testAddr1))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode, "http response")
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertStatus(t, resp.StatusCode, http.StatusNotFound, "http response")
|
||||
// Mis-spelled URL
|
||||
resp, err = doHTTP("POST", fmt.Sprintf("http://localhost:%d/xip/%s/", port, containerID))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusNotFound, resp.StatusCode, "http response")
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertStatus(t, resp.StatusCode, http.StatusNotFound, "http response")
|
||||
// Malformed URL
|
||||
resp, err = doHTTP("POST", fmt.Sprintf("http://localhost:%d/ip/%s/foo/bar/baz", port, containerID))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusNotFound, resp.StatusCode, "http response")
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertStatus(t, resp.StatusCode, http.StatusNotFound, "http response")
|
||||
}
|
||||
|
||||
func TestHTTPCancel(t *testing.T) {
|
||||
wt.RunWithTimeout(t, 2*time.Second, func() {
|
||||
impTestHTTPCancel(t)
|
||||
})
|
||||
}
|
||||
|
||||
func impTestHTTPCancel(t *testing.T) {
|
||||
common.InitDefaultLogging(true)
|
||||
var (
|
||||
containerID = "deadbeef"
|
||||
testCIDR1 = "10.0.3.0/29"
|
||||
testCIDR1 = "10.0.3.5/29"
|
||||
)
|
||||
|
||||
// Say quorum=2, so the allocate won't go ahead
|
||||
alloc, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 2)
|
||||
alloc := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 1)
|
||||
defer alloc.Stop()
|
||||
ExpectBroadcastMessage(alloc, nil) // trying to form consensus
|
||||
cidr, _ := address.ParseCIDR(testCIDR1)
|
||||
port := listenHTTP(alloc, cidr)
|
||||
alloc.claimRingForTesting()
|
||||
port := rand.Intn(10000) + 32768
|
||||
fmt.Println("Http test on port", port)
|
||||
go listenHTTP(port, alloc)
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // Allow for http server to get going
|
||||
|
||||
// Stop the alloc so nothing actually works
|
||||
unpause := alloc.pause()
|
||||
|
||||
// Ask the http server for a new address
|
||||
req, _ := http.NewRequest("POST", allocURL(port, testCIDR1, containerID), nil)
|
||||
// On another goroutine, wait for a bit then cancel the request
|
||||
done := make(chan *http.Response)
|
||||
req, _ := http.NewRequest("POST", allocURL(port, containerID), nil)
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
common.Log.Debug("Cancelling allocate")
|
||||
http.DefaultTransport.(*http.Transport).CancelRequest(req)
|
||||
res, _ := http.DefaultClient.Do(req)
|
||||
done <- res
|
||||
}()
|
||||
|
||||
res, _ := http.DefaultClient.Do(req)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
fmt.Println("Cancelling allocate")
|
||||
http.DefaultTransport.(*http.Transport).CancelRequest(req)
|
||||
unpause()
|
||||
res := <-done
|
||||
if res != nil {
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
require.FailNow(t, "Error: Allocate returned non-nil", string(body))
|
||||
wt.Fatalf(t, "Error: Allocate returned non-nil")
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package paxos
|
||||
|
||||
type Participant interface {
|
||||
GossipState() GossipState
|
||||
Update(from GossipState) bool
|
||||
SetQuorum(uint)
|
||||
Propose()
|
||||
Think() bool
|
||||
Consensus() (bool, AcceptedValue)
|
||||
IsElector() bool
|
||||
}
|
||||
|
||||
type Observer struct {
|
||||
}
|
||||
|
||||
func NewObserver() Participant {
|
||||
return &Observer{}
|
||||
}
|
||||
|
||||
func (observer *Observer) GossipState() GossipState {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (observer *Observer) Update(from GossipState) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (observer *Observer) Propose() {
|
||||
}
|
||||
|
||||
func (observer *Observer) SetQuorum(uint) {
|
||||
}
|
||||
|
||||
func (observer *Observer) Think() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (observer *Observer) Consensus() (bool, AcceptedValue) {
|
||||
return false, AcceptedValue{}
|
||||
}
|
||||
|
||||
func (observer *Observer) IsElector() bool {
|
||||
return false
|
||||
}
|
||||
+15
-29
@@ -1,7 +1,8 @@
|
||||
package paxos
|
||||
|
||||
import (
|
||||
"github.com/weaveworks/mesh"
|
||||
"fmt"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
// The node identifier. The use of the UID here is important: Paxos
|
||||
@@ -9,8 +10,8 @@ import (
|
||||
// node does not restart and lose its Paxos state but claim to have
|
||||
// the same ID.
|
||||
type NodeID struct {
|
||||
Name mesh.PeerName
|
||||
UID mesh.PeerUID
|
||||
Name router.PeerName
|
||||
UID router.PeerUID
|
||||
}
|
||||
|
||||
// note all fields exported in structs so we can Gob them
|
||||
@@ -39,7 +40,7 @@ func (a ProposalID) valid() bool {
|
||||
}
|
||||
|
||||
// For seeding IPAM, the value we want consensus on is a set of peer names
|
||||
type Value []mesh.PeerName
|
||||
type Value []router.PeerName
|
||||
|
||||
// An AcceptedValue is a Value plus the proposal which originated that
|
||||
// Value. The origin is not essential, but makes comparing
|
||||
@@ -72,7 +73,7 @@ type Node struct {
|
||||
knows GossipState
|
||||
}
|
||||
|
||||
func NewNode(name mesh.PeerName, uid mesh.PeerUID, quorum uint) *Node {
|
||||
func NewNode(name router.PeerName, uid router.PeerUID, quorum uint) *Node {
|
||||
return &Node{
|
||||
id: NodeID{name, uid},
|
||||
quorum: quorum,
|
||||
@@ -80,10 +81,6 @@ func NewNode(name mesh.PeerName, uid mesh.PeerUID, quorum uint) *Node {
|
||||
}
|
||||
}
|
||||
|
||||
func (node *Node) SetQuorum(quorum uint) {
|
||||
node.quorum = quorum
|
||||
}
|
||||
|
||||
func (node *Node) GossipState() GossipState {
|
||||
return node.knows
|
||||
}
|
||||
@@ -120,17 +117,15 @@ func (node *Node) Update(from GossipState) bool {
|
||||
func max(a uint, b uint) uint {
|
||||
if a > b {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Initiate a new proposal, i.e. the Paxos "Prepare" step. This is
|
||||
// simply a matter of gossipping a new proposal that supersedes all
|
||||
// others.
|
||||
func (node *Node) Propose() {
|
||||
if node.quorum == 0 {
|
||||
panic("Paxos node.Propose() called with no quorum set")
|
||||
}
|
||||
// Find the highest round number around
|
||||
round := uint(0)
|
||||
|
||||
@@ -230,7 +225,7 @@ func (node *Node) Think() bool {
|
||||
// about. This is not necessarily all peer names, but it is at least
|
||||
// a quorum, and so good enough for seeding the ring.
|
||||
func (node *Node) pickValue() Value {
|
||||
val := make([]mesh.PeerName, len(node.knows))
|
||||
val := make([]router.PeerName, len(node.knows))
|
||||
i := 0
|
||||
for id := range node.knows {
|
||||
val[i] = id.Name
|
||||
@@ -241,9 +236,6 @@ func (node *Node) pickValue() Value {
|
||||
|
||||
// Has a consensus been reached, based on the known claims of other nodes?
|
||||
func (node *Node) Consensus() (bool, AcceptedValue) {
|
||||
if node.quorum == 0 {
|
||||
return false, AcceptedValue{}
|
||||
}
|
||||
counts := map[ProposalID]uint{}
|
||||
|
||||
for _, claims := range node.knows {
|
||||
@@ -260,16 +252,10 @@ func (node *Node) Consensus() (bool, AcceptedValue) {
|
||||
return false, AcceptedValue{}
|
||||
}
|
||||
|
||||
func (node *Node) IsElector() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Elector bool
|
||||
KnownNodes int
|
||||
Quorum uint
|
||||
}
|
||||
|
||||
func NewStatus(node *Node) *Status {
|
||||
return &Status{true, len(node.knows), node.quorum}
|
||||
func (node *Node) String() string {
|
||||
if ok, val := node.Consensus(); ok {
|
||||
return fmt.Sprintf("Consensus reached with size %d", len(val.Value))
|
||||
} else {
|
||||
return fmt.Sprintf("Nodes known: %d, Quorum size: %d", len(node.knows), node.quorum)
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,10 @@ package paxos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/weaveworks/weave/router"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
)
|
||||
|
||||
type TestNode struct {
|
||||
@@ -97,8 +96,8 @@ func makeRandomModel(params *TestParams, r *rand.Rand, t *testing.T) *Model {
|
||||
}
|
||||
|
||||
for i := range m.nodes {
|
||||
m.nodes[i].Node = NewNode(mesh.PeerName(i/2+1),
|
||||
mesh.PeerUID(r.Int63()), m.quorum)
|
||||
m.nodes[i].Node = NewNode(router.PeerName(i/2+1),
|
||||
router.PeerUID(r.Int63()), m.quorum)
|
||||
m.nodes[i].Propose()
|
||||
}
|
||||
|
||||
@@ -172,8 +171,8 @@ func (m *Model) isolateNode(node *TestNode) {
|
||||
|
||||
// Restart a node
|
||||
func (m *Model) restart(node *TestNode) {
|
||||
node.Node = NewNode(mesh.PeerName(m.nextID),
|
||||
mesh.PeerUID(m.r.Int63()), m.quorum)
|
||||
node.Node = NewNode(router.PeerName(m.nextID),
|
||||
router.PeerUID(m.r.Int63()), m.quorum)
|
||||
m.nextID++
|
||||
node.Propose()
|
||||
|
||||
@@ -300,14 +299,14 @@ func (m *Model) validate() {
|
||||
for i := range m.nodes {
|
||||
ok, val := m.nodes[i].Consensus()
|
||||
if !ok {
|
||||
m.dump()
|
||||
//m.dump()
|
||||
m.t.Fatal("Node doesn't know about consensus")
|
||||
}
|
||||
|
||||
firstConsensus := m.nodes[i].firstConsensus
|
||||
if firstConsensus.Origin.valid() &&
|
||||
firstConsensus.Origin != val.Origin {
|
||||
m.dump()
|
||||
//m.dump()
|
||||
m.t.Fatal("Consensus mismatch")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package ipam
|
||||
|
||||
type prime struct {
|
||||
resultChan chan<- struct{}
|
||||
}
|
||||
|
||||
func (c *prime) Try(alloc *Allocator) bool {
|
||||
if !alloc.ring.Empty() {
|
||||
close(c.resultChan)
|
||||
return true
|
||||
}
|
||||
|
||||
alloc.establishRing()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *prime) Cancel() {
|
||||
close(c.resultChan)
|
||||
}
|
||||
|
||||
func (c *prime) ForContainer(ident string) bool {
|
||||
return false
|
||||
}
|
||||
+9
-8
@@ -1,21 +1,19 @@
|
||||
package ring
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
// Entry represents entries around the ring
|
||||
type entry struct {
|
||||
Token address.Address // The start of this range
|
||||
Peer mesh.PeerName // Who owns this range
|
||||
Peer router.PeerName // Who owns this range
|
||||
Version uint32 // Version of this range
|
||||
Free address.Count // Number of free IPs in this range
|
||||
Free address.Offset // Number of free IPs in this range
|
||||
}
|
||||
|
||||
func (e *entry) Equal(e2 *entry) bool {
|
||||
@@ -23,10 +21,9 @@ func (e *entry) Equal(e2 *entry) bool {
|
||||
e.Version == e2.Version
|
||||
}
|
||||
|
||||
func (e *entry) update(peername mesh.PeerName, free address.Count) {
|
||||
func (e *entry) update(peername router.PeerName, free address.Offset) {
|
||||
e.Peer = peername
|
||||
e.Version++
|
||||
common.Log.Debugln(fmt.Sprintf("Updating ring token=%s current peer=%s updated peer=%s new version=%s", e.Token, e.Peer, peername, fmt.Sprint(e.Version)))
|
||||
e.Free = free
|
||||
}
|
||||
|
||||
@@ -71,6 +68,10 @@ func (es entries) get(token address.Address) (*entry, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (es *entries) remove(i int) {
|
||||
*es = (*es)[:i+copy((*es)[i:], (*es)[i+1:])]
|
||||
}
|
||||
|
||||
// Is token between entries at i and j?
|
||||
// NB i and j can overflow and will wrap
|
||||
// NBB this function does not work very well if there is only one
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package ring
|
||||
|
||||
/* Exported variables for monitoring and management */
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
expRingSize = expvar.NewMap("ipam.ringSize")
|
||||
expRingEntries = expvar.NewMap("ipam.ringEntries")
|
||||
)
|
||||
|
||||
type _int int
|
||||
|
||||
func (i _int) String() string {
|
||||
return strconv.Itoa(int(i))
|
||||
}
|
||||
|
||||
type _uint32 uint32
|
||||
|
||||
func (i _uint32) String() string {
|
||||
return strconv.FormatUint(uint64(i), 10)
|
||||
}
|
||||
|
||||
func (r *Ring) updateExportedVariables() {
|
||||
ringName := r.Start.String()
|
||||
expRingSize.Set(ringName, _uint32(r.End-r.Start))
|
||||
expRingEntries.Set(ringName, _int(len(r.Entries)))
|
||||
}
|
||||
+153
-293
@@ -10,23 +10,17 @@ import (
|
||||
"io"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
type OnUpdate func(prev, curr []address.Range, local bool)
|
||||
|
||||
// Ring represents the ring itself
|
||||
type Ring struct {
|
||||
Start, End address.Address // [min, max) tokens in this ring. Due to wrapping, min == max (effectively)
|
||||
Peer mesh.PeerName // name of peer owning this ring instance
|
||||
Peer router.PeerName // name of peer owning this ring instance
|
||||
Entries entries // list of entries sorted by token
|
||||
Seeds []mesh.PeerName // peers with which the ring was seeded
|
||||
onUpdate OnUpdate
|
||||
}
|
||||
|
||||
func (r *Ring) assertInvariants() {
|
||||
@@ -38,55 +32,46 @@ func (r *Ring) assertInvariants() {
|
||||
|
||||
// Errors returned by Merge
|
||||
var (
|
||||
ErrNotSorted = errors.New("Ring not sorted")
|
||||
ErrTokenRepeated = errors.New("Token appears twice in ring")
|
||||
ErrTokenOutOfRange = errors.New("Token is out of range")
|
||||
ErrDifferentSeeds = errors.New("Received ring was seeded differently from ours")
|
||||
ErrDifferentRange = errors.New("Received range differs from ours")
|
||||
ErrNotSorted = errors.New("Ring not sorted")
|
||||
ErrTokenRepeated = errors.New("Token appears twice in ring")
|
||||
ErrTokenOutOfRange = errors.New("Token is out of range")
|
||||
ErrDifferentSubnets = errors.New("IP Allocator with different subnet detected")
|
||||
ErrNewerVersion = errors.New("Received new version for entry I own!")
|
||||
ErrInvalidEntry = errors.New("Received invalid state update!")
|
||||
ErrEntryInMyRange = errors.New("Received new entry in my range!")
|
||||
ErrNoFreeSpace = errors.New("No free space found!")
|
||||
ErrInvalidTimeout = errors.New("dt must be greater than 0")
|
||||
ErrNotFound = errors.New("No entries for peer found")
|
||||
)
|
||||
|
||||
func errInconsistentEntry(mine, theirs *entry) error {
|
||||
return fmt.Errorf("Inconsistent entries for %s: owned by %s but incoming message says %s", mine.Token, mine.Peer, theirs.Peer)
|
||||
}
|
||||
func errEntryInMyRange(theirs *entry) error {
|
||||
return fmt.Errorf("Peer %s says it owns the IP range from %s, which I think I own", theirs.Peer, theirs.Token)
|
||||
}
|
||||
func errNewerVersion(mine, theirs *entry) error {
|
||||
return fmt.Errorf("Received update for IP range I own at %s v%d: incoming message says owner %s v%d", mine.Token, mine.Version, theirs.Peer, theirs.Version)
|
||||
}
|
||||
|
||||
func (r *Ring) checkInvariants() error {
|
||||
return r.checkEntries(r.Entries)
|
||||
}
|
||||
|
||||
func (r *Ring) checkEntries(entries entries) error {
|
||||
if !sort.IsSorted(entries) {
|
||||
if !sort.IsSorted(r.Entries) {
|
||||
return ErrNotSorted
|
||||
}
|
||||
|
||||
// Check no token appears twice
|
||||
// We know it's sorted...
|
||||
for i := 1; i < len(entries); i++ {
|
||||
if entries[i-1].Token == entries[i].Token {
|
||||
for i := 1; i < len(r.Entries); i++ {
|
||||
if r.Entries[i-1].Token == r.Entries[i].Token {
|
||||
return ErrTokenRepeated
|
||||
}
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
if len(r.Entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check tokens are in range
|
||||
if entries.entry(0).Token < r.Start {
|
||||
if r.Entries.entry(0).Token < r.Start {
|
||||
return ErrTokenOutOfRange
|
||||
}
|
||||
if entries.entry(-1).Token >= r.End {
|
||||
if r.Entries.entry(-1).Token >= r.End {
|
||||
return ErrTokenOutOfRange
|
||||
}
|
||||
|
||||
// Check all the freespaces are in range
|
||||
for i, entry := range entries {
|
||||
next := entries.entry(i + 1)
|
||||
for i, entry := range r.Entries {
|
||||
next := r.Entries.entry(i + 1)
|
||||
distance := r.distance(entry.Token, next.Token)
|
||||
|
||||
if entry.Free > distance {
|
||||
@@ -97,46 +82,35 @@ func (r *Ring) checkEntries(entries entries) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Ring) trackUpdates() func() {
|
||||
return r.trackUpdatesOfPeer(r.Peer)
|
||||
}
|
||||
|
||||
func (r *Ring) trackUpdatesOfPeer(peer mesh.PeerName) func() {
|
||||
if r.onUpdate == nil {
|
||||
return func() {}
|
||||
}
|
||||
ranges := r.OwnedRangesOfPeer(peer)
|
||||
return func() { r.onUpdate(ranges, r.OwnedRangesOfPeer(peer), peer == r.Peer) }
|
||||
}
|
||||
|
||||
// New creates an empty ring belonging to peer.
|
||||
func New(start, end address.Address, peer mesh.PeerName, f OnUpdate) *Ring {
|
||||
func New(start, end address.Address, peer router.PeerName) *Ring {
|
||||
common.Assert(start < end)
|
||||
|
||||
ring := &Ring{Start: start, End: end, Peer: peer, Entries: make([]*entry, 0), onUpdate: f}
|
||||
ring := &Ring{Start: start, End: end, Peer: peer, Entries: make([]*entry, 0)}
|
||||
ring.updateExportedVariables()
|
||||
return ring
|
||||
}
|
||||
|
||||
func (r *Ring) Restore(other *Ring) {
|
||||
defer r.trackUpdates()()
|
||||
|
||||
onUpdate := r.onUpdate
|
||||
*r = *other
|
||||
r.onUpdate = onUpdate
|
||||
}
|
||||
|
||||
func (r *Ring) Range() address.Range {
|
||||
return address.Range{Start: r.Start, End: r.End}
|
||||
// TotalRemoteFree returns the approximate number of free IPs
|
||||
// on other hosts.
|
||||
func (r *Ring) TotalRemoteFree() address.Offset {
|
||||
result := address.Offset(0)
|
||||
for _, entry := range r.Entries {
|
||||
if entry.Peer != r.Peer {
|
||||
result += entry.Free
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns the distance between two tokens on this ring, dealing
|
||||
// with ranges which cross the origin
|
||||
func (r *Ring) distance(start, end address.Address) address.Count {
|
||||
func (r *Ring) distance(start, end address.Address) address.Offset {
|
||||
if end > start {
|
||||
return address.Count(end - start)
|
||||
return address.Offset(end - start)
|
||||
}
|
||||
|
||||
return address.Count((r.End - start) + (end - r.Start))
|
||||
return address.Offset((r.End - start) + (end - r.Start))
|
||||
}
|
||||
|
||||
// GrantRangeToHost modifies the ring such that range [start, end)
|
||||
@@ -144,12 +118,12 @@ func (r *Ring) distance(start, end address.Address) address.Count {
|
||||
// Preconditions:
|
||||
// - start < end
|
||||
// - [start, end) must be owned by the calling peer
|
||||
func (r *Ring) GrantRangeToHost(start, end address.Address, peer mesh.PeerName) {
|
||||
func (r *Ring) GrantRangeToHost(start, end address.Address, peer router.PeerName) {
|
||||
//fmt.Printf("%s GrantRangeToHost [%v,%v) -> %s\n", r.Peer, start, end, peer)
|
||||
|
||||
r.assertInvariants()
|
||||
defer r.trackUpdates()()
|
||||
defer r.assertInvariants()
|
||||
defer r.updateExportedVariables()
|
||||
|
||||
// ----------------- Start of Checks -----------------
|
||||
|
||||
@@ -211,80 +185,32 @@ func (r *Ring) GrantRangeToHost(start, end address.Address, peer mesh.PeerName)
|
||||
r.Entries.insert(entry{Token: end, Peer: r.Peer, Free: endFree})
|
||||
}
|
||||
|
||||
// Merge the given ring into this ring and indicate whether this ring
|
||||
// got updated as a result.
|
||||
func (r *Ring) Merge(gossip Ring, hasAllocations func(r []address.Range) bool) (bool, error) {
|
||||
// Merge the given ring into this ring and return any new ranges added
|
||||
func (r *Ring) Merge(gossip Ring) error {
|
||||
r.assertInvariants()
|
||||
defer r.trackUpdates()()
|
||||
defer r.assertInvariants()
|
||||
defer r.updateExportedVariables()
|
||||
|
||||
// Don't panic when checking the gossiped in ring.
|
||||
// In this case just return any error found.
|
||||
if err := gossip.checkInvariants(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(gossip.Seeds) > 0 && len(r.Seeds) > 0 {
|
||||
if len(gossip.Seeds) != len(r.Seeds) {
|
||||
return false, ErrDifferentSeeds
|
||||
}
|
||||
for i, seed := range gossip.Seeds {
|
||||
if seed != r.Seeds[i] {
|
||||
return false, ErrDifferentSeeds
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Start != gossip.Start || r.End != gossip.End {
|
||||
return false, ErrDifferentRange
|
||||
return ErrDifferentSubnets
|
||||
}
|
||||
|
||||
result, updated, err := r.Entries.merge(gossip.Entries, r.Peer, r, hasAllocations)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// reset the free space for entries if there is invalid free space
|
||||
// due to accepting an unexpected update from the peers
|
||||
for i := 0; i < len(result); i++ {
|
||||
distance := r.distance(result.entry(i).Token, result.entry(i+1).Token)
|
||||
if result.entry(i).Peer == r.Peer && result.entry(i).Free > distance {
|
||||
// case that can arise when a range that we own that got split and had no allocations
|
||||
result.entry(i).Free = distance
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.checkEntries(result); err != nil {
|
||||
return false, fmt.Errorf("Merge of incoming data causes: %s", err)
|
||||
}
|
||||
|
||||
if len(r.Seeds) == 0 {
|
||||
r.Seeds = gossip.Seeds
|
||||
}
|
||||
r.Entries = result
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
// Merge other entries into ours, and complain when that stomps on
|
||||
// entries belonging to ourPeer. Returns the merged entries and an
|
||||
// indication whether the merge resulted in any changes, i.e. the
|
||||
// result differs from the original.
|
||||
func (es entries) merge(other entries, ourPeer mesh.PeerName, r *Ring, hasAllocations func(r []address.Range) bool) (result entries, updated bool, err error) {
|
||||
var mine, theirs *entry
|
||||
var previousOwner *mesh.PeerName
|
||||
// Now merge their ring with yours, in a temporary ring.
|
||||
var result entries
|
||||
addToResult := func(e entry) { result = append(result, &e) }
|
||||
addTheirs := func(e entry) {
|
||||
addToResult(e)
|
||||
updated = true
|
||||
previousOwner = nil
|
||||
}
|
||||
|
||||
// i is index into es; j is index into other
|
||||
var mine, theirs *entry
|
||||
var previousOwner *router.PeerName
|
||||
// i is index into r.Entries; j is index into gossip.Entries
|
||||
var i, j int
|
||||
for i < len(es) && j < len(other) {
|
||||
mine, theirs = es[i], other[j]
|
||||
common.Log.Debugf("[ring %s]: Merge mine.Token=%s theirs.Token=%s mine.Peer=%s theirs.Peer=%s mine.Version=%v theirs.Version=%v", ourPeer, mine.Token, theirs.Token, mine.Peer, theirs.Peer, mine.Version, theirs.Version)
|
||||
for i < len(r.Entries) && j < len(gossip.Entries) {
|
||||
mine, theirs = r.Entries[i], gossip.Entries[j]
|
||||
switch {
|
||||
case mine.Token < theirs.Token:
|
||||
addToResult(*mine)
|
||||
@@ -292,82 +218,53 @@ func (es entries) merge(other entries, ourPeer mesh.PeerName, r *Ring, hasAlloca
|
||||
i++
|
||||
case mine.Token > theirs.Token:
|
||||
// insert, checking that a range owned by us hasn't been split
|
||||
if previousOwner != nil && *previousOwner == ourPeer && theirs.Peer != ourPeer {
|
||||
// check we have no allocations in the range that got split
|
||||
if hasAllocations(r.makeRanges(theirs.Token, mine.Token)) {
|
||||
err = errEntryInMyRange(theirs)
|
||||
return
|
||||
}
|
||||
if previousOwner != nil && *previousOwner == r.Peer && theirs.Peer != r.Peer {
|
||||
return ErrEntryInMyRange
|
||||
}
|
||||
addTheirs(*theirs)
|
||||
addToResult(*theirs)
|
||||
previousOwner = nil
|
||||
j++
|
||||
case mine.Token == theirs.Token:
|
||||
// merge
|
||||
switch {
|
||||
case mine.Version >= theirs.Version:
|
||||
if mine.Version == theirs.Version && !mine.Equal(theirs) {
|
||||
if mine.Peer == ourPeer {
|
||||
// if we own the entry and has allocations
|
||||
if hasAllocations(r.makeRanges(theirs.Token, es.entry(i+1).Token)) {
|
||||
err = errInconsistentEntry(mine, theirs)
|
||||
return
|
||||
}
|
||||
}
|
||||
// tie-break here, pick the entry with the highest free count
|
||||
if mine.Free >= theirs.Free {
|
||||
addToResult(*mine)
|
||||
previousOwner = &mine.Peer
|
||||
} else {
|
||||
addTheirs(*theirs)
|
||||
}
|
||||
} else {
|
||||
addToResult(*mine)
|
||||
previousOwner = &mine.Peer
|
||||
common.Debug.Printf("Error merging entries at %s - %v != %v\n", mine.Token, mine, theirs)
|
||||
return ErrInvalidEntry
|
||||
}
|
||||
addToResult(*mine)
|
||||
previousOwner = &mine.Peer
|
||||
case mine.Version < theirs.Version:
|
||||
if mine.Peer == ourPeer {
|
||||
// We received update to our own tokens accept the received entry
|
||||
// if either it belongs to a different peer and we do not have allocations
|
||||
// in the range effectively given away, or it belongs to our own peer, in
|
||||
// which case we should set our version to the one received plus one,
|
||||
// effectively imposing our existing entry.
|
||||
if theirs.Peer != ourPeer && !hasAllocations(r.makeRanges(mine.Token, es.entry(i+1).Token)) {
|
||||
common.Log.Infof("ring: repair inconsistent data by accepting token %v from %v - no allocations up to %v", theirs.Token, theirs.Peer, es.entry(i+1).Token)
|
||||
addTheirs(*theirs)
|
||||
} else if theirs.Peer == ourPeer {
|
||||
mine.Version = theirs.Version + 1
|
||||
addToResult(*mine)
|
||||
} else {
|
||||
err = errNewerVersion(mine, theirs)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
addTheirs(*theirs)
|
||||
if mine.Peer == r.Peer { // We shouldn't receive updates to our own tokens
|
||||
return ErrNewerVersion
|
||||
}
|
||||
addToResult(*theirs)
|
||||
previousOwner = nil
|
||||
}
|
||||
i++
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, either i is at the end of es or j is at the end
|
||||
// of other, so copy over the remaining entries.
|
||||
// At this point, either i is at the end of r or j is at the end
|
||||
// of gossip, so copy over the remaining entries.
|
||||
|
||||
for ; i < len(es); i++ {
|
||||
mine = es[i]
|
||||
for ; i < len(r.Entries); i++ {
|
||||
mine = r.Entries[i]
|
||||
addToResult(*mine)
|
||||
}
|
||||
|
||||
for ; j < len(other); j++ {
|
||||
theirs = other[j]
|
||||
if previousOwner != nil && *previousOwner == ourPeer && theirs.Peer != ourPeer {
|
||||
err = errEntryInMyRange(theirs)
|
||||
return
|
||||
for ; j < len(gossip.Entries); j++ {
|
||||
theirs = gossip.Entries[j]
|
||||
if previousOwner != nil && *previousOwner == r.Peer && theirs.Peer != r.Peer {
|
||||
return ErrEntryInMyRange
|
||||
}
|
||||
addTheirs(*theirs)
|
||||
addToResult(*theirs)
|
||||
previousOwner = nil
|
||||
}
|
||||
|
||||
return
|
||||
r.Entries = result
|
||||
return nil
|
||||
}
|
||||
|
||||
// Empty returns true if the ring has no entries
|
||||
@@ -375,11 +272,6 @@ func (r *Ring) Empty() bool {
|
||||
return len(r.Entries) == 0
|
||||
}
|
||||
|
||||
// convenience function to normalise a single range that may span zero
|
||||
func (r *Ring) makeRanges(start, end address.Address) []address.Range {
|
||||
return r.splitRangesOverZero([]address.Range{{Start: start, End: end}})
|
||||
}
|
||||
|
||||
// Given a slice of ranges which are all in the right order except
|
||||
// possibly the last one spans zero, fix that up and return the slice
|
||||
func (r *Ring) splitRangesOverZero(ranges []address.Range) []address.Range {
|
||||
@@ -405,14 +297,10 @@ func (r *Ring) splitRangesOverZero(ranges []address.Range) []address.Range {
|
||||
// ranges are owned by this peer. Will split ranges which
|
||||
// span 0 in the ring.
|
||||
func (r *Ring) OwnedRanges() (result []address.Range) {
|
||||
return r.OwnedRangesOfPeer(r.Peer)
|
||||
}
|
||||
|
||||
func (r *Ring) OwnedRangesOfPeer(peer mesh.PeerName) (result []address.Range) {
|
||||
r.assertInvariants()
|
||||
|
||||
for i, entry := range r.Entries {
|
||||
if entry.Peer == peer {
|
||||
if entry.Peer == r.Peer {
|
||||
nextEntry := r.Entries.entry(i + 1)
|
||||
result = append(result, address.Range{Start: entry.Token, End: nextEntry.Token})
|
||||
}
|
||||
@@ -421,55 +309,43 @@ func (r *Ring) OwnedRangesOfPeer(peer mesh.PeerName) (result []address.Range) {
|
||||
return r.splitRangesOverZero(result)
|
||||
}
|
||||
|
||||
// For printing status
|
||||
type RangeInfo struct {
|
||||
Peer mesh.PeerName
|
||||
address.Range
|
||||
Version uint32
|
||||
}
|
||||
|
||||
func (r *Ring) AllRangeInfo() (result []RangeInfo) {
|
||||
for i, entry := range r.Entries {
|
||||
for _, r := range r.makeRanges(entry.Token, r.Entries.entry(i+1).Token) {
|
||||
result = append(result, RangeInfo{entry.Peer, r, entry.Version})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ClaimForPeers claims the entire ring for the array of peers passed
|
||||
// in. Only works for empty rings. Each claimed range is CIDR-aligned.
|
||||
func (r *Ring) ClaimForPeers(peers []mesh.PeerName) {
|
||||
// in. Only works for empty rings.
|
||||
func (r *Ring) ClaimForPeers(peers []router.PeerName) {
|
||||
common.Assert(r.Empty())
|
||||
|
||||
defer r.trackUpdates()()
|
||||
defer r.assertInvariants()
|
||||
defer func() {
|
||||
e := r.Entries[len(r.Entries)-1]
|
||||
common.Assert(address.Add(e.Token, address.Offset(e.Free)) == r.End)
|
||||
}()
|
||||
defer r.updateExportedVariables()
|
||||
|
||||
r.subdivide(r.Start, r.End, peers)
|
||||
r.Seeds = peers
|
||||
totalSize := r.distance(r.Start, r.End)
|
||||
share := totalSize/address.Offset(len(peers)) + 1
|
||||
remainder := totalSize % address.Offset(len(peers))
|
||||
pos := r.Start
|
||||
|
||||
for i, peer := range peers {
|
||||
if address.Offset(i) == remainder {
|
||||
share--
|
||||
if share == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if e, found := r.Entries.get(pos); found {
|
||||
e.update(peer, share)
|
||||
} else {
|
||||
r.Entries.insert(entry{Token: pos, Peer: peer, Free: share})
|
||||
}
|
||||
|
||||
pos += address.Address(share)
|
||||
}
|
||||
|
||||
common.Assert(pos == r.End)
|
||||
}
|
||||
|
||||
// subdivide subdivides the [from,to) CIDR for the given peers into
|
||||
// CIDR-aligned subranges.
|
||||
func (r *Ring) subdivide(from, to address.Address, peers []mesh.PeerName) {
|
||||
share := address.Length(to, from)
|
||||
if share == 0 {
|
||||
return
|
||||
}
|
||||
if share == 1 || len(peers) == 1 {
|
||||
r.Entries.insert(entry{Token: from, Peer: peers[0], Free: share})
|
||||
return
|
||||
}
|
||||
mid := address.Add(from, address.Offset(share/2))
|
||||
r.subdivide(from, mid, peers[:len(peers)/2])
|
||||
r.subdivide(address.Add(mid, address.Offset(share%2)), to, peers[len(peers)/2:])
|
||||
func (r *Ring) ClaimItAll() {
|
||||
r.ClaimForPeers([]router.PeerName{r.Peer})
|
||||
}
|
||||
|
||||
func (r *Ring) FprintWithNicknames(w io.Writer, m map[mesh.PeerName]string) {
|
||||
func (r *Ring) FprintWithNicknames(w io.Writer, m map[router.PeerName]string) {
|
||||
for _, entry := range r.Entries {
|
||||
nickname, found := m[entry.Peer]
|
||||
if found {
|
||||
@@ -484,16 +360,17 @@ func (r *Ring) FprintWithNicknames(w io.Writer, m map[mesh.PeerName]string) {
|
||||
func (r *Ring) String() string {
|
||||
var buffer bytes.Buffer
|
||||
fmt.Fprintf(&buffer, "Ring [%s, %s)", r.Start, r.End)
|
||||
r.FprintWithNicknames(&buffer, make(map[mesh.PeerName]string))
|
||||
r.FprintWithNicknames(&buffer, make(map[router.PeerName]string))
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// ReportFree is used by the allocator to tell the ring how many free
|
||||
// ips are in a given range, so that ChoosePeersToAskForSpace can make
|
||||
// more intelligent decisions. Returns true if any changes made.
|
||||
func (r *Ring) ReportFree(freespace map[address.Address]address.Count) (updated bool) {
|
||||
// ReportFree is used by the allocator to tell the ring
|
||||
// how many free ips are in a given range, so that ChoosePeerToAskForSpace
|
||||
// can make more intelligent decisions.
|
||||
func (r *Ring) ReportFree(freespace map[address.Address]address.Offset) {
|
||||
r.assertInvariants()
|
||||
defer r.assertInvariants()
|
||||
defer r.updateExportedVariables()
|
||||
|
||||
common.Assert(!r.Empty())
|
||||
entries := r.Entries
|
||||
@@ -520,45 +397,27 @@ func (r *Ring) ReportFree(freespace map[address.Address]address.Count) (updated
|
||||
// Check we're not reporting more space than the range
|
||||
entry, next := entries.entry(i), entries.entry(i+1)
|
||||
maxSize := r.distance(entry.Token, next.Token)
|
||||
common.Assert(free <= address.Count(maxSize))
|
||||
common.Assert(free <= maxSize)
|
||||
|
||||
if entries[i].Free == free {
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
entries[i].Free = free
|
||||
entries[i].Version++
|
||||
common.Log.Debugf("[ring %s]: ReportFree token=%s peer=%s version=%v", r.Peer, entries[i].Token, entries[i].Peer, entries[i].Version)
|
||||
updated = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type weightedPeer struct {
|
||||
weight float64
|
||||
peername mesh.PeerName
|
||||
}
|
||||
type weightedPeers []weightedPeer
|
||||
|
||||
// Note Less is using > so that bigger weights sort earlier
|
||||
func (ws weightedPeers) Less(i, j int) bool { return ws[i].weight > ws[j].weight }
|
||||
func (ws weightedPeers) Len() int { return len(ws) }
|
||||
func (ws weightedPeers) Swap(i, j int) { ws[i], ws[j] = ws[j], ws[i] }
|
||||
|
||||
// ChoosePeersToAskForSpace returns all peers we can ask for space in
|
||||
// the range [start, end), in weighted-random order. Assumes start<end.
|
||||
func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []mesh.PeerName {
|
||||
totalSpacePerPeer := make(map[mesh.PeerName]address.Count)
|
||||
// ChoosePeerToAskForSpace chooses a weighted-random peer to ask
|
||||
// for space.
|
||||
func (r *Ring) ChoosePeerToAskForSpace() (result router.PeerName, err error) {
|
||||
var (
|
||||
sum address.Offset
|
||||
totalSpacePerPeer = make(map[router.PeerName]address.Offset) // Compute total free space per peer
|
||||
)
|
||||
|
||||
// iterate through tokens
|
||||
for i, entry := range r.Entries {
|
||||
// Ignore entries that don't span the range we want
|
||||
if i+1 < len(r.Entries) && r.Entries.entry(i+1).Token-1 < start {
|
||||
continue
|
||||
}
|
||||
if entry.Token >= end {
|
||||
break
|
||||
}
|
||||
for _, entry := range r.Entries {
|
||||
// Ignore ranges with no free space
|
||||
if entry.Free <= 0 {
|
||||
continue
|
||||
@@ -570,54 +429,59 @@ func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []mesh.PeerN
|
||||
}
|
||||
|
||||
totalSpacePerPeer[entry.Peer] += entry.Free
|
||||
sum += entry.Free
|
||||
}
|
||||
|
||||
// Compute weighted random numbers, then sort.
|
||||
// This isn't perfect, e.g. an item with weight 2 will get chosen more than
|
||||
// twice as often as an item with weight 1, but it's good enough for our purposes.
|
||||
ws := make(weightedPeers, 0, len(totalSpacePerPeer))
|
||||
if sum == 0 {
|
||||
err = ErrNoFreeSpace
|
||||
return
|
||||
}
|
||||
|
||||
// Pick random peer, weighted by total free space
|
||||
rn := rand.Int63n(int64(sum))
|
||||
for peername, space := range totalSpacePerPeer {
|
||||
ws = append(ws, weightedPeer{weight: float64(space) * rand.Float64(), peername: peername})
|
||||
rn -= int64(space)
|
||||
if rn < 0 {
|
||||
return peername, nil
|
||||
}
|
||||
}
|
||||
sort.Sort(ws)
|
||||
result := make([]mesh.PeerName, len(ws))
|
||||
for i, wp := range ws {
|
||||
result[i] = wp.peername
|
||||
}
|
||||
return result
|
||||
|
||||
panic("Should never reach this point")
|
||||
}
|
||||
|
||||
func (r *Ring) PickPeerForTransfer(isValid func(mesh.PeerName) bool) mesh.PeerName {
|
||||
func (r *Ring) PickPeerForTransfer() router.PeerName {
|
||||
for _, entry := range r.Entries {
|
||||
if entry.Peer != r.Peer && isValid(entry.Peer) {
|
||||
if entry.Peer != r.Peer {
|
||||
return entry.Peer
|
||||
}
|
||||
}
|
||||
return mesh.UnknownPeerName
|
||||
return router.UnknownPeerName
|
||||
}
|
||||
|
||||
// Transfer will mark all entries associated with 'from' peer as owned by 'to' peer
|
||||
// and return ranges indicating the new space we picked up
|
||||
func (r *Ring) Transfer(from, to mesh.PeerName) []address.Range {
|
||||
func (r *Ring) Transfer(from, to router.PeerName) (error, []address.Range) {
|
||||
r.assertInvariants()
|
||||
defer r.trackUpdates()()
|
||||
defer r.trackUpdatesOfPeer(from)()
|
||||
defer r.assertInvariants()
|
||||
defer r.updateExportedVariables()
|
||||
|
||||
var newRanges []address.Range
|
||||
found := false
|
||||
|
||||
for i, entry := range r.Entries {
|
||||
if entry.Peer == from {
|
||||
found = true
|
||||
entry.Peer = to
|
||||
// bump version by large value so that transfer of range takes precedence (if there is any conflict in merging entry)
|
||||
// over other opertaions on the entry that increments the version
|
||||
entry.Version = entry.Version + 100
|
||||
common.Log.Debugf("[ring %s]: Transfer token=%s from=%s to=%s version=%v", r.Peer, entry.Token, from, to, entry.Version)
|
||||
entry.Version++
|
||||
newRanges = append(newRanges, address.Range{Start: entry.Token, End: r.Entries.entry(i + 1).Token})
|
||||
}
|
||||
}
|
||||
|
||||
return r.splitRangesOverZero(newRanges)
|
||||
if !found {
|
||||
return ErrNotFound, nil
|
||||
}
|
||||
|
||||
return nil, r.splitRangesOverZero(newRanges)
|
||||
}
|
||||
|
||||
// Contains returns true if addr is in this ring
|
||||
@@ -626,13 +490,13 @@ func (r *Ring) Contains(addr address.Address) bool {
|
||||
}
|
||||
|
||||
// Owner returns the peername which owns the range containing addr
|
||||
func (r *Ring) Owner(token address.Address) mesh.PeerName {
|
||||
func (r *Ring) Owner(token address.Address) router.PeerName {
|
||||
common.Assert(r.Start <= token && token < r.End)
|
||||
|
||||
r.assertInvariants()
|
||||
// There can be no owners on an empty ring
|
||||
if r.Empty() {
|
||||
return mesh.UnknownPeerName
|
||||
return router.UnknownPeerName
|
||||
}
|
||||
|
||||
// Look for the right-most entry, less than or equal to token
|
||||
@@ -645,8 +509,8 @@ func (r *Ring) Owner(token address.Address) mesh.PeerName {
|
||||
}
|
||||
|
||||
// Get the set of PeerNames mentioned in the ring
|
||||
func (r *Ring) PeerNames() map[mesh.PeerName]struct{} {
|
||||
res := make(map[mesh.PeerName]struct{})
|
||||
func (r *Ring) PeerNames() map[router.PeerName]struct{} {
|
||||
res := make(map[router.PeerName]struct{})
|
||||
|
||||
for _, entry := range r.Entries {
|
||||
res[entry.Peer] = struct{}{}
|
||||
@@ -654,7 +518,3 @@ func (r *Ring) PeerNames() map[mesh.PeerName]struct{} {
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
}
|
||||
+207
-344
@@ -7,20 +7,18 @@ import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
peer1name, _ = mesh.PeerNameFromString("01:00:00:00:02:00")
|
||||
peer2name, _ = mesh.PeerNameFromString("02:00:00:00:02:00")
|
||||
peer3name, _ = mesh.PeerNameFromString("03:00:00:00:02:00")
|
||||
peer1name, _ = router.PeerNameFromString("01:00:00:00:02:00")
|
||||
peer2name, _ = router.PeerNameFromString("02:00:00:00:02:00")
|
||||
peer3name, _ = router.PeerNameFromString("03:00:00:00:02:00")
|
||||
|
||||
start, end = ParseIP("10.0.0.0"), ParseIP("10.0.1.0")
|
||||
dot8 = ParseIP("10.0.0.8")
|
||||
start, end = ParseIP("10.0.0.0"), ParseIP("10.0.0.255")
|
||||
dot10, dot245 = ParseIP("10.0.0.10"), ParseIP("10.0.0.245")
|
||||
dot250 = ParseIP("10.0.0.250")
|
||||
middle = ParseIP("10.0.0.128")
|
||||
@@ -31,89 +29,54 @@ func ParseIP(s string) address.Address {
|
||||
return addr
|
||||
}
|
||||
|
||||
func merge(r1, r2 *Ring) error {
|
||||
distance := func(start, end address.Address) address.Count {
|
||||
if end > start {
|
||||
return address.Count(end - start)
|
||||
}
|
||||
return address.Count((r1.End - start) + (end - r1.Start))
|
||||
}
|
||||
check := func(rs []address.Range) bool {
|
||||
for _, r := range rs {
|
||||
size := distance(r.Start, r.End)
|
||||
entry, found := r1.Entries.get(r.Start)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
if entry.Free < size {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
_, err := r1.Merge(*r2, check)
|
||||
return err
|
||||
}
|
||||
|
||||
func NewRing(start, end address.Address, peer mesh.PeerName) *Ring {
|
||||
return New(start, end, peer, nil)
|
||||
}
|
||||
|
||||
func TestInvariants(t *testing.T) {
|
||||
ring := NewRing(start, end, peer1name)
|
||||
ring := New(start, end, peer1name)
|
||||
|
||||
// Check ring is sorted
|
||||
ring.Entries = []*entry{{Token: dot245, Peer: peer1name}, {Token: dot10, Peer: peer2name}}
|
||||
require.True(t, ring.checkInvariants() == ErrNotSorted, "Expected error")
|
||||
wt.AssertTrue(t, ring.checkInvariants() == ErrNotSorted, "Expected error")
|
||||
|
||||
// Check tokens don't appear twice
|
||||
ring.Entries = []*entry{{Token: dot245, Peer: peer1name}, {Token: dot245, Peer: peer2name}}
|
||||
require.True(t, ring.checkInvariants() == ErrTokenRepeated, "Expected error")
|
||||
wt.AssertTrue(t, ring.checkInvariants() == ErrTokenRepeated, "Expected error")
|
||||
|
||||
// Check tokens are in bounds
|
||||
ring = NewRing(dot10, dot245, peer1name)
|
||||
ring = New(dot10, dot245, peer1name)
|
||||
ring.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
require.True(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
|
||||
wt.AssertTrue(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
|
||||
|
||||
ring.Entries = []*entry{{Token: end, Peer: peer1name}}
|
||||
require.True(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
|
||||
wt.AssertTrue(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
|
||||
}
|
||||
|
||||
func TestInsert(t *testing.T) {
|
||||
ring := NewRing(start, end, peer1name)
|
||||
ring := New(start, end, peer1name)
|
||||
ring.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
|
||||
|
||||
require.Panics(t, func() {
|
||||
wt.AssertPanic(t, func() {
|
||||
ring.Entries.insert(entry{Token: start, Peer: peer1name})
|
||||
})
|
||||
|
||||
ring.Entries.entry(0).Free = 0
|
||||
ring.Entries.insert(entry{Token: dot245, Peer: peer1name})
|
||||
check := []RangeInfo{
|
||||
{Peer: peer1name, Range: address.Range{Start: start, End: dot245}},
|
||||
{Peer: peer1name, Range: address.Range{Start: dot245, End: end}},
|
||||
}
|
||||
require.Equal(t, check, ring.AllRangeInfo())
|
||||
ring2 := New(start, end, peer1name)
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Free: 0}, {Token: dot245, Peer: peer1name}}
|
||||
wt.AssertEquals(t, ring, ring2)
|
||||
|
||||
ring.Entries.insert(entry{Token: dot10, Peer: peer1name})
|
||||
check2 := []RangeInfo{
|
||||
{Peer: peer1name, Range: address.Range{Start: start, End: dot10}},
|
||||
{Peer: peer1name, Range: address.Range{Start: dot10, End: dot245}},
|
||||
{Peer: peer1name, Range: address.Range{Start: dot245, End: end}},
|
||||
}
|
||||
require.Equal(t, check2, ring.AllRangeInfo())
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Free: 0}, {Token: dot10, Peer: peer1name}, {Token: dot245, Peer: peer1name}}
|
||||
wt.AssertEquals(t, ring, ring2)
|
||||
}
|
||||
|
||||
func TestBetween(t *testing.T) {
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
|
||||
|
||||
// First off, in a ring where everything is owned by the peer
|
||||
// between should return true for everything
|
||||
for i := 1; i <= 255; i++ {
|
||||
ip := ParseIP(fmt.Sprintf("10.0.0.%d", i))
|
||||
require.True(t, ring1.Entries.between(ip, 0, 1), "between should be true!")
|
||||
wt.AssertTrue(t, ring1.Entries.between(ip, 0, 1), "between should be true!")
|
||||
}
|
||||
|
||||
// Now, construct a ring with entries at +10 and -10
|
||||
@@ -124,205 +87,151 @@ func TestBetween(t *testing.T) {
|
||||
for i := 10; i <= 244; i++ {
|
||||
ipStr := fmt.Sprintf("10.0.0.%d", i)
|
||||
ip := ParseIP(ipStr)
|
||||
require.True(t, ring1.Entries.between(ip, 0, 1),
|
||||
wt.AssertTrue(t, ring1.Entries.between(ip, 0, 1),
|
||||
fmt.Sprintf("Between should be true for %s!", ipStr))
|
||||
require.False(t, ring1.Entries.between(ip, 1, 2),
|
||||
wt.AssertFalse(t, ring1.Entries.between(ip, 1, 2),
|
||||
fmt.Sprintf("Between should be false for %s!", ipStr))
|
||||
}
|
||||
for i := 0; i <= 9; i++ {
|
||||
ipStr := fmt.Sprintf("10.0.0.%d", i)
|
||||
ip := ParseIP(ipStr)
|
||||
require.False(t, ring1.Entries.between(ip, 0, 1),
|
||||
wt.AssertFalse(t, ring1.Entries.between(ip, 0, 1),
|
||||
fmt.Sprintf("Between should be false for %s!", ipStr))
|
||||
require.True(t, ring1.Entries.between(ip, 1, 2),
|
||||
wt.AssertTrue(t, ring1.Entries.between(ip, 1, 2),
|
||||
fmt.Sprintf("Between should be true for %s!", ipStr))
|
||||
}
|
||||
for i := 245; i <= 255; i++ {
|
||||
ipStr := fmt.Sprintf("10.0.0.%d", i)
|
||||
ip := ParseIP(ipStr)
|
||||
require.False(t, ring1.Entries.between(ip, 0, 1),
|
||||
wt.AssertFalse(t, ring1.Entries.between(ip, 0, 1),
|
||||
fmt.Sprintf("Between should be false for %s!", ipStr))
|
||||
require.True(t, ring1.Entries.between(ip, 1, 2),
|
||||
wt.AssertTrue(t, ring1.Entries.between(ip, 1, 2),
|
||||
fmt.Sprintf("Between should be true for %s!", ipStr))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGrantSimple(t *testing.T) {
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
|
||||
// Claim everything for peer1
|
||||
ring1.ClaimItAll()
|
||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 256}}, ring1.Entries)
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 255}})
|
||||
|
||||
// Now grant everything to peer2
|
||||
ring1.GrantRangeToHost(start, end, peer2name)
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}}
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}}
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
|
||||
// Now spint back to peer 1
|
||||
ring2.GrantRangeToHost(dot10, end, peer1name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 10, Version: 2},
|
||||
{Token: dot10, Peer: peer1name, Free: 246}}
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
{Token: dot10, Peer: peer1name, Free: 245}}
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
|
||||
// And spint back to peer 2 again
|
||||
ring1.GrantRangeToHost(dot245, end, peer2name)
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 2},
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 2},
|
||||
{Token: dot10, Peer: peer1name, Free: 235, Version: 1},
|
||||
{Token: dot245, Peer: peer2name, Free: 11}}, ring1.Entries)
|
||||
{Token: dot245, Peer: peer2name, Free: 10}})
|
||||
|
||||
// Grant range spanning a live token
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
||||
{Token: dot10, Peer: peer1name, Free: 235}, {Token: dot245, Peer: peer1name, Free: 10}}
|
||||
ring1.GrantRangeToHost(dot10, end, peer2name)
|
||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
||||
{Token: dot10, Peer: peer2name, Free: 235, Version: 1},
|
||||
{Token: dot245, Peer: peer2name, Free: 10, Version: 1}}, ring1.Entries)
|
||||
|
||||
{Token: dot245, Peer: peer2name, Free: 10, Version: 1}})
|
||||
}
|
||||
|
||||
func TestGrantSplit(t *testing.T) {
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
|
||||
// Claim everything for peer1
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 256}}
|
||||
merge(ring2, ring1)
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
|
||||
ring2.Merge(*ring1)
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
|
||||
// Now grant a split range to peer2
|
||||
ring1.GrantRangeToHost(dot10, dot245, peer2name)
|
||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 10, Version: 1},
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 10, Version: 1},
|
||||
{Token: dot10, Peer: peer2name, Free: 235},
|
||||
{Token: dot245, Peer: peer1name, Free: 11}}, ring1.Entries)
|
||||
|
||||
{Token: dot245, Peer: peer1name, Free: 10}})
|
||||
ring1.assertInvariants()
|
||||
|
||||
// Grant range spanning a live token, and inserting a new token
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
||||
{Token: dot10, Peer: peer1name, Free: 118}, {Token: middle, Peer: peer1name, Free: 127}}
|
||||
ring1.GrantRangeToHost(dot10, dot245, peer2name)
|
||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
||||
{Token: dot10, Peer: peer2name, Free: 118, Version: 1},
|
||||
{Token: middle, Peer: peer2name, Free: 117, Version: 1},
|
||||
{Token: dot245, Peer: peer1name, Free: 11, Version: 0}}, ring1.Entries)
|
||||
|
||||
{Token: dot245, Peer: peer1name, Free: 10, Version: 0}})
|
||||
ring1.assertInvariants()
|
||||
}
|
||||
|
||||
func TestMergeSimple(t *testing.T) {
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
|
||||
// Claim everything for peer1
|
||||
ring1.ClaimItAll()
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
|
||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 128, Version: 1},
|
||||
{Token: middle, Peer: peer2name, Free: 128}}, ring1.Entries)
|
||||
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 128, Version: 1},
|
||||
{Token: middle, Peer: peer2name, Free: 127}})
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
|
||||
// Now to two different operations on either side,
|
||||
// check we can Merge again
|
||||
ring1.GrantRangeToHost(start, middle, peer2name)
|
||||
ring2.GrantRangeToHost(middle, end, peer1name)
|
||||
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
wt.AssertSuccess(t, ring1.Merge(*ring2))
|
||||
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 128, Version: 1}}, ring1.Entries)
|
||||
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
}
|
||||
|
||||
func TestMergeWithConflicts(t *testing.T) {
|
||||
|
||||
// received update to entry with a token and version identical to an entry we have
|
||||
// but but holding different content, if there are no allocations, resolve the
|
||||
// conflict by picking entry one with high free count
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 256, Version: 1}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}}
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
|
||||
// received update to entry with a token and version identical to an entry we have
|
||||
// but but holding different content, if there are allocations, then reject the received
|
||||
// entry
|
||||
ring1 = NewRing(start, end, peer1name)
|
||||
ring2 = NewRing(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 128, Version: 1}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}}
|
||||
require.Error(t, merge(ring1, ring2), "Expected error")
|
||||
|
||||
// received an entry with update to one of our own tokens and with new version,
|
||||
// accept the received entry if its still going to belong to us
|
||||
ring1 = NewRing(start, end, peer1name)
|
||||
ring2 = NewRing(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Version: 1}}
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
|
||||
// received an entry with update to one of our own tokens and with new version,
|
||||
// but belongs to a different peer, accept the received entry if we do not
|
||||
// have allocations in the range
|
||||
ring1 = NewRing(start, end, peer1name)
|
||||
ring2 = NewRing(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 256}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}}
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
|
||||
// received an entry with update to one of our own tokens and with new version,
|
||||
// reject received entry if we have allocations in the range
|
||||
ring1 = NewRing(start, end, peer1name)
|
||||
ring2 = NewRing(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 128}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name, Version: 1}}
|
||||
require.Error(t, merge(ring1, ring2), "Expected error")
|
||||
|
||||
// we receive an entry that splits one of our ranges, giving some of it
|
||||
// away to another peer. accept the entry provided that we have no allocations
|
||||
// in the range that got given away.
|
||||
ring1 = NewRing(start, end, peer1name)
|
||||
ring2 = NewRing(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 256, Version: 1}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Free: 128, Version: 2}, {Token: middle, Peer: peer2name, Free: 128}}
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 127, Version: 1}})
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
}
|
||||
|
||||
func TestMergeErrors(t *testing.T) {
|
||||
// Cannot Merge in an invalid ring
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}, {Token: start, Peer: peer2name}}
|
||||
require.True(t, merge(ring1, ring2) == ErrNotSorted, "Expected ErrNotSorted")
|
||||
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrNotSorted, "Expected ErrNotSorted")
|
||||
|
||||
// Should Merge two rings for different ranges
|
||||
ring2 = NewRing(start, middle, peer2name)
|
||||
ring2 = New(start, middle, peer2name)
|
||||
ring2.Entries = []*entry{}
|
||||
require.True(t, merge(ring1, ring2) == ErrDifferentRange, "Expected ErrDifferentRange")
|
||||
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrDifferentSubnets, "Expected ErrDifferentSubnets")
|
||||
|
||||
// Cannot Merge newer version of entry I own
|
||||
ring2 = New(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Version: 1}}
|
||||
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrNewerVersion, "Expected ErrNewerVersion")
|
||||
|
||||
// Cannot Merge two entries with same version but different hosts
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name}}
|
||||
require.Error(t, merge(ring1, ring2), "Expected error")
|
||||
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrInvalidEntry, "Expected ErrInvalidEntry")
|
||||
|
||||
// Cannot Merge an entry into a range I own
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}}
|
||||
require.Error(t, merge(ring1, ring2), "Expected error")
|
||||
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrEntryInMyRange, "Expected ErrEntryInMyRange")
|
||||
}
|
||||
|
||||
func TestMergeMore(t *testing.T) {
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
|
||||
assertRing := func(ring *Ring, entries entries) {
|
||||
require.Equal(t, entries, ring.Entries)
|
||||
wt.AssertEquals(t, ring.Entries, entries)
|
||||
}
|
||||
|
||||
assertRing(ring1, []*entry{})
|
||||
@@ -330,97 +239,93 @@ func TestMergeMore(t *testing.T) {
|
||||
|
||||
// Claim everything for peer1
|
||||
ring1.ClaimItAll()
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
assertRing(ring2, []*entry{})
|
||||
|
||||
// Check the Merge sends it to the other ring
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
|
||||
// Give everything to peer2
|
||||
ring1.GrantRangeToHost(start, end, peer2name)
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}})
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
||||
|
||||
// And carve off some space
|
||||
ring2.GrantRangeToHost(middle, end, peer1name)
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 128}})
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}})
|
||||
{Token: middle, Peer: peer1name, Free: 127}})
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
||||
|
||||
// And Merge back
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
wt.AssertSuccess(t, ring1.Merge(*ring2))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 128}})
|
||||
{Token: middle, Peer: peer1name, Free: 127}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 128}})
|
||||
{Token: middle, Peer: peer1name, Free: 127}})
|
||||
|
||||
// This should be a no-op
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 128}})
|
||||
{Token: middle, Peer: peer1name, Free: 127}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 128}})
|
||||
{Token: middle, Peer: peer1name, Free: 127}})
|
||||
}
|
||||
|
||||
func TestMergeSplit(t *testing.T) {
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
|
||||
// Claim everything for peer2
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 256}}
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 255}}
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
|
||||
// Now grant a split range to peer1
|
||||
ring2.GrantRangeToHost(dot10, dot245, peer1name)
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
wt.AssertEquals(t, ring2.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
{Token: dot10, Peer: peer1name, Free: 235},
|
||||
{Token: dot245, Peer: peer2name, Free: 11}}, ring2.Entries)
|
||||
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
{Token: dot245, Peer: peer2name, Free: 10}})
|
||||
wt.AssertSuccess(t, ring1.Merge(*ring2))
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
{Token: dot10, Peer: peer1name, Free: 235},
|
||||
{Token: dot245, Peer: peer2name, Free: 11}}, ring1.Entries)
|
||||
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
{Token: dot245, Peer: peer2name, Free: 10}})
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
}
|
||||
|
||||
func TestMergeSplit2(t *testing.T) {
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
|
||||
// Claim everything for peer2
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 250}, {Token: dot250, Peer: peer2name, Free: 5}}
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
|
||||
// Now grant a split range to peer1
|
||||
ring2.GrantRangeToHost(dot10, dot245, peer1name)
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
wt.AssertEquals(t, ring2.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
{Token: dot10, Peer: peer1name, Free: 235},
|
||||
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}}, ring2.Entries)
|
||||
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}})
|
||||
wt.AssertSuccess(t, ring1.Merge(*ring2))
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
{Token: dot10, Peer: peer1name, Free: 235},
|
||||
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}}, ring1.Entries)
|
||||
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}})
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
}
|
||||
|
||||
// A simple test, very similar to above, but using the marshalling to byte[]s
|
||||
func TestGossip(t *testing.T) {
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
|
||||
assertRing := func(ring *Ring, entries entries) {
|
||||
require.Equal(t, entries, ring.Entries)
|
||||
wt.AssertEquals(t, ring.Entries, entries)
|
||||
}
|
||||
|
||||
assertRing(ring1, []*entry{})
|
||||
@@ -428,66 +333,57 @@ func TestGossip(t *testing.T) {
|
||||
|
||||
// Claim everything for peer1
|
||||
ring1.ClaimItAll()
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
assertRing(ring2, []*entry{})
|
||||
|
||||
// Check the Merge sends it to the other ring
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||
}
|
||||
|
||||
func assertPeersWithSpace(t *testing.T, ring *Ring, start, end address.Address, expected int) []mesh.PeerName {
|
||||
peers := ring.ChoosePeersToAskForSpace(start, end)
|
||||
require.Equal(t, expected, len(peers))
|
||||
return peers
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
}
|
||||
|
||||
func TestFindFree(t *testing.T) {
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
|
||||
assertPeersWithSpace(t, ring1, start, end, 0)
|
||||
_, err := ring1.ChoosePeerToAskForSpace()
|
||||
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
|
||||
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
assertPeersWithSpace(t, ring1, start, end, 0)
|
||||
_, err = ring1.ChoosePeerToAskForSpace()
|
||||
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
|
||||
|
||||
// We shouldn't return outselves
|
||||
ring1.ReportFree(map[address.Address]address.Count{start: 10})
|
||||
assertPeersWithSpace(t, ring1, start, end, 0)
|
||||
ring1.ReportFree(map[address.Address]address.Offset{start: 10})
|
||||
_, err = ring1.ChoosePeerToAskForSpace()
|
||||
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
|
||||
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 1},
|
||||
{Token: middle, Peer: peer1name, Free: 1}}
|
||||
assertPeersWithSpace(t, ring1, start, end, 0)
|
||||
ring1.assertInvariants()
|
||||
{Token: start, Peer: peer1name, Free: 1}}
|
||||
_, err = ring1.ChoosePeerToAskForSpace()
|
||||
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
|
||||
|
||||
// We should return others
|
||||
var peers []mesh.PeerName
|
||||
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1}}
|
||||
peers = assertPeersWithSpace(t, ring1, start, end, 1)
|
||||
require.Equal(t, peer2name, peers[0])
|
||||
peer, err := ring1.ChoosePeerToAskForSpace()
|
||||
wt.AssertSuccess(t, err)
|
||||
wt.AssertEquals(t, peer, peer2name)
|
||||
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1},
|
||||
{Token: middle, Peer: peer3name, Free: 1}}
|
||||
peers = assertPeersWithSpace(t, ring1, start, middle, 1)
|
||||
require.Equal(t, peer2name, peers[0])
|
||||
|
||||
peers = assertPeersWithSpace(t, ring1, middle, end, 1)
|
||||
require.Equal(t, peer3name, peers[0])
|
||||
|
||||
assertPeersWithSpace(t, ring1, start, end, 2)
|
||||
ring1.assertInvariants()
|
||||
{Token: start, Peer: peer2name, Free: 1}}
|
||||
peer, err = ring1.ChoosePeerToAskForSpace()
|
||||
wt.AssertSuccess(t, err)
|
||||
wt.AssertEquals(t, peer, peer2name)
|
||||
}
|
||||
|
||||
func TestReportFree(t *testing.T) {
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
|
||||
ring1.ClaimItAll()
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
|
||||
freespace := make(map[address.Address]address.Count)
|
||||
freespace := make(map[address.Address]address.Offset)
|
||||
for _, r := range ring2.OwnedRanges() {
|
||||
freespace[r.Start] = 0
|
||||
}
|
||||
@@ -495,130 +391,100 @@ func TestReportFree(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMisc(t *testing.T) {
|
||||
ring := NewRing(start, end, peer1name)
|
||||
ring := New(start, end, peer1name)
|
||||
|
||||
require.True(t, ring.Empty(), "empty")
|
||||
wt.AssertTrue(t, ring.Empty(), "empty")
|
||||
|
||||
ring.ClaimItAll()
|
||||
println(ring.String())
|
||||
}
|
||||
|
||||
func TestEmptyGossip(t *testing.T) {
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
|
||||
ring1.ClaimItAll()
|
||||
// This used to panic, and it shouldn't
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
wt.AssertSuccess(t, ring1.Merge(*ring2))
|
||||
}
|
||||
|
||||
func TestMergeOldMessage(t *testing.T) {
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
|
||||
ring1.ClaimItAll()
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
|
||||
ring1.GrantRangeToHost(middle, end, peer1name)
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
wt.AssertSuccess(t, ring1.Merge(*ring2))
|
||||
}
|
||||
|
||||
func TestSplitRangeAtBeginning(t *testing.T) {
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
|
||||
ring1.ClaimItAll()
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
|
||||
ring1.GrantRangeToHost(start, middle, peer2name)
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
}
|
||||
|
||||
func TestOwnedRange(t *testing.T) {
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring1.ClaimItAll()
|
||||
|
||||
require.Equal(t, []address.Range{{Start: start, End: end}}, ring1.OwnedRanges())
|
||||
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{Start: start, End: end}})
|
||||
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
require.Equal(t, []address.Range{{Start: start, End: middle}}, ring1.OwnedRanges())
|
||||
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{Start: start, End: middle}})
|
||||
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
merge(ring2, ring1)
|
||||
require.Equal(t, []address.Range{{Start: middle, End: end}}, ring2.OwnedRanges())
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring2.Merge(*ring1)
|
||||
wt.AssertEquals(t, ring2.OwnedRanges(), []address.Range{{Start: middle, End: end}})
|
||||
|
||||
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}}
|
||||
require.Equal(t, []address.Range{{Start: start, End: middle}, {Start: middle, End: end}}, ring2.OwnedRanges())
|
||||
wt.AssertEquals(t, ring2.OwnedRanges(),
|
||||
[]address.Range{{Start: start, End: middle}, {Start: middle, End: end}})
|
||||
|
||||
ring2.Entries = []*entry{{Token: dot10, Peer: peer2name}, {Token: middle, Peer: peer2name}}
|
||||
require.Equal(t, []address.Range{{Start: start, End: dot10}, {Start: dot10, End: middle},
|
||||
{Start: middle, End: end}}, ring2.OwnedRanges())
|
||||
|
||||
wt.AssertEquals(t, ring2.OwnedRanges(),
|
||||
[]address.Range{{Start: start, End: dot10}, {Start: dot10, End: middle},
|
||||
{Start: middle, End: end}})
|
||||
}
|
||||
|
||||
func TestTransfer(t *testing.T) {
|
||||
// First test just checks if we can grant some range to a host, when we transfer it, we get it back
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring1.ClaimItAll()
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
ring1.Transfer(peer2name, peer1name)
|
||||
require.Equal(t, []address.Range{{Start: start, End: middle}, {Start: middle, End: end}}, ring1.OwnedRanges())
|
||||
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{start, middle}, {middle, end}})
|
||||
|
||||
// Second test is what happens when a token exists at the end of a range but is transferred
|
||||
// - does it get resurrected correctly?
|
||||
ring1 = NewRing(start, end, peer1name)
|
||||
ring1 = New(start, end, peer1name)
|
||||
ring1.ClaimItAll()
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
ring1.Transfer(peer2name, peer1name)
|
||||
ring1.GrantRangeToHost(dot10, middle, peer2name)
|
||||
require.Equal(t, []address.Range{{Start: start, End: dot10}, {Start: middle, End: end}}, ring1.OwnedRanges())
|
||||
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{start, dot10}, {middle, end}})
|
||||
}
|
||||
|
||||
func TestOwner(t *testing.T) {
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
require.True(t, ring1.Contains(start), "start should be in ring")
|
||||
require.False(t, ring1.Contains(end), "end should not be in ring")
|
||||
ring1 := New(start, end, peer1name)
|
||||
wt.AssertTrue(t, ring1.Contains(start), "start should be in ring")
|
||||
wt.AssertFalse(t, ring1.Contains(end), "end should not be in ring")
|
||||
|
||||
require.Equal(t, mesh.UnknownPeerName, ring1.Owner(start))
|
||||
wt.AssertEquals(t, ring1.Owner(start), router.UnknownPeerName)
|
||||
|
||||
ring1.ClaimItAll()
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
require.Equal(t, peer1name, ring1.Owner(start))
|
||||
require.Equal(t, peer2name, ring1.Owner(middle))
|
||||
require.Panics(t, func() {
|
||||
wt.AssertEquals(t, ring1.Owner(start), peer1name)
|
||||
wt.AssertEquals(t, ring1.Owner(middle), peer2name)
|
||||
wt.AssertPanic(t, func() {
|
||||
ring1.Owner(end)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func makePeerName(i int) mesh.PeerName {
|
||||
if i >= 10000 {
|
||||
panic("makePeerName: invalid value")
|
||||
}
|
||||
peer, _ := mesh.PeerNameFromString(fmt.Sprintf("%02d:%02d:00:00:00:ff", i/100, i%100))
|
||||
return peer
|
||||
}
|
||||
|
||||
func makePeers(numPeers int) []mesh.PeerName {
|
||||
peers := make([]mesh.PeerName, numPeers)
|
||||
for i := 0; i < numPeers; i++ {
|
||||
peers[i] = makePeerName(i)
|
||||
}
|
||||
return peers
|
||||
}
|
||||
|
||||
func TestClaimForPeers(t *testing.T) {
|
||||
const numPeers = 12
|
||||
// Different end to usual so we get a number of addresses that a)
|
||||
// is smaller than the max number of peers, and b) is divisible by
|
||||
// some number of peers. This maximises coverage of edge cases.
|
||||
end := dot8
|
||||
peers := makePeers(numPeers)
|
||||
// Test for a range of peer counts
|
||||
for i := 0; i < numPeers; i++ {
|
||||
ring := NewRing(start, end, peers[0])
|
||||
ring.ClaimForPeers(peers[:i+1])
|
||||
}
|
||||
}
|
||||
|
||||
type addressSlice []address.Address
|
||||
@@ -633,9 +499,10 @@ func TestFuzzRing(t *testing.T) {
|
||||
iterations = 1000
|
||||
)
|
||||
|
||||
peers := make([]mesh.PeerName, numPeers)
|
||||
peers := make([]router.PeerName, numPeers)
|
||||
for i := 0; i < numPeers; i++ {
|
||||
peers[i] = makePeerName(i)
|
||||
peer, _ := router.PeerNameFromString(fmt.Sprintf("%02d:00:00:00:02:00", i))
|
||||
peers[i] = peer
|
||||
}
|
||||
|
||||
// Make a valid, random ring
|
||||
@@ -654,7 +521,7 @@ func TestFuzzRing(t *testing.T) {
|
||||
sort.Sort(addressSlice(tokens))
|
||||
|
||||
peer := peers[rand.Intn(len(peers))]
|
||||
ring := NewRing(start, end, peer)
|
||||
ring := New(start, end, peer)
|
||||
for _, token := range tokens {
|
||||
peer = peers[rand.Intn(len(peers))]
|
||||
ring.Entries = append(ring.Entries, &entry{Token: start + token, Peer: peer})
|
||||
@@ -671,7 +538,7 @@ func TestFuzzRing(t *testing.T) {
|
||||
|
||||
// Merge them - this might fail, we don't care
|
||||
// We just want to make sure it doesn't panic
|
||||
merge(ring1, ring2)
|
||||
ring1.Merge(*ring2)
|
||||
|
||||
// Check whats left still passes assertions
|
||||
ring1.assertInvariants()
|
||||
@@ -688,7 +555,7 @@ func TestFuzzRing(t *testing.T) {
|
||||
}
|
||||
|
||||
peer := peers[rand.Intn(len(peers))]
|
||||
ring := NewRing(start, end, peer)
|
||||
ring := New(start, end, peer)
|
||||
for _, token := range tokens {
|
||||
peer = peers[rand.Intn(len(peers))]
|
||||
ring.Entries = append(ring.Entries, &entry{Token: start + token, Peer: peer})
|
||||
@@ -704,7 +571,7 @@ func TestFuzzRing(t *testing.T) {
|
||||
|
||||
// Merge them - this might fail, we don't care
|
||||
// We just want to make sure it doesn't panic
|
||||
merge(ring1, ring2)
|
||||
ring1.Merge(*ring2)
|
||||
|
||||
// Check whats left still passes assertions
|
||||
ring1.assertInvariants()
|
||||
@@ -712,21 +579,21 @@ func TestFuzzRing(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFuzzRingHard(t *testing.T) {
|
||||
//common.SetLogLevel("debug")
|
||||
//common.InitDefaultLogging(true)
|
||||
var (
|
||||
numPeers = 100
|
||||
iterations = 3000
|
||||
peers []mesh.PeerName
|
||||
peers []router.PeerName
|
||||
rings []*Ring
|
||||
nextPeerID = 0
|
||||
)
|
||||
|
||||
addPeer := func() {
|
||||
peer := makePeerName(nextPeerID)
|
||||
common.Log.Debugf("%s: Adding peer", peer)
|
||||
peer, _ := router.PeerNameFromString(fmt.Sprintf("%02d:%02d:00:00:00:00", nextPeerID/10, nextPeerID%10))
|
||||
common.Debug.Printf("%s: Adding peer", peer)
|
||||
nextPeerID++
|
||||
peers = append(peers, peer)
|
||||
rings = append(rings, NewRing(start, end, peer))
|
||||
rings = append(rings, New(start, end, peer))
|
||||
}
|
||||
|
||||
for i := 0; i < numPeers; i++ {
|
||||
@@ -735,7 +602,7 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
|
||||
rings[0].ClaimItAll()
|
||||
|
||||
randomPeer := func(exclude int) (int, mesh.PeerName, *Ring) {
|
||||
randomPeer := func(exclude int) (int, router.PeerName, *Ring) {
|
||||
var peerIndex int
|
||||
if exclude >= 0 {
|
||||
peerIndex = rand.Intn(len(peers) - 1)
|
||||
@@ -759,27 +626,27 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
// Pick one peer to remove, and a different one to transfer to
|
||||
peerIndex, peername, _ := randomPeer(-1)
|
||||
_, otherPeername, otherRing := randomPeer(peerIndex)
|
||||
|
||||
// We need to be in a ~converged ring to rmpeer
|
||||
for _, ring := range rings {
|
||||
require.NoError(t, merge(otherRing, ring))
|
||||
}
|
||||
|
||||
common.Log.Debugf("%s: transferring from peer %s", otherPeername, peername)
|
||||
otherRing.Transfer(peername, otherPeername)
|
||||
|
||||
// Remove peer from our state
|
||||
peers = append(peers[:peerIndex], peers[peerIndex+1:]...)
|
||||
rings = append(rings[:peerIndex], rings[peerIndex+1:]...)
|
||||
theRanges = make(map[int][]address.Range)
|
||||
|
||||
// Transfer the space for this peer on another peer, but not this one
|
||||
_, otherPeername, otherRing := randomPeer(peerIndex)
|
||||
|
||||
// We need to be in a ~converged ring to rmpeer
|
||||
for _, ring := range rings {
|
||||
wt.AssertSuccess(t, otherRing.Merge(*ring))
|
||||
}
|
||||
|
||||
common.Debug.Printf("%s: transferring from peer %s", otherPeername, peername)
|
||||
otherRing.Transfer(peername, peername)
|
||||
|
||||
// And now tell everyone about the transfer - rmpeer is
|
||||
// not partition safe
|
||||
for i, ring := range rings {
|
||||
require.NoError(t, merge(ring, otherRing))
|
||||
wt.AssertSuccess(t, ring.Merge(*otherRing))
|
||||
theRanges[i] = ring.OwnedRanges()
|
||||
}
|
||||
}
|
||||
@@ -802,13 +669,13 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
size := address.Subtract(rangeToSplit.End, rangeToSplit.Start)
|
||||
ipInRange := address.Add(rangeToSplit.Start, address.Offset(rand.Intn(int(size))))
|
||||
_, peerToGiveTo, _ := randomPeer(-1)
|
||||
common.Log.Debugf("%s: Granting [%v, %v) to %s", ring.Peer, ipInRange, rangeToSplit.End, peerToGiveTo)
|
||||
common.Debug.Printf("%s: Granting [%v, %v) to %s", ring.Peer, ipInRange, rangeToSplit.End, peerToGiveTo)
|
||||
ring.GrantRangeToHost(ipInRange, rangeToSplit.End, peerToGiveTo)
|
||||
|
||||
// Now 'gossip' this to a random host (note, note could be same host as above)
|
||||
otherIndex, _, otherRing := randomPeer(-1)
|
||||
common.Log.Debugf("%s: 'Gossiping' to %s", ring.Peer, otherRing.Peer)
|
||||
require.NoError(t, merge(otherRing, ring))
|
||||
common.Debug.Printf("%s: 'Gossiping' to %s", ring.Peer, otherRing.Peer)
|
||||
wt.AssertSuccess(t, otherRing.Merge(*ring))
|
||||
|
||||
theRanges[indexWithRanges] = ring.OwnedRanges()
|
||||
theRanges[otherIndex] = otherRing.OwnedRanges()
|
||||
@@ -826,8 +693,8 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
}
|
||||
ring1 := ringsWithEntries[rand.Intn(len(ringsWithEntries))]
|
||||
ring2index, _, ring2 := randomPeer(-1)
|
||||
common.Log.Debugf("%s: 'Gossiping' to %s", ring1.Peer, ring2.Peer)
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
common.Debug.Printf("%s: 'Gossiping' to %s", ring1.Peer, ring2.Peer)
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
theRanges[ring2index] = ring2.OwnedRanges()
|
||||
}
|
||||
|
||||
@@ -843,10 +710,6 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Ring) ClaimItAll() {
|
||||
r.ClaimForPeers([]mesh.PeerName{r.Peer})
|
||||
}
|
||||
|
||||
func (es entries) String() string {
|
||||
var buffer bytes.Buffer
|
||||
fmt.Fprintf(&buffer, "[")
|
||||
|
||||
+61
-93
@@ -6,9 +6,11 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
)
|
||||
|
||||
type Addr address.Address
|
||||
|
||||
type Space struct {
|
||||
// ours and free represent a set of addresses as a sorted
|
||||
// sequences of ranges. Even elements give the inclusive
|
||||
@@ -31,51 +33,19 @@ func (s *Space) Add(start address.Address, size address.Offset) {
|
||||
|
||||
// Clear removes all spaces from this space set. Used during node shutdown.
|
||||
func (s *Space) Clear() {
|
||||
s.ours = s.ours[:0]
|
||||
s.free = s.free[:0]
|
||||
s.ours = s.ours[:0]
|
||||
}
|
||||
|
||||
// Walk down the free list calling f() on the in-range portions, until
|
||||
// f() returns true or we run out of free space. Return true iff f() returned true
|
||||
func (s *Space) walkFree(r address.Range, f func(address.Range) bool) bool {
|
||||
if r.Start >= r.End { // degenerate case
|
||||
return false
|
||||
func (s *Space) Allocate() (bool, address.Address) {
|
||||
if len(s.free) == 0 {
|
||||
return false, 0
|
||||
} else {
|
||||
res := s.free[0]
|
||||
s.ours = add(s.ours, res, res+1)
|
||||
s.free = subtract(s.free, res, res+1)
|
||||
return true, res
|
||||
}
|
||||
for i := 0; i < len(s.free); i += 2 {
|
||||
chunk := address.Range{Start: s.free[i], End: s.free[i+1]}
|
||||
if chunk.End <= r.Start { // this chunk comes before the range
|
||||
continue
|
||||
}
|
||||
if chunk.Start >= r.End {
|
||||
// all remaining free space is completely after range
|
||||
break
|
||||
}
|
||||
// at this point we know chunk.End>chunk.Start &&
|
||||
// chunk.End>r.Start && r.End>chunk.Start && r.End>r.Start
|
||||
// therefore max(start, r.Start) < min(end, r.End)
|
||||
// Restrict this block of free space to be in range
|
||||
if chunk.Start < r.Start {
|
||||
chunk.Start = r.Start
|
||||
}
|
||||
if chunk.End > r.End {
|
||||
chunk.End = r.End
|
||||
}
|
||||
// at this point we know start<end
|
||||
if f(chunk) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Space) Allocate(r address.Range) (bool, address.Address) {
|
||||
var result address.Address
|
||||
return s.walkFree(r, func(chunk address.Range) bool {
|
||||
result = chunk.Start
|
||||
s.ours = add(s.ours, result, result+1)
|
||||
s.free = subtract(s.free, result, result+1)
|
||||
return true
|
||||
}), result
|
||||
}
|
||||
|
||||
func (s *Space) Claim(addr address.Address) error {
|
||||
@@ -88,28 +58,29 @@ func (s *Space) Claim(addr address.Address) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Space) NumOwnedAddresses() address.Count {
|
||||
res := address.Count(0)
|
||||
for i := 0; i < len(s.ours); i += 2 {
|
||||
res += address.Length(s.ours[i+1], s.ours[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *Space) NumFreeAddresses() address.Count {
|
||||
res := address.Count(0)
|
||||
func (s *Space) NumFreeAddresses() address.Offset {
|
||||
res := address.Offset(0)
|
||||
for i := 0; i < len(s.free); i += 2 {
|
||||
res += address.Length(s.free[i+1], s.free[i])
|
||||
res += address.Subtract(s.free[i+1], s.free[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *Space) NumFreeAddressesInRange(r address.Range) address.Count {
|
||||
res := address.Count(0)
|
||||
s.walkFree(r, func(chunk address.Range) bool {
|
||||
res += chunk.Size()
|
||||
return false
|
||||
})
|
||||
func (s *Space) NumFreeAddressesInRange(start, end address.Address) address.Offset {
|
||||
res := address.Offset(0)
|
||||
for i := 0; i < len(s.free); i += 2 {
|
||||
s, e := s.free[i], s.free[i+1]
|
||||
if s < start {
|
||||
s = start
|
||||
}
|
||||
if e > end {
|
||||
e = end
|
||||
}
|
||||
if s >= e {
|
||||
continue
|
||||
}
|
||||
res += address.Subtract(e, s)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -126,38 +97,35 @@ func (s *Space) Free(addr address.Address) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Space) biggestFreeRange(r address.Range) (biggest address.Range) {
|
||||
biggestSize := address.Count(0)
|
||||
s.walkFree(r, func(chunk address.Range) bool {
|
||||
if size := chunk.Size(); size >= biggestSize {
|
||||
chunk = chunk.BiggestCIDRRange()
|
||||
if size = chunk.Size(); size >= biggestSize {
|
||||
biggest = chunk
|
||||
biggestSize = size
|
||||
}
|
||||
func (s *Space) biggestFreeRange() (int, address.Offset) {
|
||||
pos := -1
|
||||
biggest := address.Offset(0)
|
||||
|
||||
for i := 0; i < len(s.free); i += 2 {
|
||||
size := address.Subtract(s.free[i+1], s.free[i])
|
||||
if size >= biggest {
|
||||
pos = i
|
||||
biggest = size
|
||||
}
|
||||
return false
|
||||
})
|
||||
return
|
||||
}
|
||||
return pos, biggest
|
||||
}
|
||||
|
||||
func (s *Space) Donate(r address.Range) (address.Range, bool) {
|
||||
biggest := s.biggestFreeRange(r)
|
||||
|
||||
if biggest.Size() == 0 {
|
||||
return address.Range{}, false
|
||||
func (s *Space) Donate() (address.Address, address.Offset, bool) {
|
||||
if len(s.free) == 0 {
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
// Donate no more than half what we have available
|
||||
if biggest.Size() > s.NumFreeAddressesInRange(r)/2 {
|
||||
// Note size/2 rounds down, so the resulting donation size
|
||||
// rounds up, and in particular can't be empty.
|
||||
biggest.Start = address.Add(biggest.Start, address.Offset(biggest.Size()/2))
|
||||
}
|
||||
pos, biggest := s.biggestFreeRange()
|
||||
|
||||
s.ours = subtract(s.ours, biggest.Start, biggest.End)
|
||||
s.free = subtract(s.free, biggest.Start, biggest.End)
|
||||
return biggest, true
|
||||
// Donate half of that biggest free range, rounding up so
|
||||
// that the donation can't be empty
|
||||
end := s.free[pos+1]
|
||||
start := end - address.Address((biggest+1)/2)
|
||||
|
||||
s.ours = subtract(s.ours, start, end)
|
||||
s.free = subtract(s.free, start, end)
|
||||
return start, address.Subtract(end, start), true
|
||||
}
|
||||
|
||||
func firstGreater(a []address.Address, x address.Address) int {
|
||||
@@ -184,25 +152,25 @@ func subtract(addrs []address.Address, start address.Address, end address.Addres
|
||||
}
|
||||
|
||||
func addSub(addrs []address.Address, start address.Address, end address.Address, sense int) []address.Address {
|
||||
startPos := firstGreaterOrEq(addrs, start)
|
||||
endPos := firstGreater(addrs[startPos:], end) + startPos
|
||||
start_pos := firstGreaterOrEq(addrs, start)
|
||||
end_pos := firstGreater(addrs[start_pos:], end) + start_pos
|
||||
|
||||
// Boundaries up to startPos are unaffected
|
||||
res := make([]address.Address, startPos, len(addrs)+2)
|
||||
// Boundaries up to start_pos are unaffected
|
||||
res := make([]address.Address, start_pos, len(addrs)+2)
|
||||
copy(res, addrs)
|
||||
|
||||
// Include start and end as new boundaries if they lie
|
||||
// outside/inside existing ranges (according to sense).
|
||||
if startPos&1 == sense {
|
||||
if start_pos&1 == sense {
|
||||
res = append(res, start)
|
||||
}
|
||||
|
||||
if endPos&1 == sense {
|
||||
if end_pos&1 == sense {
|
||||
res = append(res, end)
|
||||
}
|
||||
|
||||
// Boundaries after endPos are unaffected
|
||||
return append(res, addrs[endPos:]...)
|
||||
// Boundaries after end_pos are unaffected
|
||||
return append(res, addrs[end_pos:]...)
|
||||
}
|
||||
|
||||
func (s *Space) String() string {
|
||||
|
||||
+113
-123
@@ -1,10 +1,10 @@
|
||||
package space
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
@@ -22,204 +22,194 @@ func ip(s string) address.Address {
|
||||
func TestLowlevel(t *testing.T) {
|
||||
a := []address.Address{}
|
||||
a = add(a, 100, 200)
|
||||
require.Equal(t, []address.Address{100, 200}, a)
|
||||
require.True(t, !contains(a, 99), "")
|
||||
require.True(t, contains(a, 100), "")
|
||||
require.True(t, contains(a, 199), "")
|
||||
require.True(t, !contains(a, 200), "")
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200})
|
||||
wt.AssertTrue(t, !contains(a, 99), "")
|
||||
wt.AssertTrue(t, contains(a, 100), "")
|
||||
wt.AssertTrue(t, contains(a, 199), "")
|
||||
wt.AssertTrue(t, !contains(a, 200), "")
|
||||
a = add(a, 700, 800)
|
||||
require.Equal(t, []address.Address{100, 200, 700, 800}, a)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 700, 800})
|
||||
a = add(a, 300, 400)
|
||||
require.Equal(t, []address.Address{100, 200, 300, 400, 700, 800}, a)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 400, 700, 800})
|
||||
a = add(a, 400, 500)
|
||||
require.Equal(t, []address.Address{100, 200, 300, 500, 700, 800}, a)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 700, 800})
|
||||
a = add(a, 600, 700)
|
||||
require.Equal(t, []address.Address{100, 200, 300, 500, 600, 800}, a)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 600, 800})
|
||||
a = add(a, 500, 600)
|
||||
require.Equal(t, []address.Address{100, 200, 300, 800}, a)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 800})
|
||||
a = subtract(a, 500, 600)
|
||||
require.Equal(t, []address.Address{100, 200, 300, 500, 600, 800}, a)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 600, 800})
|
||||
a = subtract(a, 600, 700)
|
||||
require.Equal(t, []address.Address{100, 200, 300, 500, 700, 800}, a)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 700, 800})
|
||||
a = subtract(a, 400, 500)
|
||||
require.Equal(t, []address.Address{100, 200, 300, 400, 700, 800}, a)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 400, 700, 800})
|
||||
a = subtract(a, 300, 400)
|
||||
require.Equal(t, []address.Address{100, 200, 700, 800}, a)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 700, 800})
|
||||
a = subtract(a, 700, 800)
|
||||
require.Equal(t, []address.Address{100, 200}, a)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200})
|
||||
a = subtract(a, 100, 200)
|
||||
require.Equal(t, []address.Address{}, a)
|
||||
wt.AssertEquals(t, a, []address.Address{})
|
||||
|
||||
s := New()
|
||||
require.Equal(t, address.Count(0), s.NumFreeAddresses())
|
||||
ok, _ := s.Allocate(address.NewRange(0, 1000))
|
||||
require.False(t, ok, "allocate in empty space should fail")
|
||||
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(0))
|
||||
ok, got := s.Allocate()
|
||||
wt.AssertFalse(t, ok, "allocate in empty space should fail")
|
||||
|
||||
s.Add(100, 100)
|
||||
require.Equal(t, address.Count(100), s.NumFreeAddresses())
|
||||
ok, got := s.Allocate(address.NewRange(0, 1000))
|
||||
require.True(t, ok && got == 100, "allocate")
|
||||
require.Equal(t, address.Count(99), s.NumFreeAddresses())
|
||||
require.NoError(t, s.Claim(150))
|
||||
require.Equal(t, address.Count(98), s.NumFreeAddresses())
|
||||
require.NoError(t, s.Free(100))
|
||||
require.Equal(t, address.Count(99), s.NumFreeAddresses())
|
||||
wt.AssertErrorInterface(t, (*error)(nil), s.Free(0), "free not allocated")
|
||||
wt.AssertErrorInterface(t, (*error)(nil), s.Free(100), "double free")
|
||||
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(100))
|
||||
ok, got = s.Allocate()
|
||||
wt.AssertTrue(t, ok && got == 100, "allocate")
|
||||
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(99))
|
||||
wt.AssertNoErr(t, s.Claim(150))
|
||||
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(98))
|
||||
wt.AssertNoErr(t, s.Free(100))
|
||||
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(99))
|
||||
wt.AssertErrorInterface(t, s.Free(0), (*error)(nil), "free not allocated")
|
||||
wt.AssertErrorInterface(t, s.Free(100), (*error)(nil), "double free")
|
||||
|
||||
r, ok := s.Donate(address.NewRange(0, 1000))
|
||||
require.True(t, ok, "donate")
|
||||
require.Equal(t, address.NewRange(0xa0, 0x20), r, "donate")
|
||||
start, size, ok := s.Donate()
|
||||
wt.AssertTrue(t, ok && start == 125 && size == 25, "donate")
|
||||
|
||||
// test Donate when addresses are scarce
|
||||
s = New()
|
||||
r, ok = s.Donate(address.NewRange(0, 1000))
|
||||
require.True(t, !ok, "donate on empty space should fail")
|
||||
start, size, ok = s.Donate()
|
||||
wt.AssertTrue(t, !ok, "donate on empty space should fail")
|
||||
s.Add(0, 3)
|
||||
require.NoError(t, s.Claim(0))
|
||||
require.NoError(t, s.Claim(2))
|
||||
r, ok = s.Donate(address.NewRange(0, 1000))
|
||||
require.True(t, ok, "donate")
|
||||
require.Equal(t, address.NewRange(1, 1), r, "donate")
|
||||
r, ok = s.Donate(address.NewRange(0, 1000))
|
||||
require.True(t, !ok, "donate should fail")
|
||||
wt.AssertNoErr(t, s.Claim(0))
|
||||
wt.AssertNoErr(t, s.Claim(2))
|
||||
start, size, ok = s.Donate()
|
||||
wt.AssertTrue(t, ok && start == 1 && size == 1, "donate")
|
||||
start, size, ok = s.Donate()
|
||||
wt.AssertTrue(t, !ok, "donate should fail")
|
||||
}
|
||||
|
||||
func TestSpaceAllocate(t *testing.T) {
|
||||
const (
|
||||
testAddr1 = "10.0.3.4"
|
||||
testAddr2 = "10.0.3.5"
|
||||
testAddrx = "10.0.3.19"
|
||||
testAddry = "10.0.9.19"
|
||||
containerID = "deadbeef"
|
||||
size = 20
|
||||
)
|
||||
var (
|
||||
start = ip(testAddr1)
|
||||
)
|
||||
|
||||
space1 := makeSpace(start, size)
|
||||
require.Equal(t, address.Count(20), space1.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(0), space1.NumOwnedAddresses())
|
||||
space1 := makeSpace(ip(testAddr1), 20)
|
||||
wt.AssertEquals(t, space1.NumFreeAddresses(), address.Offset(20))
|
||||
space1.assertInvariants()
|
||||
|
||||
_, addr1 := space1.Allocate(address.NewRange(start, size))
|
||||
require.Equal(t, testAddr1, addr1.String(), "address")
|
||||
require.Equal(t, address.Count(19), space1.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(1), space1.NumOwnedAddresses())
|
||||
_, addr1 := space1.Allocate()
|
||||
wt.AssertEqualString(t, addr1.String(), testAddr1, "address")
|
||||
wt.AssertEquals(t, space1.NumFreeAddresses(), address.Offset(19))
|
||||
space1.assertInvariants()
|
||||
|
||||
_, addr2 := space1.Allocate(address.NewRange(start, size))
|
||||
require.False(t, addr2.String() == testAddr1, "address")
|
||||
require.Equal(t, address.Count(18), space1.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(2), space1.NumOwnedAddresses())
|
||||
require.Equal(t, address.Count(13), space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddrx)}))
|
||||
require.Equal(t, address.Count(18), space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddry)}))
|
||||
_, addr2 := space1.Allocate()
|
||||
wt.AssertFalse(t, addr2.String() == testAddr1, "address")
|
||||
wt.AssertEquals(t, space1.NumFreeAddresses(), address.Offset(18))
|
||||
wt.AssertEquals(t, space1.NumFreeAddressesInRange(ip(testAddr1), ip(testAddrx)), address.Offset(13))
|
||||
wt.AssertEquals(t, space1.NumFreeAddressesInRange(ip(testAddr1), ip(testAddry)), address.Offset(18))
|
||||
space1.assertInvariants()
|
||||
|
||||
space1.Free(addr2)
|
||||
space1.assertInvariants()
|
||||
|
||||
wt.AssertErrorInterface(t, (*error)(nil), space1.Free(addr2), "double free")
|
||||
wt.AssertErrorInterface(t, (*error)(nil), space1.Free(ip(testAddrx)), "address not allocated")
|
||||
wt.AssertErrorInterface(t, (*error)(nil), space1.Free(ip(testAddry)), "wrong out of range")
|
||||
wt.AssertErrorInterface(t, space1.Free(addr2), (*error)(nil), "double free")
|
||||
wt.AssertErrorInterface(t, space1.Free(ip(testAddrx)), (*error)(nil), "address not allocated")
|
||||
wt.AssertErrorInterface(t, space1.Free(ip(testAddry)), (*error)(nil), "wrong out of range")
|
||||
|
||||
space1.assertInvariants()
|
||||
}
|
||||
|
||||
func TestSpaceFree(t *testing.T) {
|
||||
const (
|
||||
testAddr1 = "10.0.3.16"
|
||||
testAddr1 = "10.0.3.4"
|
||||
testAddrx = "10.0.3.19"
|
||||
testAddry = "10.0.9.19"
|
||||
containerID = "deadbeef"
|
||||
size = 16
|
||||
)
|
||||
|
||||
entireRange := address.NewRange(ip(testAddr1), size)
|
||||
space := makeSpace(ip(testAddr1), size)
|
||||
space := makeSpace(ip(testAddr1), 20)
|
||||
|
||||
// Check we are prepared to give up the entire space
|
||||
r := space.biggestFreeRange(entireRange)
|
||||
require.Equal(t, address.NewRange(ip(testAddr1), size), r, "Wrong space")
|
||||
pos, size := space.biggestFreeRange()
|
||||
wt.AssertTrue(t, space.free[pos] == ip(testAddr1) && size == 20, "Wrong space")
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
ok, _ := space.Allocate(entireRange)
|
||||
require.True(t, ok, "Failed to get address")
|
||||
for i := 0; i < 20; i++ {
|
||||
ok, _ := space.Allocate()
|
||||
wt.AssertTrue(t, ok, "Failed to get address")
|
||||
}
|
||||
|
||||
// Check we are full
|
||||
ok, _ := space.Allocate(entireRange)
|
||||
require.True(t, !ok, "Should have failed to get address")
|
||||
r, _ = space.Donate(entireRange)
|
||||
require.True(t, r.Size() == 0, "Wrong space")
|
||||
ok, _ := space.Allocate()
|
||||
wt.AssertTrue(t, !ok, "Should have failed to get address")
|
||||
_, size, ok = space.Donate()
|
||||
wt.AssertTrue(t, size == 0, "Wrong space")
|
||||
|
||||
// Free in the middle
|
||||
require.NoError(t, space.Free(ip("10.0.3.23")))
|
||||
r = space.biggestFreeRange(entireRange)
|
||||
require.True(t, r.Start == ip("10.0.3.23") && r.Size() == 1, "Wrong space")
|
||||
wt.AssertSuccess(t, space.Free(ip("10.0.3.13")))
|
||||
pos, size = space.biggestFreeRange()
|
||||
wt.AssertTrue(t, space.free[pos] == ip("10.0.3.13") && size == 1, "Wrong space")
|
||||
|
||||
// Free one at the end
|
||||
require.NoError(t, space.Free(ip("10.0.3.31")))
|
||||
r = space.biggestFreeRange(entireRange)
|
||||
require.True(t, r.Start == ip("10.0.3.31") && r.Size() == 1, "Wrong space")
|
||||
wt.AssertSuccess(t, space.Free(ip("10.0.3.23")))
|
||||
pos, size = space.biggestFreeRange()
|
||||
wt.AssertTrue(t, space.free[pos] == ip("10.0.3.23") && size == 1, "Wrong space")
|
||||
|
||||
// Now free a few at the end
|
||||
require.NoError(t, space.Free(ip("10.0.3.30")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.29")))
|
||||
wt.AssertSuccess(t, space.Free(ip("10.0.3.22")))
|
||||
wt.AssertSuccess(t, space.Free(ip("10.0.3.21")))
|
||||
|
||||
require.Equal(t, address.Count(4), space.NumFreeAddresses())
|
||||
wt.AssertEquals(t, space.NumFreeAddresses(), address.Offset(4))
|
||||
|
||||
// Now get the biggest free space; should be 3.30
|
||||
r = space.biggestFreeRange(entireRange)
|
||||
require.Equal(t, address.NewRange(ip("10.0.3.30"), 2), r, "Wrong space")
|
||||
// Now get the biggest free space; should be 3.21
|
||||
pos, size = space.biggestFreeRange()
|
||||
wt.AssertTrue(t, space.free[pos] == ip("10.0.3.21") && size == 3, "Wrong space")
|
||||
|
||||
// Now free a few in the middle
|
||||
require.NoError(t, space.Free(ip("10.0.3.24")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.22")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.21")))
|
||||
wt.AssertSuccess(t, space.Free(ip("10.0.3.12")))
|
||||
wt.AssertSuccess(t, space.Free(ip("10.0.3.11")))
|
||||
wt.AssertSuccess(t, space.Free(ip("10.0.3.10")))
|
||||
|
||||
require.Equal(t, address.Count(7), space.NumFreeAddresses())
|
||||
wt.AssertEquals(t, space.NumFreeAddresses(), address.Offset(7))
|
||||
|
||||
// Now get the biggest free space; should be 3.30
|
||||
r = space.biggestFreeRange(entireRange)
|
||||
require.Equal(t, address.NewRange(ip("10.0.3.30"), 2), r, "Wrong space")
|
||||
// Now get the biggest free space; should be 3.21
|
||||
pos, size = space.biggestFreeRange()
|
||||
wt.AssertTrue(t, space.free[pos] == ip("10.0.3.10") && size == 4, "Wrong space")
|
||||
|
||||
require.Equal(t, []address.Range{{Start: ip("10.0.3.16"), End: ip("10.0.3.32")}}, space.OwnedRanges())
|
||||
wt.AssertEquals(t, space.OwnedRanges(), []address.Range{address.Range{Start: ip("10.0.3.4"), End: ip("10.0.3.24")}})
|
||||
}
|
||||
|
||||
func (s1 *Space) Equal(s2 *Space) bool {
|
||||
return reflect.DeepEqual(s1.ours, s2.ours) && reflect.DeepEqual(s1.free, s2.free)
|
||||
}
|
||||
|
||||
func TestDonateSimple(t *testing.T) {
|
||||
const (
|
||||
testAddr1 = "10.0.1.0"
|
||||
testAddr2 = "10.0.1.32"
|
||||
size = 64
|
||||
)
|
||||
|
||||
var (
|
||||
ipAddr1 = ip(testAddr1)
|
||||
)
|
||||
|
||||
ps1 := makeSpace(ipAddr1, size)
|
||||
ps1 := makeSpace(ipAddr1, 48)
|
||||
|
||||
// Empty space set should split in two and give me the second half
|
||||
r, ok := ps1.Donate(address.NewRange(ip(testAddr1), size))
|
||||
numGivenUp := r.Size()
|
||||
require.True(t, ok, "Donate result")
|
||||
require.Equal(t, "10.0.1.32", r.Start.String(), "Invalid start")
|
||||
require.Equal(t, address.Count(size/2), numGivenUp)
|
||||
require.Equal(t, address.Count(size/2), ps1.NumFreeAddresses())
|
||||
start, numGivenUp, ok := ps1.Donate()
|
||||
wt.AssertTrue(t, ok, "Donate result")
|
||||
wt.AssertEqualString(t, start.String(), "10.0.1.24", "Invalid start")
|
||||
wt.AssertEquals(t, numGivenUp, address.Offset(24))
|
||||
wt.AssertEquals(t, ps1.NumFreeAddresses(), address.Offset(24))
|
||||
|
||||
// Now check we can give the rest up.
|
||||
count := 0 // count to avoid infinite loop
|
||||
for ; count < 1000; count++ {
|
||||
r, ok := ps1.Donate(address.NewRange(ip(testAddr1), size))
|
||||
_, size, ok := ps1.Donate()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
numGivenUp += r.Size()
|
||||
numGivenUp += size
|
||||
}
|
||||
require.Equal(t, address.Count(0), ps1.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(size), numGivenUp)
|
||||
wt.AssertEquals(t, ps1.NumFreeAddresses(), address.Offset(0))
|
||||
wt.AssertEquals(t, numGivenUp, address.Offset(48))
|
||||
}
|
||||
|
||||
func TestDonateHard(t *testing.T) {
|
||||
@@ -232,28 +222,28 @@ func TestDonateHard(t *testing.T) {
|
||||
// Fill a fresh space
|
||||
spaceset := makeSpace(start, size)
|
||||
for i := address.Offset(0); i < size; i++ {
|
||||
ok, _ := spaceset.Allocate(address.NewRange(start, size))
|
||||
require.True(t, ok, "Failed to get IP!")
|
||||
ok, _ := spaceset.Allocate()
|
||||
wt.AssertTrue(t, ok, "Failed to get IP!")
|
||||
}
|
||||
|
||||
require.Equal(t, address.Count(0), spaceset.NumFreeAddresses())
|
||||
wt.AssertEquals(t, spaceset.NumFreeAddresses(), address.Offset(0))
|
||||
|
||||
// Now free all but the last address
|
||||
// this will force us to split the free list
|
||||
for i := address.Offset(0); i < size-1; i++ {
|
||||
require.NoError(t, spaceset.Free(address.Add(start, i)))
|
||||
wt.AssertSuccess(t, spaceset.Free(address.Add(start, i)))
|
||||
}
|
||||
|
||||
// Now split
|
||||
newRange, ok := spaceset.Donate(address.NewRange(start, size))
|
||||
require.True(t, ok, "GiveUpSpace result")
|
||||
require.Equal(t, address.NewRange(ip("10.0.1.16"), 16), newRange, "Wrong space")
|
||||
require.Equal(t, address.Count(31), spaceset.NumFreeAddresses())
|
||||
newRange, numGivenUp, ok := spaceset.Donate()
|
||||
wt.AssertTrue(t, ok, "GiveUpSpace result")
|
||||
wt.AssertEquals(t, newRange, ip("10.0.1.23"))
|
||||
wt.AssertEquals(t, numGivenUp, address.Offset(24))
|
||||
wt.AssertEquals(t, spaceset.NumFreeAddresses(), address.Offset(23))
|
||||
|
||||
//Space set should now have 3 spaces
|
||||
expected := &Space{
|
||||
ours: add(nil, ip("10.0.1.47"), ip("10.0.1.48")),
|
||||
free: add(add(nil, ip("10.0.1.0"), ip("10.0.1.16")), ip("10.0.1.32"), ip("10.0.1.47")),
|
||||
}
|
||||
require.Equal(t, expected, spaceset)
|
||||
//Space set should now have 2 spaces
|
||||
expected := New()
|
||||
expected.Add(start, 23)
|
||||
expected.ours = add(nil, ip("10.0.1.47"), ip("10.0.1.48"))
|
||||
wt.AssertEquals(t, spaceset, expected)
|
||||
}
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
package ipam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/weaveworks/weave/ipam/paxos"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
Paxos *paxos.Status
|
||||
Range string
|
||||
RangeNumIPs int
|
||||
ActiveIPs int
|
||||
DefaultSubnet string
|
||||
Entries []EntryStatus
|
||||
PendingClaims []ClaimStatus
|
||||
PendingAllocates []string
|
||||
}
|
||||
|
||||
type EntryStatus struct {
|
||||
Token string
|
||||
Size uint32
|
||||
Peer string
|
||||
Nickname string
|
||||
IsKnownPeer bool
|
||||
Version uint32
|
||||
}
|
||||
|
||||
type ClaimStatus struct {
|
||||
Ident string
|
||||
CIDR address.CIDR
|
||||
}
|
||||
|
||||
func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status {
|
||||
if allocator == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var paxosStatus *paxos.Status
|
||||
if allocator.awaitingConsensus && allocator.paxos != nil {
|
||||
if allocator.paxos.IsElector() {
|
||||
if node, ok := allocator.paxos.(*paxos.Node); ok {
|
||||
paxosStatus = paxos.NewStatus(node)
|
||||
}
|
||||
} else {
|
||||
paxosStatus = &paxos.Status{Elector: false}
|
||||
}
|
||||
}
|
||||
|
||||
resultChan := make(chan *Status)
|
||||
allocator.actionChan <- func() {
|
||||
resultChan <- &Status{
|
||||
paxosStatus,
|
||||
allocator.universe.String(),
|
||||
int(allocator.universe.Size()),
|
||||
int(allocator.space.NumOwnedAddresses()),
|
||||
defaultSubnet.String(),
|
||||
newEntryStatusSlice(allocator),
|
||||
newClaimStatusSlice(allocator),
|
||||
newAllocateIdentSlice(allocator)}
|
||||
}
|
||||
|
||||
return <-resultChan
|
||||
}
|
||||
|
||||
func newEntryStatusSlice(allocator *Allocator) []EntryStatus {
|
||||
var slice []EntryStatus
|
||||
|
||||
if allocator.ring.Empty() {
|
||||
return slice
|
||||
}
|
||||
|
||||
for _, r := range allocator.ring.AllRangeInfo() {
|
||||
slice = append(slice, EntryStatus{
|
||||
Token: r.Start.String(),
|
||||
Size: uint32(r.Size()),
|
||||
Peer: r.Peer.String(),
|
||||
Nickname: allocator.nicknames[r.Peer],
|
||||
IsKnownPeer: allocator.isKnownPeer(r.Peer),
|
||||
Version: r.Version,
|
||||
})
|
||||
}
|
||||
|
||||
return slice
|
||||
}
|
||||
|
||||
func newClaimStatusSlice(allocator *Allocator) []ClaimStatus {
|
||||
var slice []ClaimStatus
|
||||
for _, op := range allocator.pendingClaims {
|
||||
claim := op.(*claim)
|
||||
slice = append(slice, ClaimStatus{claim.ident, claim.cidr})
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func newAllocateIdentSlice(allocator *Allocator) []string {
|
||||
var slice []string
|
||||
for _, op := range allocator.pendingAllocates {
|
||||
allocate := op.(*allocate)
|
||||
slice = append(slice, fmt.Sprintf("%s %s", allocate.ident, allocate.r.String()))
|
||||
}
|
||||
return slice
|
||||
}
|
||||
+136
-100
@@ -3,19 +3,16 @@ package ipam
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/testing/gossip"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/router"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
type mockMessage struct {
|
||||
dst mesh.PeerName
|
||||
dst router.PeerName
|
||||
msgType byte
|
||||
buf []byte
|
||||
}
|
||||
@@ -33,15 +30,12 @@ func toStringArray(messages []mockMessage) []string {
|
||||
}
|
||||
|
||||
type mockGossipComms struct {
|
||||
sync.RWMutex
|
||||
*testing.T
|
||||
t *testing.T
|
||||
name string
|
||||
messages []mockMessage
|
||||
}
|
||||
|
||||
func (m *mockGossipComms) String() string {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
return fmt.Sprintf("[mockGossipComms %s]", m.name)
|
||||
}
|
||||
|
||||
@@ -49,25 +43,21 @@ func (m *mockGossipComms) String() string {
|
||||
// that the contents of messages are never re-ordered. Which, for instance,
|
||||
// requires they are not based off iterating through a map.
|
||||
|
||||
func (m *mockGossipComms) GossipBroadcast(update mesh.GossipData) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
func (m *mockGossipComms) GossipBroadcast(update router.GossipData) error {
|
||||
buf := []byte{}
|
||||
if len(m.messages) == 0 {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Gossip broadcast message unexpected: \n%x", m.name, buf))
|
||||
} else if msg := m.messages[0]; msg.dst != mesh.UnknownPeerName {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message to %s but got broadcast", m.name, msg.dst))
|
||||
m.Fatalf("%s: Gossip broadcast message unexpected: \n%x", m.name, buf)
|
||||
} else if msg := m.messages[0]; msg.dst != router.UnknownPeerName {
|
||||
m.Fatalf("%s: Expected Gossip message to %s but got broadcast", m.name, msg.dst)
|
||||
} else if msg.buf != nil && !equalByteBuffer(msg.buf, buf) {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf))
|
||||
m.Fatalf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf)
|
||||
} else {
|
||||
// Swallow this message
|
||||
m.messages = m.messages[1:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockGossipComms) GossipNeighbourSubset(update mesh.GossipData) {
|
||||
m.GossipBroadcast(update)
|
||||
}
|
||||
func equalByteBuffer(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
@@ -80,19 +70,22 @@ func equalByteBuffer(a, b []byte) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *mockGossipComms) GossipUnicast(dstPeerName mesh.PeerName, buf []byte) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
func (m *mockGossipComms) Fatalf(format string, args ...interface{}) {
|
||||
// this sometimes hangs: wt.Fatalf(m.t, args...)
|
||||
panic(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (m *mockGossipComms) GossipUnicast(dstPeerName router.PeerName, buf []byte) error {
|
||||
if len(m.messages) == 0 {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Gossip message to %s unexpected: \n%s", m.name, dstPeerName, buf))
|
||||
} else if msg := m.messages[0]; msg.dst == mesh.UnknownPeerName {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip broadcast message but got dest %s", m.name, dstPeerName))
|
||||
m.Fatalf("%s: Gossip message to %s unexpected: \n%s", m.name, dstPeerName, buf)
|
||||
} else if msg := m.messages[0]; msg.dst == router.UnknownPeerName {
|
||||
m.Fatalf("%s: Expected Gossip broadcast message but got dest %s", m.name, dstPeerName)
|
||||
} else if msg.dst != dstPeerName {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message to %s but got dest %s", m.name, msg.dst, dstPeerName))
|
||||
m.Fatalf("%s: Expected Gossip message to %s but got dest %s", m.name, msg.dst, dstPeerName)
|
||||
} else if buf[0] != msg.msgType {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message of type %d but got type %d", m.name, msg.msgType, buf[0]))
|
||||
m.Fatalf("%s: Expected Gossip message of type %d but got type %d", m.name, msg.msgType, buf[0])
|
||||
} else if msg.buf != nil && !equalByteBuffer(msg.buf, buf[1:]) {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf[1:]))
|
||||
m.Fatalf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf[1:])
|
||||
} else {
|
||||
// Swallow this message
|
||||
m.messages = m.messages[1:]
|
||||
@@ -102,69 +95,49 @@ func (m *mockGossipComms) GossipUnicast(dstPeerName mesh.PeerName, buf []byte) e
|
||||
|
||||
func ExpectMessage(alloc *Allocator, dst string, msgType byte, buf []byte) {
|
||||
m := alloc.gossip.(*mockGossipComms)
|
||||
dstPeerName, _ := mesh.PeerNameFromString(dst)
|
||||
m.Lock()
|
||||
dstPeerName, _ := router.PeerNameFromString(dst)
|
||||
m.messages = append(m.messages, mockMessage{dstPeerName, msgType, buf})
|
||||
m.Unlock()
|
||||
}
|
||||
|
||||
func ExpectBroadcastMessage(alloc *Allocator, buf []byte) {
|
||||
m := alloc.gossip.(*mockGossipComms)
|
||||
m.Lock()
|
||||
m.messages = append(m.messages, mockMessage{mesh.UnknownPeerName, 0, buf})
|
||||
m.Unlock()
|
||||
m.messages = append(m.messages, mockMessage{router.UnknownPeerName, 0, buf})
|
||||
}
|
||||
|
||||
func CheckAllExpectedMessagesSent(allocs ...*Allocator) {
|
||||
for _, alloc := range allocs {
|
||||
m := alloc.gossip.(*mockGossipComms)
|
||||
m.RLock()
|
||||
if len(m.messages) > 0 {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Gossip message(s) not sent as expected: \n%v", m.name, m.messages))
|
||||
wt.Fatalf(m.t, "%s: Gossip message(s) not sent as expected: \n%x", m.name, m.messages)
|
||||
}
|
||||
m.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
type mockDB struct{}
|
||||
|
||||
func (d *mockDB) Load(_ string, _ interface{}) (bool, error) { return false, nil }
|
||||
func (d *mockDB) Save(_ string, _ interface{}) error { return nil }
|
||||
|
||||
func makeAllocator(name string, cidrStr string, quorum uint, preClaims ...PreClaim) (*Allocator, address.CIDR) {
|
||||
peername, err := mesh.PeerNameFromString(name)
|
||||
func makeAllocator(name string, cidr string, quorum uint) *Allocator {
|
||||
peername, err := router.PeerNameFromString(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cidr, err := address.ParseCIDR(cidrStr)
|
||||
alloc, err := NewAllocator(peername, router.PeerUID(rand.Int63()),
|
||||
"nick-"+name, cidr, quorum)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return NewAllocator(Config{
|
||||
OurName: peername,
|
||||
OurUID: mesh.PeerUID(rand.Int63()),
|
||||
OurNickname: "nick-" + name,
|
||||
Universe: cidr,
|
||||
IsObserver: quorum == 0,
|
||||
PreClaims: preClaims,
|
||||
Quorum: func() uint { return quorum },
|
||||
Db: new(mockDB),
|
||||
IsKnownPeer: func(mesh.PeerName) bool { return true },
|
||||
}), cidr
|
||||
return alloc
|
||||
}
|
||||
|
||||
func makeAllocatorWithMockGossip(t *testing.T, name string, universeCIDR string, quorum uint) (*Allocator, address.CIDR) {
|
||||
alloc, subnet := makeAllocator(name, universeCIDR, quorum)
|
||||
gossip := &mockGossipComms{T: t, name: name}
|
||||
func makeAllocatorWithMockGossip(t *testing.T, name string, universeCIDR string, quorum uint) *Allocator {
|
||||
alloc := makeAllocator(name, universeCIDR, quorum)
|
||||
gossip := &mockGossipComms{t: t, name: name}
|
||||
alloc.SetInterfaces(gossip)
|
||||
alloc.Start()
|
||||
return alloc, subnet
|
||||
return alloc
|
||||
}
|
||||
|
||||
func (alloc *Allocator) claimRingForTesting(allocs ...*Allocator) {
|
||||
peers := []mesh.PeerName{alloc.ourName}
|
||||
peers := []router.PeerName{alloc.ourName}
|
||||
for _, alloc2 := range allocs {
|
||||
peers = append(peers, alloc2.ourName)
|
||||
}
|
||||
@@ -172,22 +145,6 @@ func (alloc *Allocator) claimRingForTesting(allocs ...*Allocator) {
|
||||
alloc.space.AddRanges(alloc.ring.OwnedRanges())
|
||||
}
|
||||
|
||||
func (alloc *Allocator) NumFreeAddresses(r address.Range) address.Count {
|
||||
resultChan := make(chan address.Count)
|
||||
alloc.actionChan <- func() {
|
||||
resultChan <- alloc.space.NumFreeAddressesInRange(r)
|
||||
}
|
||||
return <-resultChan
|
||||
}
|
||||
|
||||
func (alloc *Allocator) OwnedRanges() (result []address.Range) {
|
||||
resultChan := make(chan []address.Range)
|
||||
alloc.actionChan <- func() {
|
||||
resultChan <- alloc.ring.OwnedRanges()
|
||||
}
|
||||
return <-resultChan
|
||||
}
|
||||
|
||||
// Check whether or not something was sent on a channel
|
||||
func AssertSent(t *testing.T, ch <-chan bool) {
|
||||
timeout := time.After(10 * time.Second)
|
||||
@@ -195,14 +152,14 @@ func AssertSent(t *testing.T, ch <-chan bool) {
|
||||
case <-ch:
|
||||
// This case is ok
|
||||
case <-timeout:
|
||||
require.FailNow(t, "Nothing sent on channel")
|
||||
wt.Fatalf(t, "Nothing sent on channel")
|
||||
}
|
||||
}
|
||||
|
||||
func AssertNothingSent(t *testing.T, ch <-chan bool) {
|
||||
select {
|
||||
case val := <-ch:
|
||||
require.FailNow(t, fmt.Sprintf("Unexpected value on channel: %v", val))
|
||||
wt.Fatalf(t, "Unexpected value on channel: %t", val)
|
||||
default:
|
||||
// no message received
|
||||
}
|
||||
@@ -211,39 +168,118 @@ func AssertNothingSent(t *testing.T, ch <-chan bool) {
|
||||
func AssertNothingSentErr(t *testing.T, ch <-chan error) {
|
||||
select {
|
||||
case val := <-ch:
|
||||
require.FailNow(t, fmt.Sprintf("Unexpected value on channel: %v", val))
|
||||
wt.Fatalf(t, "Unexpected value on channel: %t", val)
|
||||
default:
|
||||
// no message received
|
||||
}
|
||||
}
|
||||
|
||||
func makeNetworkOfAllocators(size int, cidr string, preClaims ...[]PreClaim) ([]*Allocator, *gossip.TestRouter, address.CIDR) {
|
||||
gossipRouter := gossip.NewTestRouter(0.0)
|
||||
// Router to convey gossip from one gossiper to another, for testing
|
||||
type gossipMessage struct {
|
||||
isUnicast bool
|
||||
sender *router.PeerName
|
||||
buf []byte
|
||||
exitChan chan bool
|
||||
}
|
||||
|
||||
type TestGossipRouter struct {
|
||||
gossipChans map[router.PeerName]chan gossipMessage
|
||||
loss float32 // 0.0 means no loss
|
||||
}
|
||||
|
||||
func (grouter *TestGossipRouter) GossipBroadcast(update router.GossipData) error {
|
||||
for _, gossipChan := range grouter.gossipChans {
|
||||
select {
|
||||
case gossipChan <- gossipMessage{buf: update.(*ipamGossipData).alloc.encode()}:
|
||||
default: // drop the message if we cannot send it
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (grouter *TestGossipRouter) removePeer(peer router.PeerName) {
|
||||
gossipChan := grouter.gossipChans[peer]
|
||||
resultChan := make(chan bool)
|
||||
gossipChan <- gossipMessage{exitChan: resultChan}
|
||||
<-resultChan
|
||||
delete(grouter.gossipChans, peer)
|
||||
}
|
||||
|
||||
type TestGossipRouterClient struct {
|
||||
router *TestGossipRouter
|
||||
sender router.PeerName
|
||||
}
|
||||
|
||||
func (grouter *TestGossipRouter) connect(sender router.PeerName, gossiper router.Gossiper) router.Gossip {
|
||||
gossipChan := make(chan gossipMessage, 100)
|
||||
|
||||
go func() {
|
||||
gossipTimer := time.Tick(10 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case message := <-gossipChan:
|
||||
if message.exitChan != nil {
|
||||
message.exitChan <- true
|
||||
return
|
||||
}
|
||||
|
||||
if rand.Float32() > (1.0 - grouter.loss) {
|
||||
continue
|
||||
}
|
||||
|
||||
if message.isUnicast {
|
||||
err := gossiper.OnGossipUnicast(*message.sender, message.buf)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error doing gossip unicast to %s: %s", sender, err))
|
||||
}
|
||||
} else {
|
||||
_, err := gossiper.OnGossipBroadcast(message.buf)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error doing gossip broadcast to %s: %s", sender, err))
|
||||
}
|
||||
}
|
||||
case <-gossipTimer:
|
||||
grouter.GossipBroadcast(gossiper.Gossip())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
grouter.gossipChans[sender] = gossipChan
|
||||
return TestGossipRouterClient{grouter, sender}
|
||||
}
|
||||
|
||||
func (client TestGossipRouterClient) GossipUnicast(dstPeerName router.PeerName, buf []byte) error {
|
||||
select {
|
||||
case client.router.gossipChans[dstPeerName] <- gossipMessage{isUnicast: true, sender: &client.sender, buf: buf}:
|
||||
default: // drop the message if we cannot send it
|
||||
common.Error.Printf("Dropping message")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client TestGossipRouterClient) GossipBroadcast(update router.GossipData) error {
|
||||
return client.router.GossipBroadcast(update)
|
||||
}
|
||||
|
||||
func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, TestGossipRouter) {
|
||||
|
||||
gossipRouter := TestGossipRouter{make(map[router.PeerName]chan gossipMessage), 0.0}
|
||||
allocs := make([]*Allocator, size)
|
||||
var subnet address.CIDR
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
var alloc *Allocator
|
||||
preClaim := []PreClaim{}
|
||||
if i < len(preClaims) {
|
||||
preClaim = preClaims[i]
|
||||
}
|
||||
alloc, subnet = makeAllocator(fmt.Sprintf("%02d:00:00:02:00:00", i),
|
||||
cidr, uint(size/2+1), preClaim...)
|
||||
alloc.SetInterfaces(gossipRouter.Connect(alloc.ourName, alloc))
|
||||
alloc := makeAllocator(fmt.Sprintf("%02d:00:00:02:00:00", i),
|
||||
cidr, uint(size/2+1))
|
||||
alloc.SetInterfaces(gossipRouter.connect(alloc.ourName, alloc))
|
||||
alloc.Start()
|
||||
allocs[i] = alloc
|
||||
}
|
||||
|
||||
allocs[size-1].gossip.GossipBroadcast(allocs[size-1].Gossip())
|
||||
gossipRouter.Flush()
|
||||
return allocs, gossipRouter, subnet
|
||||
gossipRouter.GossipBroadcast(allocs[size-1].Gossip())
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
return allocs, gossipRouter
|
||||
}
|
||||
|
||||
func stopNetworkOfAllocators(allocs []*Allocator, gossipRouter *gossip.TestRouter) {
|
||||
// NB: We must stop the router first since ipam gossip makes
|
||||
// synchronous calls into the allocator.
|
||||
gossipRouter.Stop()
|
||||
func stopNetworkOfAllocators(allocs []*Allocator) {
|
||||
for _, alloc := range allocs {
|
||||
alloc.Stop()
|
||||
}
|
||||
|
||||
@@ -1,247 +0,0 @@
|
||||
package tracker
|
||||
|
||||
// The AWSVPC tracker tracks the IPAM ring changes and accordingly updates
|
||||
// the AWS VPC route table and, if the announced change is local
|
||||
// (denoted by local=true) to the host, the host route table.
|
||||
//
|
||||
// During the initialization, the tracker detects AWS VPC route table id which
|
||||
// is associated with the host's subnet on the AWS network. If such a table does
|
||||
// not exist, the default VPC route table is used.
|
||||
//
|
||||
// When a host A donates a range to a host B, the necessary route table
|
||||
// updates (removal) happen on the host A first, and afterwards on
|
||||
// the host B (installation).
|
||||
//
|
||||
// NB: there is a hard limit for 50 routes within any VPC route table
|
||||
// (practically, it is 48, because one route is used by the AWS Internet GW and
|
||||
// one by the AWS host subnet), therefore it is suggested to avoid
|
||||
// an excessive fragmentation within the IPAM ring which might happen due to
|
||||
// the claim operations or uneven distribution of containers across the hosts.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
type AWSVPCTracker struct {
|
||||
ec2 *ec2.EC2
|
||||
instanceID string // EC2 Instance ID
|
||||
routeTableID string // VPC Route Table ID
|
||||
linkIndex int // The weave bridge link index
|
||||
}
|
||||
|
||||
// NewAWSVPCTracker creates and initialises AWS VPC based tracker.
|
||||
func NewAWSVPCTracker(bridgeName string) (*AWSVPCTracker, error) {
|
||||
var (
|
||||
err error
|
||||
session = session.New()
|
||||
t = &AWSVPCTracker{}
|
||||
)
|
||||
|
||||
// Detect region and instance id
|
||||
meta := ec2metadata.New(session)
|
||||
t.instanceID, err = meta.GetMetadata("instance-id")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot detect instance-id: %s", err)
|
||||
}
|
||||
region, err := meta.Region()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot detect region: %s", err)
|
||||
}
|
||||
|
||||
t.ec2 = ec2.New(session, aws.NewConfig().WithRegion(region))
|
||||
|
||||
routeTableID, err := t.detectRouteTableID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.routeTableID = *routeTableID
|
||||
|
||||
// Detect Weave bridge link index
|
||||
link, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find \"%s\" interface: %s", bridgeName, err)
|
||||
}
|
||||
t.linkIndex = link.Attrs().Index
|
||||
|
||||
t.infof("AWSVPC has been initialized on %s instance for %s route table at %s region",
|
||||
t.instanceID, t.routeTableID, region)
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// HandleUpdate method updates the AWS VPC and the host route tables.
|
||||
func (t *AWSVPCTracker) HandleUpdate(prevRanges, currRanges []address.Range, local bool) error {
|
||||
t.debugf("replacing %q by %q; local(%t)", prevRanges, currRanges, local)
|
||||
|
||||
prev, curr := RemoveCommon(address.NewCIDRs(Merge(prevRanges)), address.NewCIDRs(Merge(currRanges)))
|
||||
|
||||
// It might make sense to do the removal first and then add entries
|
||||
// because of the 50 routes limit. However, in such case a container might
|
||||
// not be reachable for a short period of time which is not a desired behavior.
|
||||
|
||||
// Add new entries
|
||||
for _, cidr := range curr {
|
||||
cidrStr := cidr.String()
|
||||
t.debugf("adding route %s to %s", cidrStr, t.instanceID)
|
||||
if _, err := t.createVPCRoute(cidrStr); err != nil {
|
||||
return fmt.Errorf("createVPCRoutes failed: %s", err)
|
||||
}
|
||||
if local {
|
||||
if err := t.createHostRoute(cidrStr); err != nil {
|
||||
if errno, ok := err.(syscall.Errno); !(ok && errno == syscall.EEXIST) {
|
||||
return fmt.Errorf("createHostRoute failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove obsolete entries
|
||||
for _, cidr := range prev {
|
||||
cidrStr := cidr.String()
|
||||
t.debugf("removing %s route", cidrStr)
|
||||
if _, err := t.deleteVPCRoute(cidrStr); err != nil {
|
||||
return fmt.Errorf("deleteVPCRoute failed: %s", err)
|
||||
}
|
||||
if local {
|
||||
if err := t.deleteHostRoute(cidrStr); err != nil {
|
||||
return fmt.Errorf("deleteHostRoute failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *AWSVPCTracker) String() string {
|
||||
return "awsvpc"
|
||||
}
|
||||
|
||||
func (t *AWSVPCTracker) createVPCRoute(cidr string) (*ec2.CreateRouteOutput, error) {
|
||||
route := &ec2.CreateRouteInput{
|
||||
RouteTableId: &t.routeTableID,
|
||||
InstanceId: &t.instanceID,
|
||||
DestinationCidrBlock: &cidr,
|
||||
}
|
||||
return t.ec2.CreateRoute(route)
|
||||
}
|
||||
|
||||
func (t *AWSVPCTracker) createHostRoute(cidr string) error {
|
||||
dst, err := parseCIDR(cidr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
route := &netlink.Route{
|
||||
LinkIndex: t.linkIndex,
|
||||
Dst: dst,
|
||||
Scope: netlink.SCOPE_LINK,
|
||||
}
|
||||
return netlink.RouteAdd(route)
|
||||
}
|
||||
|
||||
func (t *AWSVPCTracker) deleteVPCRoute(cidr string) (*ec2.DeleteRouteOutput, error) {
|
||||
route := &ec2.DeleteRouteInput{
|
||||
RouteTableId: &t.routeTableID,
|
||||
DestinationCidrBlock: &cidr,
|
||||
}
|
||||
return t.ec2.DeleteRoute(route)
|
||||
}
|
||||
|
||||
func (t *AWSVPCTracker) deleteHostRoute(cidr string) error {
|
||||
dst, err := parseCIDR(cidr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
route := &netlink.Route{
|
||||
LinkIndex: t.linkIndex,
|
||||
Dst: dst,
|
||||
Scope: netlink.SCOPE_LINK,
|
||||
}
|
||||
return netlink.RouteDel(route)
|
||||
}
|
||||
|
||||
// detectRouteTableID detects AWS VPC Route Table ID of the given tracker instance.
|
||||
func (t *AWSVPCTracker) detectRouteTableID() (*string, error) {
|
||||
instancesParams := &ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{aws.String(t.instanceID)},
|
||||
}
|
||||
instancesResp, err := t.ec2.DescribeInstances(instancesParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DescribeInstances failed: %s", err)
|
||||
}
|
||||
if len(instancesResp.Reservations) == 0 ||
|
||||
len(instancesResp.Reservations[0].Instances) == 0 {
|
||||
return nil, fmt.Errorf("cannot find %s instance within reservations", t.instanceID)
|
||||
}
|
||||
vpcID := instancesResp.Reservations[0].Instances[0].VpcId
|
||||
subnetID := instancesResp.Reservations[0].Instances[0].SubnetId
|
||||
|
||||
// First try to find a routing table associated with the subnet of the instance
|
||||
tablesParams := &ec2.DescribeRouteTablesInput{
|
||||
Filters: []*ec2.Filter{
|
||||
{
|
||||
Name: aws.String("association.subnet-id"),
|
||||
Values: []*string{subnetID},
|
||||
},
|
||||
},
|
||||
}
|
||||
tablesResp, err := t.ec2.DescribeRouteTables(tablesParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DescribeRouteTables failed: %s", err)
|
||||
}
|
||||
if len(tablesResp.RouteTables) != 0 {
|
||||
return tablesResp.RouteTables[0].RouteTableId, nil
|
||||
}
|
||||
// Fallback to the default routing table
|
||||
tablesParams = &ec2.DescribeRouteTablesInput{
|
||||
Filters: []*ec2.Filter{
|
||||
{
|
||||
Name: aws.String("association.main"),
|
||||
Values: []*string{aws.String("true")},
|
||||
},
|
||||
{
|
||||
Name: aws.String("vpc-id"),
|
||||
Values: []*string{vpcID},
|
||||
},
|
||||
},
|
||||
}
|
||||
tablesResp, err = t.ec2.DescribeRouteTables(tablesParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DescribeRouteTables failed: %s", err)
|
||||
}
|
||||
if len(tablesResp.RouteTables) != 0 {
|
||||
return tablesResp.RouteTables[0].RouteTableId, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("cannot find routetable for %s instance", t.instanceID)
|
||||
}
|
||||
|
||||
func (t *AWSVPCTracker) debugf(fmt string, args ...interface{}) {
|
||||
common.Log.Debugf("[tracker] "+fmt, args...)
|
||||
}
|
||||
|
||||
func (t *AWSVPCTracker) infof(fmt string, args ...interface{}) {
|
||||
common.Log.Infof("[tracker] "+fmt, args...)
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
func parseCIDR(cidr string) (*net.IPNet, error) {
|
||||
ip, ipnet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ipnet.IP = ip
|
||||
|
||||
return ipnet, nil
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package tracker
|
||||
|
||||
import (
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
// Merge merges adjacent range entries.
|
||||
// The given slice has to be sorted in increasing order.
|
||||
func Merge(r []address.Range) []address.Range {
|
||||
var merged []address.Range
|
||||
|
||||
for i := range r {
|
||||
if prev := len(merged) - 1; prev >= 0 && merged[prev].End == r[i].Start {
|
||||
merged[prev].End = r[i].End
|
||||
} else {
|
||||
merged = append(merged, r[i])
|
||||
}
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
||||
|
||||
// RemoveCommon filters out CIDR ranges which are contained in both a and b slices.
|
||||
// Both slices have to be sorted in increasing order.
|
||||
func RemoveCommon(a, b []address.CIDR) (newA, newB []address.CIDR) {
|
||||
i, j := 0, 0
|
||||
|
||||
for i < len(a) && j < len(b) {
|
||||
switch {
|
||||
case a[i].Start() < b[j].Start() || a[i].End() < b[j].End():
|
||||
newA = append(newA, a[i])
|
||||
i++
|
||||
case a[i].Start() > b[j].Start() || a[i].End() > b[j].End():
|
||||
newB = append(newB, b[j])
|
||||
j++
|
||||
default:
|
||||
i++
|
||||
j++
|
||||
}
|
||||
|
||||
}
|
||||
newA = append(newA, a[i:]...)
|
||||
newB = append(newB, b[j:]...)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package tracker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
var (
|
||||
r0to127 = cidr("10.0.0.0", "10.0.0.127")
|
||||
r128to255 = cidr("10.0.0.128", "10.0.0.255")
|
||||
r0to255 = cidr("10.0.0.0", "10.0.0.255")
|
||||
r1dot0to255 = cidr("10.0.1.0", "10.0.1.255")
|
||||
r2dot0to255 = cidr("10.0.2.0", "10.0.2.255")
|
||||
)
|
||||
|
||||
func TestRemoveCommon(t *testing.T) {
|
||||
a := []address.CIDR{r0to127, r1dot0to255}
|
||||
b := []address.CIDR{r1dot0to255, r2dot0to255}
|
||||
newA, newB := RemoveCommon(a, b)
|
||||
require.Equal(t, []address.CIDR{r0to127}, newA)
|
||||
require.Equal(t, []address.CIDR{r2dot0to255}, newB)
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
ranges := []address.Range{
|
||||
r0to127.Range(),
|
||||
r128to255.Range(),
|
||||
r2dot0to255.Range(),
|
||||
}
|
||||
require.Equal(t, []address.Range{r0to255.Range(), r2dot0to255.Range()}, Merge(ranges))
|
||||
}
|
||||
|
||||
// Helper
|
||||
|
||||
// TODO(mp) DRY with helpers of other tests.
|
||||
|
||||
func ip(s string) address.Address {
|
||||
addr, _ := address.ParseIP(s)
|
||||
return addr
|
||||
}
|
||||
|
||||
// [start; end]
|
||||
func cidr(start, end string) address.CIDR {
|
||||
c := address.Range{Start: ip(start), End: ip(end) + 1}.CIDRs()
|
||||
if len(c) != 1 {
|
||||
panic("invalid cidr")
|
||||
}
|
||||
return c[0]
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package tracker
|
||||
|
||||
import (
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
// LocalRangeTracker is an interface for tracking changes in the IPAM ring.
|
||||
type LocalRangeTracker interface {
|
||||
// HandleUpdate is called whenever an address ring gets updated.
|
||||
//
|
||||
// prevRanges corresponds to ranges which were owned by a peer before
|
||||
// a change in the ring, while currRanges to the ones which are currently
|
||||
// owned by the peer.
|
||||
// Both slices have to be sorted in increasing order.
|
||||
// Adjacent ranges within each slice might appear as separate ranges.
|
||||
//
|
||||
// The local parameter indicates whether the ranges belong to the peer
|
||||
// by which the method is called.
|
||||
HandleUpdate(prevRanges, currRanges []address.Range, local bool) error
|
||||
|
||||
// String returns the tracker name
|
||||
String() string
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// A zone record
|
||||
// Note: we will try to keep all the zone records information is concentrated here. Maybe not all queries will
|
||||
// use things like priorities or weights, but we do not want to create a hierarchy for dealing with all
|
||||
// possible queries...
|
||||
type ZoneRecord interface {
|
||||
Name() string // The name for this IP
|
||||
IP() net.IP // The IP (v4) address
|
||||
Priority() int // The priority
|
||||
Weight() int // The weight
|
||||
TTL() int // TTL
|
||||
}
|
||||
|
||||
type ZoneLookup interface {
|
||||
// Lookup for a name
|
||||
LookupName(name string) ([]ZoneRecord, error)
|
||||
// Lookup for an address
|
||||
LookupInaddr(inaddr string) ([]ZoneRecord, error)
|
||||
}
|
||||
|
||||
type ZoneObserver interface {
|
||||
// Observe anything that affects a particular name in the zone
|
||||
ObserveName(name string, observer ZoneRecordObserver) error
|
||||
// Observe anything that affects a particular IP in the zone
|
||||
ObserveInaddr(inaddr string, observer ZoneRecordObserver) error
|
||||
}
|
||||
|
||||
type ZoneRecordObserver func()
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
// A basic record struct (satisfying the ZoneRecord interface)
|
||||
type Record struct {
|
||||
name string
|
||||
ip net.IP
|
||||
priority int
|
||||
weight int
|
||||
ttl int
|
||||
}
|
||||
|
||||
func (r Record) Name() string { return r.name }
|
||||
func (r Record) IP() net.IP { return r.ip }
|
||||
func (r Record) Priority() int { return r.priority }
|
||||
func (r Record) Weight() int { return r.weight }
|
||||
func (r Record) TTL() int { return r.ttl }
|
||||
|
||||
func (r Record) String() string {
|
||||
var buf bytes.Buffer
|
||||
if len(r.Name()) > 0 {
|
||||
fmt.Fprintf(&buf, "%s", r.Name())
|
||||
}
|
||||
if !r.IP().IsUnspecified() {
|
||||
fmt.Fprintf(&buf, "[%s]", r.IP())
|
||||
}
|
||||
if r.Priority() > 0 {
|
||||
fmt.Fprintf(&buf, "/P:%d", r.Priority())
|
||||
}
|
||||
if r.Weight() > 0 {
|
||||
fmt.Fprintf(&buf, "/W:%d", r.Weight())
|
||||
}
|
||||
if r.TTL() > 0 {
|
||||
fmt.Fprintf(&buf, "/TTL:%d", r.TTL())
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
func newParseError(reason string, addr string) *net.ParseError {
|
||||
return &net.ParseError{Type: reason, Text: addr}
|
||||
}
|
||||
|
||||
// simple IPv4 type that can be used as a key in a map (in contrast with net.IP), used for sets of IPs, etc...
|
||||
type IPv4 [4]byte
|
||||
|
||||
func (ip IPv4) toNetIP() net.IP { return net.IPv4(ip[0], ip[1], ip[2], ip[3]) }
|
||||
func (ip IPv4) String() string { return ip.toNetIP().String() }
|
||||
func ipToIPv4(nip net.IP) IPv4 { ip := nip.To4(); return IPv4([4]byte{ip[0], ip[1], ip[2], ip[3]}) }
|
||||
|
||||
// Parse an address (eg, "10.13.12.11") and return the corresponding IPv4 (eg, "10.13.12.11")
|
||||
func addrToIPv4(addr string) (IPv4, error) {
|
||||
ip := net.ParseIP(addr)
|
||||
if ip == nil {
|
||||
return IPv4{}, newParseError("IP address", addr)
|
||||
}
|
||||
return ipToIPv4(ip), nil
|
||||
}
|
||||
|
||||
// Parse a reverse address (eg, "11.12.13.10.in-addr.arpa.") and return the corresponding IPv4 (eg, "10.13.12.11")
|
||||
func raddrToIPv4(addr string) (IPv4, error) {
|
||||
l := len(addr)
|
||||
if l < rdnsDomainLen+1 {
|
||||
return IPv4{}, newParseError("too short reverse IP address", addr)
|
||||
}
|
||||
|
||||
suffixLen := l - rdnsDomainLen
|
||||
suffix := addr[suffixLen+1:]
|
||||
if suffix != RDNSDomain {
|
||||
return IPv4{}, newParseError("suffix of reverse IP address", addr)
|
||||
}
|
||||
ipStr := addr[:suffixLen]
|
||||
revIP := net.ParseIP(ipStr)
|
||||
if revIP == nil {
|
||||
return IPv4{}, newParseError("reverse IP address", addr)
|
||||
}
|
||||
|
||||
revIP4 := revIP.To4()
|
||||
return IPv4([4]byte{revIP4[3], revIP4[2], revIP4[1], revIP4[0]}), nil
|
||||
}
|
||||
|
||||
func raddrToIP(addr string) (net.IP, error) {
|
||||
r, err := raddrToIPv4(addr)
|
||||
if err != nil {
|
||||
return net.IP{}, err
|
||||
}
|
||||
return r.toNetIP(), nil
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
. "github.com/weaveworks/weave/common"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAddrs(t *testing.T) {
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestAddrs starting")
|
||||
|
||||
ip, err := addrToIPv4("10.13.12.11")
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertTrue(t, net.ParseIP("10.13.12.11").Equal(ip.toNetIP()), "IP")
|
||||
|
||||
ip, err = raddrToIPv4("11.12.13.10.in-addr.arpa.")
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertTrue(t, net.ParseIP("10.13.12.11").Equal(ip.toNetIP()), "IP")
|
||||
|
||||
// some malformed addresses
|
||||
ip, err = addrToIPv4("10.13.12")
|
||||
wt.AssertTrue(t, err != nil, "when parsing malformed address")
|
||||
ip, err = addrToIPv4("10.13.AA.12")
|
||||
wt.AssertTrue(t, err != nil, "when parsing malformed address")
|
||||
ip, err = raddrToIPv4("11.12.13.10.in-axxx.arpa.")
|
||||
wt.AssertTrue(t, err != nil, "when parsing malformed address")
|
||||
ip, err = raddrToIPv4("11.12.13.10.in-addr")
|
||||
wt.AssertTrue(t, err != nil, "when parsing malformed address")
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/heap"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidCapacity = errors.New("Invalid cache capacity")
|
||||
errCouldNotResolve = errors.New("Could not resolve")
|
||||
errTimeout = errors.New("Timeout while waiting for resolution")
|
||||
errNoLocalReplies = errors.New("No local replies")
|
||||
)
|
||||
|
||||
const (
|
||||
defPendingTimeout int = 5 // timeout for a resolution
|
||||
)
|
||||
|
||||
const nullTTL = 0 // a null TTL
|
||||
|
||||
type entryStatus uint8
|
||||
|
||||
const (
|
||||
stPending entryStatus = iota // someone is waiting for the resolution
|
||||
stResolved entryStatus = iota // resolved
|
||||
)
|
||||
|
||||
var statusToString = map[entryStatus]string{
|
||||
stPending: "pending",
|
||||
stResolved: "resolved",
|
||||
}
|
||||
|
||||
const (
|
||||
CacheNoLocalReplies uint8 = 1 << iota // not found in local network (stored in the cache so we skip another local lookup or some time)
|
||||
)
|
||||
|
||||
// shuffleAnswers reorders answers for very basic load balancing
|
||||
func shuffleAnswers(answers []dns.RR) []dns.RR {
|
||||
n := len(answers)
|
||||
if n > 1 {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
r := i + rand.Intn(n-i)
|
||||
answers[r], answers[i] = answers[i], answers[r]
|
||||
}
|
||||
}
|
||||
|
||||
return answers
|
||||
}
|
||||
|
||||
// a cache entry
|
||||
type cacheEntry struct {
|
||||
Status entryStatus // status of the entry
|
||||
Flags uint8 // some extra flags
|
||||
ReplyLen int
|
||||
|
||||
question dns.Question
|
||||
protocol dnsProtocol
|
||||
reply dns.Msg
|
||||
|
||||
validUntil time.Time // obtained from the reply and stored here for convenience/speed
|
||||
putTime time.Time
|
||||
|
||||
index int // for fast lookups in the heap
|
||||
}
|
||||
|
||||
func newCacheEntry(question *dns.Question, now time.Time) *cacheEntry {
|
||||
e := &cacheEntry{
|
||||
Status: stPending,
|
||||
validUntil: now.Add(time.Second * time.Duration(defPendingTimeout)),
|
||||
question: *question,
|
||||
index: -1,
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// Get a copy of the reply stored in the entry, but with some values adjusted like the TTL
|
||||
func (e *cacheEntry) getReply(request *dns.Msg, maxLen int, now time.Time) (*dns.Msg, error) {
|
||||
if e.Status != stResolved {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// if the reply has expired or is invalid, force the caller to start a new resolution
|
||||
if e.hasExpired(now) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if e.Flags&CacheNoLocalReplies != 0 {
|
||||
return nil, errNoLocalReplies
|
||||
}
|
||||
|
||||
if e.ReplyLen >= maxLen {
|
||||
Debug.Printf("[cache msgid %d] returning truncated reponse: %d > %d", request.MsgHdr.Id, e.ReplyLen, maxLen)
|
||||
return makeTruncatedReply(request), nil
|
||||
}
|
||||
|
||||
// create a copy of the reply, with values for this particular query
|
||||
reply := e.reply.Copy()
|
||||
reply.SetReply(request)
|
||||
|
||||
// adjust the TTLs
|
||||
passedSecs := uint32(now.Sub(e.putTime).Seconds())
|
||||
for _, rr := range reply.Answer {
|
||||
hdr := rr.Header()
|
||||
ttl := hdr.Ttl
|
||||
if passedSecs < ttl {
|
||||
hdr.Ttl = ttl - passedSecs
|
||||
} else {
|
||||
return nil, nil // it is expired: do not spend more time and return nil...
|
||||
}
|
||||
}
|
||||
|
||||
reply.Rcode = e.reply.Rcode
|
||||
reply.Authoritative = true
|
||||
|
||||
// shuffle the values, etc...
|
||||
reply.Answer = shuffleAnswers(reply.Answer)
|
||||
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
func (e cacheEntry) hasExpired(now time.Time) bool {
|
||||
return e.validUntil.Before(now) || e.validUntil == now
|
||||
}
|
||||
|
||||
// set the reply for the entry
|
||||
// returns True if the entry has changed the validUntil time
|
||||
func (e *cacheEntry) setReply(reply *dns.Msg, ttl int, flags uint8, now time.Time) bool {
|
||||
var prevValidUntil time.Time
|
||||
if e.Status == stResolved {
|
||||
if reply != nil {
|
||||
Debug.Printf("[cache msgid %d] replacing response in cache", reply.MsgHdr.Id)
|
||||
}
|
||||
prevValidUntil = e.validUntil
|
||||
}
|
||||
|
||||
// make sure we do not overwrite noLocalReplies entries
|
||||
if flags&CacheNoLocalReplies != 0 {
|
||||
if e.Flags&CacheNoLocalReplies != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if ttl != nullTTL {
|
||||
e.validUntil = now.Add(time.Second * time.Duration(ttl))
|
||||
} else if reply != nil {
|
||||
// calculate the validUntil from the reply TTL
|
||||
var minTTL uint32 = math.MaxUint32
|
||||
for _, rr := range reply.Answer {
|
||||
ttl := rr.Header().Ttl
|
||||
if ttl < minTTL {
|
||||
minTTL = ttl // TODO: improve the minTTL calculation (maybe we should skip some RRs)
|
||||
}
|
||||
}
|
||||
e.validUntil = now.Add(time.Second * time.Duration(minTTL))
|
||||
} else {
|
||||
Warning.Printf("[cache] no valid TTL could be calculated")
|
||||
}
|
||||
|
||||
e.Status = stResolved
|
||||
e.Flags = flags
|
||||
e.putTime = now
|
||||
|
||||
if reply != nil {
|
||||
e.reply = *reply
|
||||
e.ReplyLen = reply.Len()
|
||||
}
|
||||
|
||||
return (prevValidUntil != e.validUntil)
|
||||
}
|
||||
|
||||
func (e *cacheEntry) String() string {
|
||||
var buf bytes.Buffer
|
||||
q := e.question
|
||||
fmt.Fprintf(&buf, "'%s'[%s]: ", q.Name, dns.TypeToString[q.Qtype])
|
||||
if e.Flags&CacheNoLocalReplies != 0 {
|
||||
fmt.Fprintf(&buf, "neg-local")
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "%s", statusToString[e.Status])
|
||||
}
|
||||
fmt.Fprintf(&buf, "(%d bytes)", e.ReplyLen)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// An entriesPtrHeap is a min-heap of cache entries.
|
||||
type entriesPtrsHeap []*cacheEntry
|
||||
|
||||
func (h entriesPtrsHeap) Len() int { return len(h) }
|
||||
func (h entriesPtrsHeap) Less(i, j int) bool { return h[i].validUntil.Before(h[j].validUntil) }
|
||||
func (h entriesPtrsHeap) Swap(i, j int) {
|
||||
h[i], h[j] = h[j], h[i]
|
||||
h[i].index = i
|
||||
h[j].index = j
|
||||
}
|
||||
|
||||
func (h *entriesPtrsHeap) Push(x interface{}) {
|
||||
// Push and Pop use pointer receivers because they modify the slice's length,
|
||||
// not just its contents.
|
||||
n := len(*h)
|
||||
entry := x.(*cacheEntry)
|
||||
entry.index = n
|
||||
*h = append(*h, entry)
|
||||
}
|
||||
|
||||
func (h *entriesPtrsHeap) Pop() interface{} {
|
||||
old := *h
|
||||
n := len(old)
|
||||
item := old[n-1]
|
||||
item.index = -1 // for safety
|
||||
*h = old[0 : n-1]
|
||||
return item
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The cache interface
|
||||
type ZoneCache interface {
|
||||
// Get from the cache
|
||||
Get(request *dns.Msg, maxLen int) (reply *dns.Msg, err error)
|
||||
// Put in the cache
|
||||
Put(request *dns.Msg, reply *dns.Msg, ttl int, flags uint8) int
|
||||
// Remove from the cache
|
||||
Remove(question *dns.Question)
|
||||
// Purge all expired entries from the cache
|
||||
Purge()
|
||||
// Remove all entries
|
||||
Clear()
|
||||
// Return the cache length
|
||||
Len() int
|
||||
// Return the max capacity
|
||||
Capacity() int
|
||||
}
|
||||
|
||||
type cacheKey dns.Question
|
||||
type entries map[cacheKey]*cacheEntry
|
||||
|
||||
// Cache is a thread-safe fixed capacity LRU cache.
|
||||
type Cache struct {
|
||||
capacity int
|
||||
entries entries
|
||||
entriesH entriesPtrsHeap // len(entriesH) <= len(entries), as pending entries can be in entries but not in entriesH
|
||||
clock clock.Clock
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewCache creates a cache of the given capacity
|
||||
func NewCache(capacity int, clk clock.Clock) (*Cache, error) {
|
||||
if capacity <= 0 {
|
||||
return nil, errInvalidCapacity
|
||||
}
|
||||
c := &Cache{
|
||||
capacity: capacity,
|
||||
entries: make(entries, capacity),
|
||||
clock: clk,
|
||||
}
|
||||
|
||||
if c.clock == nil {
|
||||
c.clock = clock.New()
|
||||
}
|
||||
|
||||
heap.Init(&c.entriesH)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Clear removes all the entries in the cache
|
||||
func (c *Cache) Clear() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.entries = make(entries, c.capacity)
|
||||
heap.Init(&c.entriesH)
|
||||
}
|
||||
|
||||
// Purge removes the old elements in the cache
|
||||
func (c *Cache) Purge() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
now := c.clock.Now()
|
||||
for i, entry := range c.entriesH {
|
||||
if entry.hasExpired(now) {
|
||||
heap.Remove(&c.entriesH, i)
|
||||
delete(c.entries, cacheKey(entry.question))
|
||||
} else {
|
||||
return // all remaining entries must be still valid...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a reply to the cache.
|
||||
// When `ttl` is equal to `nullTTL`, the cache entry will be valid until the closest TTL in the `reply`
|
||||
func (c *Cache) Put(request *dns.Msg, reply *dns.Msg, ttl int, flags uint8) int {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
now := c.clock.Now()
|
||||
question := request.Question[0]
|
||||
key := cacheKey(question)
|
||||
ent, found := c.entries[key]
|
||||
if found {
|
||||
updated := ent.setReply(reply, ttl, flags, now)
|
||||
if updated {
|
||||
heap.Fix(&c.entriesH, ent.index)
|
||||
}
|
||||
} else {
|
||||
// If we will add a new item and the capacity has been exceeded, make some room...
|
||||
if len(c.entriesH) >= c.capacity {
|
||||
lowestEntry := heap.Pop(&c.entriesH).(*cacheEntry)
|
||||
delete(c.entries, cacheKey(lowestEntry.question))
|
||||
}
|
||||
ent = newCacheEntry(&question, now)
|
||||
ent.setReply(reply, ttl, flags, now)
|
||||
heap.Push(&c.entriesH, ent)
|
||||
c.entries[key] = ent
|
||||
}
|
||||
return ent.ReplyLen
|
||||
}
|
||||
|
||||
// Look up for a question's reply from the cache.
|
||||
// If no reply is stored in the cache, it returns a `nil` reply and no error.
|
||||
func (c *Cache) Get(request *dns.Msg, maxLen int) (reply *dns.Msg, err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
now := c.clock.Now()
|
||||
question := request.Question[0]
|
||||
key := cacheKey(question)
|
||||
if ent, found := c.entries[key]; found {
|
||||
reply, err = ent.getReply(request, maxLen, now)
|
||||
if ent.hasExpired(now) {
|
||||
Debug.Printf("[cache msgid %d] expired: removing", request.MsgHdr.Id)
|
||||
if ent.index > 0 {
|
||||
heap.Remove(&c.entriesH, ent.index)
|
||||
}
|
||||
delete(c.entries, key)
|
||||
reply = nil
|
||||
}
|
||||
} else {
|
||||
// we are the first asking for this name: create an entry with no reply... the caller must wait
|
||||
Debug.Printf("[cache msgid %d] addind in pending state", request.MsgHdr.Id)
|
||||
c.entries[key] = newCacheEntry(&question, now)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Remove removes the provided question from the cache.
|
||||
func (c *Cache) Remove(question *dns.Question) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
key := cacheKey(*question)
|
||||
if entry, found := c.entries[key]; found {
|
||||
Debug.Printf("[cache] removing %s-response for '%s'", dns.TypeToString[question.Qtype], question.Name)
|
||||
if entry.index > 0 {
|
||||
heap.Remove(&c.entriesH, entry.index)
|
||||
}
|
||||
delete(c.entries, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the number of entries in the cache.
|
||||
func (c *Cache) Len() int {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
return len(c.entries)
|
||||
}
|
||||
|
||||
// Return the max capacity
|
||||
func (c *Cache) Capacity() int {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
return c.capacity
|
||||
}
|
||||
|
||||
// Return the max capacity
|
||||
func (c *Cache) String() string {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
var buf bytes.Buffer
|
||||
for _, entry := range c.entries {
|
||||
fmt.Fprintf(&buf, "%s\n", entry)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
// Check that the cache keeps its intended capacity constant
|
||||
func TestCacheLength(t *testing.T) {
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestCacheLength starting")
|
||||
|
||||
const cacheLen = 128
|
||||
|
||||
l, err := NewCache(cacheLen, nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
insTime := time.Now()
|
||||
|
||||
t.Logf("Inserting 256 questions in the cache at '%s', with TTL from 0 to 255", insTime)
|
||||
for i := 0; i < cacheLen*2; i++ {
|
||||
name := fmt.Sprintf("name%d", i)
|
||||
questionMsg := new(dns.Msg)
|
||||
questionMsg.SetQuestion(name, dns.TypeA)
|
||||
questionMsg.RecursionDesired = true
|
||||
|
||||
question := &questionMsg.Question[0]
|
||||
|
||||
ip := net.ParseIP(fmt.Sprintf("10.0.1.%d", i))
|
||||
records := []ZoneRecord{Record{name, ip, 0, 0, 0}}
|
||||
|
||||
reply := makeAddressReply(questionMsg, question, records)
|
||||
reply.Answer[0].Header().Ttl = uint32(i)
|
||||
|
||||
l.Put(questionMsg, reply, 0, 0)
|
||||
}
|
||||
|
||||
wt.AssertEqualInt(t, l.Len(), cacheLen, "cache length")
|
||||
|
||||
minExpectedTime := insTime.Add(time.Duration(cacheLen) * time.Second)
|
||||
t.Logf("Checking all remaining entries expire after insert_time + %d secs='%s'", cacheLen, minExpectedTime)
|
||||
for _, entry := range l.entries {
|
||||
if entry.validUntil.Before(minExpectedTime) {
|
||||
t.Fatalf("Entry valid until %s", entry.validUntil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the cache entries are ok
|
||||
func TestCacheEntries(t *testing.T) {
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestCacheEntries starting")
|
||||
|
||||
Info.Println("Checking cache consistency")
|
||||
|
||||
const cacheLen = 128
|
||||
clk := clock.NewMock()
|
||||
|
||||
l, err := NewCache(cacheLen, clk)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
questionMsg := new(dns.Msg)
|
||||
questionMsg.SetQuestion("some.name", dns.TypeA)
|
||||
questionMsg.RecursionDesired = true
|
||||
|
||||
question := &questionMsg.Question[0]
|
||||
|
||||
t.Logf("Trying to get a name")
|
||||
resp, err := l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
if resp != nil {
|
||||
t.Logf("Got\n%s", resp)
|
||||
t.Fatalf("ERROR: Did not expect a reponse from Get() yet")
|
||||
}
|
||||
t.Logf("Trying to get it again")
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
if resp != nil {
|
||||
t.Logf("Got\n%s", resp)
|
||||
t.Fatalf("ERROR: Did not expect a reponse from Get() yet")
|
||||
}
|
||||
|
||||
t.Logf("Inserting the reply")
|
||||
records := []ZoneRecord{Record{"some.name", net.ParseIP("10.0.1.1"), 0, 0, 0}}
|
||||
reply1 := makeAddressReply(questionMsg, question, records)
|
||||
l.Put(questionMsg, reply1, nullTTL, 0)
|
||||
|
||||
t.Logf("Checking we can Get() the reply now")
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertTrue(t, resp != nil, "reponse from Get()")
|
||||
t.Logf("Received '%s'", resp.Answer[0])
|
||||
wt.AssertType(t, resp.Answer[0], (*dns.A)(nil), "DNS record")
|
||||
ttlGet1 := resp.Answer[0].Header().Ttl
|
||||
|
||||
clk.Add(time.Duration(1) * time.Second)
|
||||
t.Logf("Checking that a second Get(), after 1 second, gets the same result, but with reduced TTL")
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertTrue(t, resp != nil, "reponse from a second Get()")
|
||||
t.Logf("Received '%s'", resp.Answer[0])
|
||||
wt.AssertType(t, resp.Answer[0], (*dns.A)(nil), "DNS record")
|
||||
ttlGet2 := resp.Answer[0].Header().Ttl
|
||||
wt.AssertEqualInt(t, int(ttlGet1-ttlGet2), 1, "TTL difference")
|
||||
|
||||
clk.Add(time.Duration(localTTL) * time.Second)
|
||||
t.Logf("Checking that a third Get(), after %d second, gets no result", localTTL)
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
if resp != nil {
|
||||
t.Logf("Got\n%s", resp)
|
||||
t.Fatalf("ERROR: Did NOT expect a reponse from the second Get()")
|
||||
}
|
||||
|
||||
t.Logf("Checking that an Remove() results in Get() returning nothing")
|
||||
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.9.9"), 0, 0, 0}}
|
||||
replyTemp := makeAddressReply(questionMsg, question, records)
|
||||
l.Put(questionMsg, replyTemp, nullTTL, 0)
|
||||
lenBefore := l.Len()
|
||||
l.Remove(question)
|
||||
wt.AssertEqualInt(t, l.Len(), lenBefore-1, "cache length")
|
||||
l.Remove(question) // do it again: should have no effect...
|
||||
wt.AssertEqualInt(t, l.Len(), lenBefore-1, "cache length")
|
||||
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertTrue(t, resp == nil, "reponse from the Get() after a Remove()")
|
||||
|
||||
t.Logf("Inserting a two replies for the same query")
|
||||
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.1.2"), 0, 0, 0}}
|
||||
reply2 := makeAddressReply(questionMsg, question, records)
|
||||
l.Put(questionMsg, reply2, nullTTL, 0)
|
||||
clk.Add(time.Duration(1) * time.Second)
|
||||
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.1.3"), 0, 0, 0}}
|
||||
reply3 := makeAddressReply(questionMsg, question, records)
|
||||
l.Put(questionMsg, reply3, nullTTL, 0)
|
||||
|
||||
t.Logf("Checking we get the last one...")
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertTrue(t, resp != nil, "reponse from the Get()")
|
||||
t.Logf("Received '%s'", resp.Answer[0])
|
||||
wt.AssertType(t, resp.Answer[0], (*dns.A)(nil), "DNS record")
|
||||
wt.AssertEqualString(t, resp.Answer[0].(*dns.A).A.String(), "10.0.1.3", "IP address")
|
||||
wt.AssertEqualInt(t, int(resp.Answer[0].Header().Ttl), int(localTTL), "TTL")
|
||||
|
||||
clk.Add(time.Duration(localTTL-1) * time.Second)
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertTrue(t, resp != nil, "reponse from the Get()")
|
||||
t.Logf("Received '%s'", resp.Answer[0])
|
||||
wt.AssertType(t, resp.Answer[0], (*dns.A)(nil), "DNS record")
|
||||
wt.AssertEqualString(t, resp.Answer[0].(*dns.A).A.String(), "10.0.1.3", "IP address")
|
||||
wt.AssertEqualInt(t, int(resp.Answer[0].Header().Ttl), 1, "TTL")
|
||||
|
||||
t.Logf("Checking we get empty replies when they are expired...")
|
||||
lenBefore = l.Len()
|
||||
clk.Add(time.Duration(localTTL) * time.Second)
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
if resp != nil {
|
||||
t.Logf("Got\n%s", resp.Answer[0])
|
||||
t.Fatalf("ERROR: Did NOT expect a reponse from the Get()")
|
||||
}
|
||||
wt.AssertEqualInt(t, l.Len(), lenBefore-1, "cache length (after getting an expired entry)")
|
||||
|
||||
questionMsg2 := new(dns.Msg)
|
||||
questionMsg2.SetQuestion("some.other.name", dns.TypeA)
|
||||
questionMsg2.RecursionDesired = true
|
||||
question2 := &questionMsg2.Question[0]
|
||||
|
||||
t.Logf("Trying to Get() a name")
|
||||
resp, err = l.Get(questionMsg2, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertNil(t, resp, "reponse from Get() yet")
|
||||
|
||||
t.Logf("Checking that an Remove() between Get() and Put() does not break things")
|
||||
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.9.9"), 0, 0, 0}}
|
||||
replyTemp2 := makeAddressReply(questionMsg2, question2, records)
|
||||
l.Remove(question2)
|
||||
l.Put(questionMsg2, replyTemp2, nullTTL, 0)
|
||||
resp, err = l.Get(questionMsg2, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertNotNil(t, resp, "reponse from Get()")
|
||||
|
||||
questionMsg3 := new(dns.Msg)
|
||||
questionMsg3.SetQuestion("some.other.name", dns.TypeA)
|
||||
questionMsg3.RecursionDesired = true
|
||||
question3 := &questionMsg3.Question[0]
|
||||
|
||||
t.Logf("Checking that a entry with CacheNoLocalReplies return an error")
|
||||
l.Put(questionMsg3, nil, negLocalTTL, CacheNoLocalReplies)
|
||||
resp, err = l.Get(questionMsg3, minUDPSize)
|
||||
wt.AssertNil(t, resp, "Get() response with CacheNoLocalReplies")
|
||||
wt.AssertTrue(t, err == errNoLocalReplies, "Get() error with CacheNoLocalReplies")
|
||||
|
||||
t.Logf("Checking that more Put's do not make the negative TTL longer")
|
||||
clk.Add(time.Duration(negLocalTTL/2) * time.Second)
|
||||
l.Put(questionMsg3, nil, negLocalTTL, CacheNoLocalReplies)
|
||||
clk.Add(time.Duration(1) * time.Second)
|
||||
l.Put(questionMsg3, nil, negLocalTTL, CacheNoLocalReplies)
|
||||
clk.Add(time.Duration(1) * time.Second)
|
||||
l.Put(questionMsg3, nil, negLocalTTL, CacheNoLocalReplies)
|
||||
resp, err = l.Get(questionMsg3, minUDPSize)
|
||||
wt.AssertNil(t, resp, "Get() response with CacheNoLocalReplies")
|
||||
wt.AssertTrue(t, err == errNoLocalReplies, "Get() error with CacheNoLocalReplies")
|
||||
|
||||
clk.Add(time.Duration(negLocalTTL/2) * time.Second)
|
||||
t.Logf("Checking that we get an expired response after %f seconds", negLocalTTL)
|
||||
resp, err = l.Get(questionMsg3, minUDPSize)
|
||||
wt.AssertNil(t, resp, "expired Get() response with CacheNoLocalReplies")
|
||||
wt.AssertNil(t, err, "expired Get() error with CacheNoLocalReplies")
|
||||
|
||||
l.Remove(question3)
|
||||
t.Logf("Checking that Put&Get with CacheNoLocalReplies with a Remove in the middle returns nothing")
|
||||
l.Put(questionMsg3, nil, nullTTL, CacheNoLocalReplies)
|
||||
l.Remove(question3)
|
||||
resp, err = l.Get(questionMsg3, minUDPSize)
|
||||
wt.AssertNil(t, resp, "Get() reponse with CacheNoLocalReplies")
|
||||
wt.AssertNil(t, err, "Get() error with CacheNoLocalReplies")
|
||||
}
|
||||
+60
-325
@@ -1,351 +1,86 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
const (
|
||||
topDomain = "."
|
||||
reverseDNSdomain = "in-addr.arpa."
|
||||
udpBuffSize = uint16(4096)
|
||||
minUDPSize = 512
|
||||
|
||||
DefaultListenAddress = "0.0.0.0:53"
|
||||
DefaultTTL = 1
|
||||
DefaultClientTimeout = 5 * time.Second
|
||||
localTTL uint32 = 30 // somewhat arbitrary; we don't expect anyone downstream to cache results
|
||||
negLocalTTL = 30 // TTL for negative local resolutions
|
||||
minUDPSize = 512
|
||||
maxUDPSize = 65535
|
||||
)
|
||||
|
||||
type Upstream interface {
|
||||
Config() (*dns.ClientConfig, error)
|
||||
func makeHeader(r *dns.Msg, q *dns.Question) *dns.RR_Header {
|
||||
return &dns.RR_Header{
|
||||
Name: q.Name, Rrtype: q.Qtype,
|
||||
Class: dns.ClassINET, Ttl: localTTL}
|
||||
}
|
||||
|
||||
func NewUpstream(resolvConf, filterAddress string) Upstream {
|
||||
return &upstream{
|
||||
resolvConf: resolvConf,
|
||||
filterAddress: filterAddress,
|
||||
cachedConfig: &dns.ClientConfig{}}
|
||||
}
|
||||
|
||||
type upstream struct {
|
||||
sync.Mutex
|
||||
|
||||
resolvConf string
|
||||
filterAddress string
|
||||
cachedConfig *dns.ClientConfig
|
||||
lastStat time.Time
|
||||
lastModified time.Time
|
||||
}
|
||||
|
||||
func (u *upstream) Config() (*dns.ClientConfig, error) {
|
||||
u.Lock()
|
||||
defer u.Unlock()
|
||||
|
||||
// If we checked less than five seconds ago return what we already have
|
||||
now := time.Now()
|
||||
if now.Sub(u.lastStat) < 5*time.Second {
|
||||
return u.cachedConfig, nil
|
||||
}
|
||||
u.lastStat = now
|
||||
|
||||
// Reread file if it has changed
|
||||
if fi, err := os.Stat(u.resolvConf); err != nil {
|
||||
return u.cachedConfig, err
|
||||
} else if fi.ModTime() != u.lastModified {
|
||||
if config, err := dns.ClientConfigFromFile(u.resolvConf); err == nil {
|
||||
config.Servers = filter(config.Servers, u.filterAddress)
|
||||
u.lastModified = fi.ModTime()
|
||||
u.cachedConfig = config
|
||||
} else {
|
||||
return u.cachedConfig, err
|
||||
}
|
||||
}
|
||||
|
||||
return u.cachedConfig, nil
|
||||
}
|
||||
|
||||
func filter(ss []string, s string) []string {
|
||||
for i := 0; i < len(ss); {
|
||||
if ss[i] == s {
|
||||
ss = append(ss[:i], ss[i+1:]...)
|
||||
continue
|
||||
}
|
||||
i++
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
type DNSServer struct {
|
||||
ns *Nameserver
|
||||
domain string
|
||||
ttl uint32
|
||||
address string
|
||||
|
||||
servers []*dns.Server
|
||||
upstream Upstream
|
||||
tcpClient *dns.Client
|
||||
udpClient *dns.Client
|
||||
}
|
||||
|
||||
func NewDNSServer(ns *Nameserver, domain, address string, upstream Upstream, ttl uint32, clientTimeout time.Duration) (*DNSServer, error) {
|
||||
s := &DNSServer{
|
||||
ns: ns,
|
||||
domain: dns.Fqdn(domain),
|
||||
ttl: ttl,
|
||||
address: address,
|
||||
upstream: upstream,
|
||||
tcpClient: &dns.Client{Net: "tcp", ReadTimeout: clientTimeout},
|
||||
udpClient: &dns.Client{Net: "udp", ReadTimeout: clientTimeout, UDPSize: udpBuffSize},
|
||||
}
|
||||
|
||||
err := s.listen(address)
|
||||
return s, err
|
||||
}
|
||||
|
||||
func (d *DNSServer) String() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "WeaveDNS (%s)\n", d.ns.ourName)
|
||||
fmt.Fprintf(&buf, " listening on %s, for domain %s\n", d.address, d.domain)
|
||||
fmt.Fprintf(&buf, " response ttl %d\n", d.ttl)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (d *DNSServer) listen(address string) error {
|
||||
udpListener, err := net.ListenPacket("udp", address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
udpServer := &dns.Server{PacketConn: udpListener, Handler: d.createMux(d.udpClient, minUDPSize)}
|
||||
|
||||
tcpListener, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
udpServer.Shutdown()
|
||||
return err
|
||||
}
|
||||
tcpServer := &dns.Server{Listener: tcpListener, Handler: d.createMux(d.tcpClient, -1)}
|
||||
|
||||
d.servers = []*dns.Server{udpServer, tcpServer}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSServer) ActivateAndServe() {
|
||||
for _, server := range d.servers {
|
||||
go func(server *dns.Server) {
|
||||
server.ActivateAndServe()
|
||||
}(server)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DNSServer) Stop() error {
|
||||
for _, server := range d.servers {
|
||||
if err := server.Shutdown(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
*DNSServer
|
||||
maxResponseSize int
|
||||
client *dns.Client
|
||||
}
|
||||
|
||||
func (d *DNSServer) createMux(client *dns.Client, defaultMaxResponseSize int) *dns.ServeMux {
|
||||
m := dns.NewServeMux()
|
||||
h := &handler{
|
||||
DNSServer: d,
|
||||
maxResponseSize: defaultMaxResponseSize,
|
||||
client: client,
|
||||
}
|
||||
m.HandleFunc(d.domain, h.handleLocal)
|
||||
m.HandleFunc(reverseDNSdomain, h.handleReverse)
|
||||
m.HandleFunc(topDomain, h.handleRecursive)
|
||||
func makeReply(r *dns.Msg, as []dns.RR) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.RecursionAvailable = true
|
||||
m.Answer = as
|
||||
return m
|
||||
}
|
||||
|
||||
func (h *handler) handleLocal(w dns.ResponseWriter, req *dns.Msg) {
|
||||
h.ns.debugf("local request: %+v", *req)
|
||||
if len(req.Question) != 1 {
|
||||
h.nameError(w, req)
|
||||
return
|
||||
}
|
||||
func makeTruncatedReply(r *dns.Msg) *dns.Msg {
|
||||
// for truncated response, we create a minimal reply with the Truncated bit set
|
||||
reply := new(dns.Msg)
|
||||
reply.SetReply(r)
|
||||
reply.Truncated = true
|
||||
return reply
|
||||
}
|
||||
|
||||
hostname := dns.Fqdn(req.Question[0].Name)
|
||||
if strings.Count(hostname, ".") == 1 {
|
||||
hostname = hostname + h.domain
|
||||
}
|
||||
|
||||
addrs := h.ns.Lookup(hostname)
|
||||
if len(addrs) == 0 {
|
||||
h.nameError(w, req)
|
||||
return
|
||||
}
|
||||
// Per RFC4074, if we have an A but another type was requested,
|
||||
// return 'no error' with empty answer section
|
||||
if req.Question[0].Qtype != dns.TypeA {
|
||||
h.respond(w, h.makeResponse(req, nil))
|
||||
return
|
||||
}
|
||||
|
||||
header := dns.RR_Header{
|
||||
Name: req.Question[0].Name,
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: h.ttl,
|
||||
}
|
||||
func makeAddressReply(r *dns.Msg, q *dns.Question, addrs []ZoneRecord) *dns.Msg {
|
||||
answers := make([]dns.RR, len(addrs))
|
||||
for i, addr := range addrs {
|
||||
ip := addr.IP4()
|
||||
answers[i] = &dns.A{Hdr: header, A: ip}
|
||||
}
|
||||
shuffleAnswers(&answers)
|
||||
header := makeHeader(r, q)
|
||||
count := 0
|
||||
for _, addr := range addrs {
|
||||
ip := addr.IP()
|
||||
ip4 := ip.To4()
|
||||
|
||||
h.respond(w, h.makeResponse(req, answers))
|
||||
}
|
||||
|
||||
func (h *handler) handleReverse(w dns.ResponseWriter, req *dns.Msg) {
|
||||
h.ns.debugf("reverse request: %+v", *req)
|
||||
if len(req.Question) != 1 || req.Question[0].Qtype != dns.TypePTR {
|
||||
h.nameError(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
ipStr := strings.TrimSuffix(strings.ToLower(req.Question[0].Name), "."+reverseDNSdomain)
|
||||
ip, err := address.ParseIP(ipStr)
|
||||
if err != nil {
|
||||
h.nameError(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
hostname, err := h.ns.ReverseLookup(ip.Reverse())
|
||||
if err != nil {
|
||||
h.handleRecursive(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
header := dns.RR_Header{
|
||||
Name: req.Question[0].Name,
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: h.ttl,
|
||||
}
|
||||
answers := []dns.RR{&dns.PTR{
|
||||
Hdr: header,
|
||||
Ptr: hostname,
|
||||
}}
|
||||
|
||||
h.respond(w, h.makeResponse(req, answers))
|
||||
}
|
||||
|
||||
func (h *handler) handleRecursive(w dns.ResponseWriter, req *dns.Msg) {
|
||||
h.ns.debugf("recursive request: %+v", *req)
|
||||
|
||||
// Resolve unqualified names locally
|
||||
if len(req.Question) == 1 {
|
||||
hostname := dns.Fqdn(req.Question[0].Name)
|
||||
if strings.Count(hostname, ".") == 1 {
|
||||
h.handleLocal(w, req)
|
||||
return
|
||||
switch q.Qtype {
|
||||
case dns.TypeA:
|
||||
if ip4 != nil {
|
||||
answers[count] = &dns.A{Hdr: *header, A: ip}
|
||||
count++
|
||||
}
|
||||
case dns.TypeAAAA:
|
||||
if ip4 == nil {
|
||||
answers[count] = &dns.AAAA{Hdr: *header, AAAA: ip}
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
upstreamConfig, err := h.upstream.Config()
|
||||
if err != nil {
|
||||
h.ns.errorf("unable to read upstream config: %s", err)
|
||||
}
|
||||
for _, server := range upstreamConfig.Servers {
|
||||
reqCopy := req.Copy()
|
||||
reqCopy.Id = dns.Id()
|
||||
response, _, err := h.client.Exchange(reqCopy, fmt.Sprintf("%s:%s", server, upstreamConfig.Port))
|
||||
if (err != nil && err != dns.ErrTruncated) || response == nil {
|
||||
h.ns.debugf("error trying %s: %v", server, err)
|
||||
continue
|
||||
}
|
||||
response.Id = req.Id
|
||||
if h.responseTooBig(req, response) {
|
||||
response.Compress = true
|
||||
}
|
||||
h.respond(w, response)
|
||||
return
|
||||
}
|
||||
|
||||
h.respond(w, h.makeErrorResponse(req, dns.RcodeServerFailure))
|
||||
return makeReply(r, answers[:count])
|
||||
}
|
||||
|
||||
func (h *handler) makeResponse(req *dns.Msg, answers []dns.RR) *dns.Msg {
|
||||
response := &dns.Msg{}
|
||||
response.SetReply(req)
|
||||
response.RecursionAvailable = true
|
||||
response.Authoritative = true
|
||||
response.Answer = answers
|
||||
if !h.responseTooBig(req, response) {
|
||||
return response
|
||||
func makePTRReply(r *dns.Msg, q *dns.Question, names []ZoneRecord) *dns.Msg {
|
||||
answers := make([]dns.RR, len(names))
|
||||
header := makeHeader(r, q)
|
||||
for i, name := range names {
|
||||
answers[i] = &dns.PTR{Hdr: *header, Ptr: name.Name()}
|
||||
}
|
||||
return makeReply(r, answers)
|
||||
}
|
||||
|
||||
// search for smallest i that is too big
|
||||
maxSize := h.getMaxResponseSize(req)
|
||||
i := sort.Search(len(answers), func(i int) bool {
|
||||
// return true if too big
|
||||
response.Answer = answers[:i+1]
|
||||
return response.Len() > maxSize
|
||||
})
|
||||
func makeDNSFailResponse(r *dns.Msg) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.RecursionAvailable = true
|
||||
m.Rcode = dns.RcodeNameError
|
||||
return m
|
||||
}
|
||||
|
||||
response.Answer = answers[:i]
|
||||
if i < len(answers) {
|
||||
response.Truncated = true
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func (h *handler) makeErrorResponse(req *dns.Msg, code int) *dns.Msg {
|
||||
response := &dns.Msg{}
|
||||
response.SetReply(req)
|
||||
response.RecursionAvailable = true
|
||||
response.Rcode = code
|
||||
return response
|
||||
}
|
||||
|
||||
func (h *handler) responseTooBig(req, response *dns.Msg) bool {
|
||||
return len(response.Answer) > 1 && h.maxResponseSize > 0 && response.Len() > h.getMaxResponseSize(req)
|
||||
}
|
||||
|
||||
func (h *handler) respond(w dns.ResponseWriter, response *dns.Msg) {
|
||||
h.ns.debugf("response: %+v", response)
|
||||
if err := w.WriteMsg(response); err != nil {
|
||||
h.ns.infof("error responding: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) nameError(w dns.ResponseWriter, req *dns.Msg) {
|
||||
h.respond(w, h.makeErrorResponse(req, dns.RcodeNameError))
|
||||
}
|
||||
|
||||
func (h *handler) getMaxResponseSize(req *dns.Msg) int {
|
||||
if opt := req.IsEdns0(); opt != nil {
|
||||
return int(opt.UDPSize())
|
||||
}
|
||||
return h.maxResponseSize
|
||||
}
|
||||
|
||||
func shuffleAnswers(answers *[]dns.RR) {
|
||||
if len(*answers) <= 1 {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range *answers {
|
||||
j := rand.Intn(i + 1)
|
||||
(*answers)[i], (*answers)[j] = (*answers)[j], (*answers)[i]
|
||||
// get the maximum UDP-reply length
|
||||
func getMaxReplyLen(r *dns.Msg, proto dnsProtocol) int {
|
||||
maxLen := minUDPSize
|
||||
if proto == protTCP {
|
||||
maxLen = maxUDPSize
|
||||
} else if opt := r.IsEdns0(); opt != nil {
|
||||
maxLen = int(opt.UDPSize())
|
||||
}
|
||||
return maxLen
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
type mockUpstream struct {
|
||||
config *dns.ClientConfig
|
||||
}
|
||||
|
||||
func (mu *mockUpstream) Config() (*dns.ClientConfig, error) {
|
||||
return mu.config, nil
|
||||
}
|
||||
|
||||
func startServer(t *testing.T, upstream *dns.ClientConfig) (*DNSServer, *Nameserver, int, int) {
|
||||
peername, err := mesh.PeerNameFromString("00:00:00:02:00:00")
|
||||
require.Nil(t, err)
|
||||
nameserver := New(peername, "", func(mesh.PeerName) bool { return true })
|
||||
dnsserver, err := NewDNSServer(nameserver, "weave.local.", "0.0.0.0:0", &mockUpstream{upstream}, 30, 5*time.Second)
|
||||
require.Nil(t, err)
|
||||
udpPort := dnsserver.servers[0].PacketConn.LocalAddr().(*net.UDPAddr).Port
|
||||
tcpPort := dnsserver.servers[1].Listener.Addr().(*net.TCPAddr).Port
|
||||
go dnsserver.ActivateAndServe()
|
||||
return dnsserver, nameserver, udpPort, tcpPort
|
||||
}
|
||||
|
||||
func TestTruncation(t *testing.T) {
|
||||
//common.SetLogLevel("debug")
|
||||
dnsserver, nameserver, udpPort, tcpPort := startServer(t, nil)
|
||||
defer dnsserver.Stop()
|
||||
|
||||
// Add 100 mappings to nameserver
|
||||
addrs := []address.Address{}
|
||||
for i := address.Address(0); i < 100; i++ {
|
||||
addrs = append(addrs, i)
|
||||
nameserver.AddEntry("foo.weave.local.", "", mesh.UnknownPeerName, i)
|
||||
}
|
||||
|
||||
doRequest := func(client *dns.Client, request *dns.Msg, port int, expectedErr error) *dns.Msg {
|
||||
request.SetQuestion("foo.weave.local.", dns.TypeA)
|
||||
response, _, err := client.Exchange(request, fmt.Sprintf("127.0.0.1:%d", port))
|
||||
require.Equal(t, expectedErr, err)
|
||||
return response
|
||||
}
|
||||
|
||||
// do a udp query, ensure we get a truncated response
|
||||
{
|
||||
udpClient := dns.Client{Net: "udp", UDPSize: minUDPSize}
|
||||
response := doRequest(&udpClient, &dns.Msg{}, udpPort, dns.ErrTruncated)
|
||||
require.True(t, response.MsgHdr.Truncated)
|
||||
require.True(t, len(response.Answer) < 100)
|
||||
}
|
||||
|
||||
// do a udp query with big size, ensure we don't get a truncated response
|
||||
{
|
||||
udpClient := dns.Client{Net: "udp", UDPSize: 65535}
|
||||
request := &dns.Msg{}
|
||||
request.SetEdns0(65535, false)
|
||||
response := doRequest(&udpClient, request, udpPort, nil)
|
||||
require.False(t, response.MsgHdr.Truncated)
|
||||
require.Equal(t, len(response.Answer), 100)
|
||||
}
|
||||
|
||||
// do a tcp query, ensure we don't get a truncated response
|
||||
{
|
||||
tcpClient := dns.Client{Net: "tcp"}
|
||||
response := doRequest(&tcpClient, &dns.Msg{}, tcpPort, nil)
|
||||
require.False(t, response.MsgHdr.Truncated)
|
||||
require.Equal(t, len(response.Answer), 100)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncateResponse(t *testing.T) {
|
||||
|
||||
header := dns.RR_Header{
|
||||
Name: "host.domain.com",
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 30,
|
||||
}
|
||||
|
||||
for i := 0; i < 10000; i++ {
|
||||
// generate a random answer set
|
||||
numAnswers := 40 + rand.Intn(200)
|
||||
answers := make([]dns.RR, numAnswers)
|
||||
for j := 0; j < numAnswers; j++ {
|
||||
answers[j] = &dns.A{Hdr: header, A: address.Address(j).IP4()}
|
||||
}
|
||||
|
||||
// pick a random max size, truncate response to that, check it
|
||||
maxSize := 512 + rand.Intn(2*512)
|
||||
h := handler{maxResponseSize: maxSize}
|
||||
response := h.makeResponse(&dns.Msg{}, answers)
|
||||
require.True(t, response.Len() <= maxSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecursiveCompress(t *testing.T) {
|
||||
const (
|
||||
hostname = "foo.example."
|
||||
maxSize = 512
|
||||
)
|
||||
|
||||
// Construct a response that is >512 when uncompressed, <512 when compressed
|
||||
response := dns.Msg{}
|
||||
response.Authoritative = true
|
||||
response.Answer = []dns.RR{}
|
||||
header := dns.RR_Header{
|
||||
Name: hostname,
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 10,
|
||||
}
|
||||
for response.Len() <= maxSize {
|
||||
ip := address.Address(rand.Uint32()).IP4()
|
||||
response.Answer = append(response.Answer, &dns.A{Hdr: header, A: ip})
|
||||
}
|
||||
response.Compress = true
|
||||
require.True(t, response.Len() <= maxSize)
|
||||
|
||||
// A dns server that returns the above response
|
||||
var gotRequest = make(chan struct{}, 1)
|
||||
handleRecursive := func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
gotRequest <- struct{}{}
|
||||
require.Equal(t, req.Question[0].Name, hostname)
|
||||
response.SetReply(req)
|
||||
err := w.WriteMsg(&response)
|
||||
require.Nil(t, err)
|
||||
}
|
||||
mux := dns.NewServeMux()
|
||||
mux.HandleFunc(topDomain, handleRecursive)
|
||||
udpListener, err := net.ListenPacket("udp", "0.0.0.0:0")
|
||||
require.Nil(t, err)
|
||||
udpServer := &dns.Server{PacketConn: udpListener, Handler: mux}
|
||||
udpServerPort := udpListener.LocalAddr().(*net.UDPAddr).Port
|
||||
go udpServer.ActivateAndServe()
|
||||
defer udpServer.Shutdown()
|
||||
|
||||
// The weavedns server, pointed at the above server
|
||||
dnsserver, _, udpPort, _ := startServer(t, &dns.ClientConfig{
|
||||
Servers: []string{"127.0.0.1"},
|
||||
Port: strconv.Itoa(udpServerPort),
|
||||
Ndots: 1,
|
||||
Timeout: 5,
|
||||
Attempts: 2,
|
||||
})
|
||||
defer dnsserver.Stop()
|
||||
|
||||
// Now do lookup, check its what we expected.
|
||||
// NB this doesn't really test golang's resolver behaves correctly, as I can't see
|
||||
// a way to point golangs resolver at a specific hosts.
|
||||
req := new(dns.Msg)
|
||||
req.Id = dns.Id()
|
||||
req.RecursionDesired = true
|
||||
req.Question = make([]dns.Question, 1)
|
||||
req.Question[0] = dns.Question{
|
||||
Name: hostname,
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
}
|
||||
c := new(dns.Client)
|
||||
res, _, err := c.Exchange(req, fmt.Sprintf("127.0.0.1:%d", udpPort))
|
||||
require.Nil(t, err)
|
||||
require.True(t, len(gotRequest) > 0)
|
||||
require.True(t, res.Len() > maxSize)
|
||||
}
|
||||
@@ -1,304 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
var now = func() int64 { return time.Now().Unix() }
|
||||
|
||||
type Entry struct {
|
||||
ContainerID string
|
||||
Origin mesh.PeerName
|
||||
Addr address.Address
|
||||
Hostname string // as supplied
|
||||
lHostname string // lowercased (not exported, so not encoded by gob)
|
||||
Version int
|
||||
Tombstone int64 // timestamp of when it was deleted
|
||||
}
|
||||
|
||||
type Entries []Entry
|
||||
type CaseSensitive Entries
|
||||
type CaseInsensitive Entries
|
||||
type SortableEntries interface {
|
||||
sort.Interface
|
||||
Get(i int) Entry
|
||||
}
|
||||
|
||||
// Gossip messages are sorted in a case sensitive order...
|
||||
func (es CaseSensitive) Len() int { return len(es) }
|
||||
func (es CaseSensitive) Swap(i, j int) { es[i], es[j] = es[j], es[i] }
|
||||
func (es CaseSensitive) Get(i int) Entry { return es[i] }
|
||||
func (es CaseSensitive) Less(i, j int) bool { return es[i].less(&es[j]) }
|
||||
|
||||
// ... but we store entries in a case insensitive order.
|
||||
func (es CaseInsensitive) Len() int { return len(es) }
|
||||
func (es CaseInsensitive) Swap(i, j int) { es[i], es[j] = es[j], es[i] }
|
||||
func (es CaseInsensitive) Get(i int) Entry { return es[i] }
|
||||
func (es CaseInsensitive) Less(i, j int) bool { return es[i].insensitiveLess(&es[j]) }
|
||||
|
||||
func (e1 Entry) equal(e2 Entry) bool {
|
||||
return e1.ContainerID == e2.ContainerID &&
|
||||
e1.Origin == e2.Origin &&
|
||||
e1.Addr == e2.Addr &&
|
||||
e1.Hostname == e2.Hostname
|
||||
}
|
||||
|
||||
func (e1 *Entry) less(e2 *Entry) bool {
|
||||
// Entries are kept sorted by Hostname, Origin, ContainerID then address
|
||||
switch {
|
||||
case e1.Hostname != e2.Hostname:
|
||||
return e1.Hostname < e2.Hostname
|
||||
|
||||
case e1.Origin != e2.Origin:
|
||||
return e1.Origin < e2.Origin
|
||||
|
||||
case e1.ContainerID != e2.ContainerID:
|
||||
return e1.ContainerID < e2.ContainerID
|
||||
|
||||
default:
|
||||
return e1.Addr < e2.Addr
|
||||
}
|
||||
}
|
||||
|
||||
func (e1 *Entry) insensitiveLess(e2 *Entry) bool {
|
||||
// Entries are kept sorted by Hostname, Origin, ContainerID then address
|
||||
e1Hostname, e2Hostname := e1.lHostname, e2.lHostname
|
||||
switch {
|
||||
case e1Hostname != e2Hostname:
|
||||
return e1Hostname < e2Hostname
|
||||
|
||||
case e1.Origin != e2.Origin:
|
||||
return e1.Origin < e2.Origin
|
||||
|
||||
case e1.ContainerID != e2.ContainerID:
|
||||
return e1.ContainerID < e2.ContainerID
|
||||
|
||||
default:
|
||||
return e1.Addr < e2.Addr
|
||||
}
|
||||
}
|
||||
|
||||
// returns true to indicate a change
|
||||
func (e1 *Entry) merge(e2 *Entry) bool {
|
||||
// we know container id, origin, add and hostname are equal
|
||||
if e2.Version > e1.Version {
|
||||
e1.Version = e2.Version
|
||||
e1.Tombstone = e2.Tombstone
|
||||
return true
|
||||
} else if e2.Version == e1.Version && e2.Tombstone > e1.Tombstone {
|
||||
e1.Tombstone = e2.Tombstone
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e1 *Entry) String() string {
|
||||
return fmt.Sprintf("%s -> %s", e1.Hostname, e1.Addr.String())
|
||||
}
|
||||
|
||||
func (e1 *Entry) addLowercase() {
|
||||
e1.lHostname = strings.ToLower(e1.Hostname)
|
||||
}
|
||||
|
||||
func (e1 *Entry) tombstone() bool {
|
||||
if e1.Tombstone > 0 {
|
||||
return false
|
||||
}
|
||||
e1.Tombstone = now()
|
||||
e1.Version++
|
||||
return true
|
||||
}
|
||||
|
||||
func check(es SortableEntries) error {
|
||||
if !sort.IsSorted(es) {
|
||||
return fmt.Errorf("Not sorted")
|
||||
}
|
||||
for i := 1; i < es.Len(); i++ {
|
||||
if es.Get(i).equal(es.Get(i - 1)) {
|
||||
return fmt.Errorf("Duplicate entry: %d:%v and %d:%v", i-1, es.Get(i-1), i, es.Get(i))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkAndPanic(es SortableEntries) {
|
||||
if err := check(es); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (es *Entries) checkAndPanic() *Entries {
|
||||
checkAndPanic(CaseInsensitive(*es))
|
||||
return es
|
||||
}
|
||||
|
||||
func (es *Entries) add(hostname, containerid string, origin mesh.PeerName, addr address.Address) Entry {
|
||||
defer es.checkAndPanic().checkAndPanic()
|
||||
|
||||
entry := Entry{Hostname: hostname, lHostname: strings.ToLower(hostname),
|
||||
Origin: origin, ContainerID: containerid, Addr: addr}
|
||||
i := sort.Search(len(*es), func(i int) bool {
|
||||
return !(*es)[i].insensitiveLess(&entry)
|
||||
})
|
||||
if i < len(*es) && (*es)[i].equal(entry) {
|
||||
if (*es)[i].Tombstone > 0 {
|
||||
(*es)[i].Tombstone = 0
|
||||
(*es)[i].Version++
|
||||
}
|
||||
} else {
|
||||
*es = append(*es, Entry{})
|
||||
copy((*es)[i+1:], (*es)[i:])
|
||||
(*es)[i] = entry
|
||||
}
|
||||
return (*es)[i]
|
||||
}
|
||||
|
||||
func (es *Entries) merge(incoming Entries) Entries {
|
||||
defer es.checkAndPanic().checkAndPanic()
|
||||
incoming.checkAndPanic()
|
||||
|
||||
newEntries := Entries{}
|
||||
i := 0
|
||||
|
||||
for _, entry := range incoming {
|
||||
for i < len(*es) && (*es)[i].insensitiveLess(&entry) {
|
||||
i++
|
||||
}
|
||||
if i < len(*es) && (*es)[i].equal(entry) {
|
||||
if (*es)[i].merge(&entry) {
|
||||
newEntries = append(newEntries, entry)
|
||||
}
|
||||
} else {
|
||||
*es = append(*es, Entry{})
|
||||
copy((*es)[i+1:], (*es)[i:])
|
||||
(*es)[i] = entry
|
||||
newEntries = append(newEntries, entry)
|
||||
}
|
||||
}
|
||||
|
||||
return newEntries
|
||||
}
|
||||
|
||||
// f returning true means keep the entry.
|
||||
func (es *Entries) tombstone(ourname mesh.PeerName, f func(*Entry) bool) Entries {
|
||||
defer es.checkAndPanic().checkAndPanic()
|
||||
|
||||
tombstoned := Entries{}
|
||||
for i, e := range *es {
|
||||
if f(&e) && e.Origin == ourname && e.tombstone() {
|
||||
(*es)[i] = e
|
||||
tombstoned = append(tombstoned, e)
|
||||
}
|
||||
}
|
||||
return tombstoned
|
||||
}
|
||||
|
||||
// note f() may only modify entries such that they remain in order defined by less()
|
||||
func (es *Entries) filter(f func(*Entry) bool) {
|
||||
defer es.checkAndPanic().checkAndPanic()
|
||||
|
||||
i := 0
|
||||
for _, e := range *es {
|
||||
if !f(&e) {
|
||||
continue
|
||||
}
|
||||
(*es)[i] = e
|
||||
i++
|
||||
}
|
||||
*es = (*es)[:i]
|
||||
}
|
||||
|
||||
func (es Entries) findEqual(e *Entry) (*Entry, bool) {
|
||||
i := sort.Search(len(es), func(i int) bool {
|
||||
return !es[i].insensitiveLess(e)
|
||||
})
|
||||
if i < len(es) && es[i].equal(*e) {
|
||||
return &es[i], true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (es Entries) lookup(hostname string) Entries {
|
||||
es.checkAndPanic()
|
||||
|
||||
lowerHostname := strings.ToLower(hostname)
|
||||
i := sort.Search(len(es), func(i int) bool {
|
||||
return es[i].lHostname >= lowerHostname
|
||||
})
|
||||
if i >= len(es) || es[i].lHostname != lowerHostname {
|
||||
return Entries{}
|
||||
}
|
||||
|
||||
j := sort.Search(len(es)-i, func(j int) bool {
|
||||
return es[i+j].lHostname > lowerHostname
|
||||
})
|
||||
|
||||
return es[i : i+j]
|
||||
}
|
||||
|
||||
func (es Entries) first(f func(*Entry) bool) (*Entry, error) {
|
||||
es.checkAndPanic()
|
||||
|
||||
for _, e := range es {
|
||||
if f(&e) {
|
||||
return &e, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Not found")
|
||||
}
|
||||
|
||||
func (es Entries) addLowercase() {
|
||||
for i := range es {
|
||||
es[i].addLowercase()
|
||||
}
|
||||
}
|
||||
|
||||
type GossipData struct {
|
||||
Timestamp int64
|
||||
Entries
|
||||
}
|
||||
|
||||
func (g *GossipData) Merge(o mesh.GossipData) mesh.GossipData {
|
||||
other := o.(*GossipData)
|
||||
gossip := g.copy()
|
||||
gossip.Entries.merge(other.Entries)
|
||||
if gossip.Timestamp < other.Timestamp {
|
||||
gossip.Timestamp = other.Timestamp
|
||||
}
|
||||
return gossip
|
||||
}
|
||||
|
||||
func (g *GossipData) Decode(msg []byte) error {
|
||||
if err := gob.NewDecoder(bytes.NewReader(msg)).Decode(g); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.Entries.addLowercase() // lowercase strings not sent on the wire
|
||||
sort.Sort(CaseInsensitive(g.Entries))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GossipData) Encode() [][]byte {
|
||||
g2 := g.copy()
|
||||
sort.Sort(CaseSensitive(g2.Entries))
|
||||
buf := &bytes.Buffer{}
|
||||
if err := gob.NewEncoder(buf).Encode(g2); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return [][]byte{buf.Bytes()}
|
||||
}
|
||||
|
||||
func (g *GossipData) copy() *GossipData {
|
||||
g2 := &GossipData{Timestamp: g.Timestamp, Entries: make(Entries, len(g.Entries))}
|
||||
copy(g2.Entries, g.Entries)
|
||||
return g2
|
||||
}
|
||||
Loaded 100 of 3793 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user