Compare commits

...
1397 Commits
Author SHA1 Message Date
Bryan Boreham 2884b6deca Add release v1.1.2 2015-10-20 17:06:14 +01:00
Matthias Radestock 44255862a9 prevent spurious errors from showing up
silly mistake
2015-10-15 18:12:56 +01:00
Matthias Radestock 379048d8fc make test/setup.sh work under go 1.5
Fixes #1543.
2015-10-15 17:40:51 +01:00
Adam Harrison dea1405d71 Remove hardcoded docker bridge IP from proxy test 2015-10-15 14:39:56 +01:00
David Wragg 6edd430538 Use consistent options for all "go get"/"go build" invocations
Without this, building with go-1.5 fails with

    go get ./prog/weavewait
    go install net: open /usr/local/go/pkg/linux_amd64/net.a: permission denied

This makes the options passed too "go get"/"go build" consistent for
all executables. So it should be safe with go-1.4.

Conflicts:
	Makefile
2015-10-15 14:16:33 +01:00
Matthias Radestock 0d2ff55c07 Merge pull request #1535 from weaveworks/1523-rewrite-inspect-failure
[proxy] only attempt to rewrite successful inspects

Fixes #1523.
2015-10-15 13:04:34 +01:00
Paul Bellamy 36de840c91 [proxy] only attempt to rewrite successful inspects 2015-10-14 11:28:43 +01:00
Matthias Radestock 0ef548b9c9 prevent dropping of large multicast packets
Fixes #1507.
2015-10-06 16:25:02 +01:00
Ilya Dmitrichenko 7743f0e05f Merge branch 'latest_release_doc_updates' into 1.1 2015-10-06 15:26:50 +01:00
Ilya Dmitrichenko a1581ed5b5 Remove stale/invalid flag 2015-10-06 15:26:14 +01:00
Ilya Dmitrichenko a6c30088ab Merge branch 'latest_release_doc_updates' into 1.1 2015-10-06 15:24:08 +01:00
Ilya Dmitrichenko fdbe950483 Fix #1509 2015-10-06 15:21:24 +01:00
Matthias Radestock 2f86326405 Merge tag 'v1.1.1' into latest_release_doc_updates
Release v1.1.1
2015-10-06 15:13:50 +01:00
Matthias Radestock 6c4ea7a957 unswap src and dst ip in "too big DF broadcast" warning
and some whitespace cosmetics.

Fixes #1508.
2015-10-05 19:56:59 +01:00
Matthias Radestock de95859e0d add release v1.1.1 2015-10-05 11:26:24 +01:00
David Wragg 8fe0228d4d Merge 1364-ip-link-kernel-4.2 (PR #1504)
strip off @... from interface names returned by `ip link show`
2015-10-04 20:23:43 +01:00
Matthias Radestock 37ea8a28ad strip off @... from interface names returned by ip link show
Apparently kernel 4.2 adds these, which breaks `weave reset` when we
pass the names to `ip link del'.

Fixes #1364.
2015-10-04 15:43:55 +01:00
Matthias Radestock dac932f553 fix multi-ip attach
This got broken in f7dfe9d
2015-10-03 17:09:25 +01:00
Matthias Radestock d798b0cabb Merge pull request #1476 from weaveworks/1455-mount-var-run
unmount /var/run after boot, so the kernel can GC veth pairs

Fixes #1455.
2015-10-01 11:55:24 +01:00
Paul Bellamy 29bfd77855 [proxy] don't mount /var/run into the proxy, so the kernel can GC veth pairs 2015-10-01 11:43:13 +01:00
Tom Wilkie 0b7af28c86 Typo in Makefile. 2015-10-01 08:21:55 +00:00
Matthias Radestock 526259ef8e Merge pull request #1487 from weaveworks/1480-dns-test-race
Fix races in TestRecursiveCompress

Fixes #1480.
2015-10-01 08:11:48 +01:00
Matthias Radestock 3795c14931 better 2015-09-30 22:57:10 +01:00
Matthias Radestock 3e5cdd8bf2 refactor: remove pointless NXDOMAIN tests 2015-09-30 22:42:27 +01:00
Matthias Radestock 35058a1dde refactor: put negative DNS queries in one test suite 2015-09-30 22:38:48 +01:00
Tom Wilkie 11d93b1883 Fix races in TestRecursiveCompress 2015-09-30 15:51:07 +00:00
Tom Wilkie f86f392641 Merge pull request #1486 from weaveworks/1482-disable-160
Disable 160_claim test.
2015-09-30 16:20:34 +01:00
Matthias Radestock cd8befe43e Merge pull request #1485 from weaveworks/1450-check-slice
Sanity-check gossip messages
2015-09-30 15:41:21 +01:00
Tom Wilkie db46204ff9 Disable 160_claim test. 2015-09-30 14:22:24 +00:00
Bryan Boreham d2290cc82c Sanity-check gossip messages 2015-09-30 14:39:27 +01:00
Matthias Radestock 63e3f441d8 cosmetic: link to relevant RFC section for ARP announcements 2015-09-24 12:07:12 +01:00
Tom Wilkie 99ae38c9a6 Merge pull request #1467 from weaveworks/docker-machine
Update for docker/machine PR #1729
2015-09-24 11:53:21 +08:00
Tom Wilkie 011f96c6a7 New test image using docker 1.8 2015-09-24 03:42:44 +00:00
Tom Wilkie c7b4136d31 Update for docker/machine PR #1729 2015-09-24 03:25:30 +00:00
Matthias Radestock b1d453a0c2 Merge pull request #1460 from weaveworks/simplify-ipam-cidrs
Simplify ipam_cidrs function in weave script
2015-09-22 21:17:42 +01:00
Bryan Boreham a51f63267a Merge 0-args case in ipam_cidrs() and simplify loop 2015-09-22 21:06:12 +01:00
Matthias Radestock 7f74ae7912 Merge pull request #1426 from weaveworks/1418-clean-cancel
Handle cancelled allocation in 'weave attach'

Fixes #1418.
2015-09-22 16:27:27 +01:00
Bryan Boreham abf13c7026 Simplify detection of issues 2015-09-22 16:14:14 +01:00
Bryan Boreham 98b80c59d9 Detect cancelled allocation more cleanly
to avoid the weave script trying to parse an error message as a CIDR
2015-09-16 14:14:40 +01:00
Bryan Boreham 1beaf79e47 Refactor: extract duplicated code into helper functions 2015-09-16 14:14:30 +01:00
Matthias Radestock 8e656c1fd7 Merge pull request #1429 from weaveworks/1424-fix-cancel
Use explicit index into operations slice in cancelOpsFor()

Fixes #1424
2015-09-15 16:45:11 +01:00
Bryan Boreham d55c68c248 Review feedback 2015-09-15 16:18:37 +01:00
Matthias Radestock 8ec3ab5e28 Merge pull request #1435 from weaveworks/update-vagrantfile-docker-repos
Update docker repo, keys and package name
2015-09-15 15:49:33 +01:00
Matthias Radestock 58a30f7282 Merge branch 'latest_release_doc_updates' into 1.1 2015-09-15 15:44:15 +01:00
Matthias Radestock 2b5c2874ab remove 'lxc-docker' reference from build-on-ubuntu instructions
...since the Docker docs have been updated to install docker-engine.
2015-09-15 15:34:44 +01:00
Matthias Radestock bed11f4214 Merge pull request #1431 from weaveworks/issues/1397-strip-version-prefix
Automatically strip 'v' prefix for mainline and branch releases

Fixes #1397.
2015-09-15 13:15:49 +01:00
Adam Harrison bbf7646751 Update docker repo, keys and package name 2015-09-15 11:33:53 +01:00
Adam Harrison 6dfefb95e8 Advise installation of weaveworks github-release fork 2015-09-15 10:47:43 +01:00
Adam Harrison 44c2861aba Update release process doc 2015-09-15 10:41:41 +01:00
Adam Harrison 3039046a6d Strip v prefix for mainline and branch releases 2015-09-15 10:41:24 +01:00
Bryan Boreham a7e0f03647 Use explicit index into operations slice in cancelOpsFor()
to avoid mis-indexing when more than one op is removed
2015-09-14 17:02:35 +01:00
Bryan Boreham 5635ba1fd0 Update out-dated error message 2015-09-14 16:31:37 +01:00
Adam Harrison dd0eee6418 Merge pull request #1411 from weaveworks/1405-arping-dynamic-attach-1
always arp_update new addresses in 'attach'
2015-09-14 14:20:21 +01:00
Matthias Radestock 83ef93f3fb detect running proxy regardless of version
Fixes #1413.
2015-09-11 14:06:44 +01:00
Matthias Radestock f7dfe9dd23 always arp_update new addresses in 'attach'
Previously we'd only do that when the container had no ethwe at the
start. Therefore adding a recycled IP address to an already attached
container would not send out an arp update for that IP, which would
break connectivity to that IP for containers which had previously
accessed that IP.

Fixes #1405.
2015-09-10 18:43:49 +01:00
Adam Harrison ac145052d6 Correct weave stop advice
Fixes #1408.
2015-09-10 17:23:33 +01:00
Matthias Radestock 7a41337d17 Merge branch 'latest_release_doc_updates' into 1.1 2015-09-09 20:06:14 +01:00
Matthias Radestock 2d006dffaf add link to latest release to docs homepage
Closes #1400.
2015-09-09 20:04:37 +01:00
Matthias Radestock 69c935a6e8 Make latest_release description link to real release
Closes #1399.
2015-09-09 19:52:08 +01:00
Adam Harrison 676127893f Escape double curly braces eaten by Jekyll/Liquid 2015-09-09 14:21:31 +01:00
Adam Harrison df62d512cd Correct weave report example in troubleshooting guide 2015-09-09 14:00:53 +01:00
Adam Harrison 64e3cf04cc Add release v1.1.0 2015-09-07 16:43:03 +01:00
Adam Harrison 4f9185e3fe Merge branch 'weave_1.1_readme_updates' 2015-09-07 16:38:28 +01:00
Adam Harrison 4dbccea5df Add missing quotes 2015-09-07 16:38:02 +01:00
Adam Harrison a412268154 Merge branch '1.0' 2015-09-07 15:59:17 +01:00
Matthias Radestock d79ac3c12c Merge pull request #1387 from weaveworks/issues/1383-switch-release-behaviour-on-type
Only update DockerHub `latest` images on mainline release

Fixes #1383.
2015-09-07 12:53:07 +01:00
Matthias Radestock 85c309eee0 fix broken link 2015-09-05 16:04:21 +01:00
Matthias Radestock f782fb569f Merge pull request #1391 from weaveworks/1390-use-number
Stop marshalling numbers as floats

Fixes #1390.
2015-09-04 20:15:58 +01:00
Bryan Boreham c50968a0a4 Stop marshalling numbers as floats 2015-09-04 20:01:31 +01:00
Matthias Radestock 99b482b363 Merge pull request #1384 from weaveworks/dns-hostname-from-labels
[proxy] Add --hostname-from-label

Closes #1381.
2015-09-04 17:14:44 +01:00
Alfonso Acosta ce5dd847e9 [proxy] Add --hostname-from-label
This allows registering container hostnames based on a label provided when
creating the container.
2015-09-04 17:02:07 +01:00
Adam Harrison 5c0383059e Update documentation 2015-09-04 15:45:27 +01:00
Adam Harrison 6f672f9bc1 Infer release type
Script adjusts its behaviour based on the format of the version tag.
2015-09-04 15:44:48 +01:00
Matthias Radestock 32a82f2d0e Merge pull request #1376 from weaveworks/1199-proxy-kubernetes
[proxy] add --rewrite-inspect flag

Fixes #117. Fixes #1199.
2015-09-04 14:27:16 +01:00
Matthias Radestock 633df0cdaf refactor: extract docker API endpoint regexp creation 2015-09-04 13:40:07 +01:00
Paul Bellamy 32a12578f3 Don't use go-dockerclient to en/decode inspects in the proxy 2015-09-04 13:40:07 +01:00
Paul Bellamy 11b655154c "Clarifying" proxy --rewrite-inspect documentation 2015-09-04 13:40:07 +01:00
Paul Bellamy 19ceb424c3 [proxy] refactoring out common marshalling logic 2015-09-04 13:40:07 +01:00
Paul Bellamy e21a204eed [proxy] add --rewrite-inspect flag
To return the weave ip/mac for docker inspect calls.
2015-09-04 13:40:06 +01:00
Bryan Boreham 145a527626 Merge pull request #1379 from awh/issues/1369-weave-dns-add-remove-optional-ip
LGTM.  Fixes #1369
2015-09-04 12:13:38 +01:00
Paul Bellamy b33bbdb3f8 Merge commit '704e6a8' 2015-09-04 10:41:27 +01:00
Paul Bellamy 704e6a8676 errcheck says I should handle this case 2015-09-04 10:40:47 +01:00
Matthias Radestock 4c6a8f29c0 Merge pull request #1372 from weaveworks/issues/880-draft-releases
Support draft and pre-releases in `bin/release`

Fixes #880. Fixes #909.
2015-09-03 15:54:07 +01:00
Adam Harrison 5dfe1d28ff Add release 1.0.3 2015-09-03 15:02:57 +01:00
Adam Harrison 5744ed8994 Document release process 2015-09-03 13:01:29 +01:00
Matthias Radestock c71d9b126f Merge pull request #1380 from weaveworks/598-update-docs
Update docs for mixed manual and automatic IP allocation

Missed from #1200.
2015-09-03 12:27:24 +01:00
Bryan Boreham 5b437d2199 Update docs for mixed manual and automatic IP allocation 2015-09-03 12:17:31 +01:00
Adam Harrison 0125e67b35 Implement smoke test 2015-09-03 09:59:37 +01:00
Adam Harrison 1472e35011 Update documentation 2015-09-03 09:59:37 +01:00
Adam Harrison 58ea422ea2 Optional address argument to dns-add/dns-remove 2015-09-02 17:10:48 +01:00
Adam Harrison 3097565664 Merge branch '1.0'
Conflicts:
	site/weavedns.md
2015-09-02 17:09:39 +01:00
Adam Harrison 81134b408d Merge branch 'latest_release_doc_updates' into 1.0 2015-09-02 17:07:48 +01:00
Adam Harrison 26fdd5da03 Remove misplaced sentence 2015-09-02 17:06:23 +01:00
Adam Harrison 6a5df80bda Fix latest_release hash test
Obtain the version and latest_release tag commit hashes before changing
into the cloned repository so that all tagging and pushing happens on
the outer repository.
2015-09-02 14:13:11 +01:00
Adam Harrison b13baccbd2 Introduce draft step
The publish stage has been split into two pieces - a draft step that
creates a draft version release in Github and a publish step that
publishes the draft, updates `latest_release` and pushes images to
DockerHub.

It is also possible to specify `--pre-release` to draft. Once published
such a release will be marked with a prominent warning in GitHub;
additionally we do not update `latest_release` in GitHub nor push an
update to the `latest` tag in DockerHub.
2015-09-01 17:04:11 +01:00
Adam Harrison 82f2f7b2b9 Check for required tooling at top level
Pull common checks out to top level in preparation for the introduction
of an additional process step.
2015-09-01 17:04:11 +01:00
Adam Harrison f48a4373d1 Allow freeform version tags
To support arbitrarily named pre-releases we no longer impose any
restrictions on the format of the most recent annotated tag - instead we
expect to find a single annotated tag pointing at HEAD of the current
branch and assume this is the version to release.
2015-09-01 17:04:11 +01:00
Adam Harrison ab9d1777a1 Support make UPDATE_LATEST=false publish
When publishing pre-releases via the release script we do not want to
modify the `latest` tag in DockerHub - specifying UPDATE_LATEST=false
causes publish to push to $WEAVE_VERSION only. Omitting the flag
entirely retains the existing behaviour for backwards compatability.
2015-09-01 17:04:11 +01:00
Bryan Boreham 6a34995796 Merge pull request #1370 from weaveworks/docker-py-test-matcher
LGTM. Fixes #1367
2015-09-01 16:47:24 +01:00
Bryan Boreham df25f84011 Merge pull request #1375 from weaveworks/1373-document-no-rewrite-hosts
LGTM.  Fixes #1373
2015-09-01 15:01:51 +01:00
Paul Bellamy cead9579be document --no-rewrite-hosts flag 2015-09-01 11:01:45 +01:00
Paul Bellamy a23c0237c3 using --collect-only so we don't miss tests 2015-09-01 10:50:13 +01:00
Matthias Radestock 5c3efbdca0 Merge pull request #1366 from weaveworks/python-call-changed
Fix docker api proxy smoke-tests after docker-py change
2015-08-28 20:50:35 +01:00
Bryan Boreham 176ee73efe Ensure we have the busybox image 2015-08-28 20:34:43 +01:00
Bryan Boreham 54833195ed Call to docker-py changed upstream 2015-08-28 20:34:43 +01:00
Matthias Radestock 8960a3a898 Merge branch '1.0'
Conflicts:
	proxy/create_exec_interceptor.go
	test/600_proxy_docker_py_test.sh
2015-08-28 20:29:43 +01:00
Matthias Radestock 8184d29d4a Merge pull request #1367 from weaveworks/1.0-python-call-changed
Fix docker api proxy smoke-tests after docker-py change
2015-08-28 20:22:22 +01:00
Matthias Radestock fef91b5339 comment 2015-08-28 18:09:23 +01:00
Matthias Radestock 24de98e188 refactor 2015-08-28 18:08:24 +01:00
Matthias Radestock 6291a89665 Merge pull request #1314 from weaveworks/1209-wait-iface-netlink
Wait for ethwe via netlink, not via polling or signals

Fixes #1209. Fixes #1258. Fixes #1300.
2015-08-28 17:44:55 +01:00
Bryan Boreham a45e5ac152 Test calling 'start' with a HOST_CONFIG not supplied to 'create' 2015-08-28 16:20:31 +01:00
Paul Bellamy 6b5f35e574 [proxy] Only replace the request body if we've modified it.
This is a temporary fix for a failure in docker-py tests:
"test_valid_no_config_specified".
2015-08-28 15:09:33 +01:00
Matthias Radestock 244a679f90 Merge pull request #1349 from weaveworks/issues/1266-detect-adverse-firewall-config
Warn on detection of REJECT rule in FORWARD chain

Fixes #1266.
2015-08-28 12:22:23 +01:00
Matthias Radestock 1a9909e3e2 also warn on weave reset when env's DOCKER_HOST points to proxy
This was missed in #1327.
2015-08-28 09:34:18 +01:00
Matthias Radestock 6655974ef3 cosmetic: tidy up log messages 2015-08-28 09:12:42 +01:00
Matthias Radestock cdf4fb605a Merge branch '1.0' 2015-08-27 20:04:04 +01:00
Michael Bridgen bca31c7335 Merge pull request #1365 from weaveworks/1356_ignore_interface_deletion_errors_in_weave_reset
ignore interface deletion errors during `weave reset`
2015-08-27 15:38:16 +01:00
Bryan Boreham 7652b44c21 Ensure we have the busybox image 2015-08-27 15:14:06 +01:00
Bryan Boreham 6fce798164 Factor out deserializeRoute() func 2015-08-27 15:10:31 +01:00
Bryan Boreham 7801dcd849 Factor out common loop receiving from netlink 2015-08-27 15:10:31 +01:00
Bryan Boreham 2eed101a93 In weavewait, wait for the multicast route to be added before calling container entrypoint.
We have moved the 'route add' command to the end of 'attach', so this
indicates that all network interface set-up is complete.
2015-08-27 15:10:31 +01:00
Bryan Boreham 3d25620d13 In proxy, rewrite /etc/hosts from 'weave attach', via new utility weavehosts 2015-08-27 15:03:41 +01:00
Bryan Boreham 6e89b036e2 Wait for ethwe via netlink, not via polling or signals
('docker kill' breaks restart policy)
2015-08-27 15:03:41 +01:00
Michael Bridgen 8b5992e43f Merge pull request #1290 from weaveworks/labels
Add role label to weave containers so scope can filter
2015-08-27 15:00:15 +01:00
Bryan Boreham 548b4aea40 Call to docker-py changed upstream 2015-08-27 14:54:20 +01:00
Tom Wilkie 42b64f826b Merge pull request #1358 from weaveworks/1357-dns-reverse-tombstone
Make reverse dns lookups honor tombstones.
2015-08-27 14:25:23 +01:00
Tom Wilkie 95379b0c5a Add works.weave.role=system label to weave containers. 2015-08-27 12:22:34 +00:00
Matthias Radestock f5918a3c14 remove dead code
since #1248
2015-08-27 08:42:48 +01:00
Matthias Radestock 1df752cd33 ignore interface deletion errors during weave reset
These can occur when a container dies during `weave reset`.

Fixes #1356.
2015-08-27 07:54:51 +01:00
Tom Wilkie a1f5d90323 Merge pull request #1363 from weaveworks/1362-fix-quoted-params
Fix test 295
2015-08-26 13:46:49 +01:00
Bryan Boreham 5289a380f2 Use $* instead of $@ to get the desired quoting 2015-08-26 12:31:38 +01:00
Tom Wilkie 914c88454d Review feedback 2015-08-25 16:53:21 +00:00
Tom Wilkie 46363f4129 Make reverse dns lookups honor tombstones. 2015-08-25 12:01:09 +00:00
Michael Bridgen bd19b86259 Merge pull request #1352 from weaveworks/doc-format
Small improvements in features doc
2015-08-25 12:56:56 +01:00
Tom Wilkie db2e7e4ba1 Merge pull request #1355 from weaveworks/1343-docker-bridge-in-resolv-conf
Pass docker bridge ip to dns server, and filter it out of upstream dns ips.
2015-08-25 09:48:06 +01:00
Bryan Boreham 760f419e8f Merge pull request #1354 from weaveworks/1347-dns-tombstone-comment
Lgtm.  Closes #1347
2015-08-24 17:05:54 +01:00
Tom Wilkie 58ae0cd113 Pass docker bridge ip to dns server, and filter it out of upstream dns ips. 2015-08-24 16:00:26 +00:00
Tom Wilkie 4809c113b3 Update comment. 2015-08-24 15:43:31 +00:00
Ilya Dmitrichenko 6d0db90f43 Revert "Merge pull request #1337 from weaveworks/1295-push-on-green"
This reverts commit 5054805ac6, reversing
changes made to 396df6bcda.
2015-08-24 14:22:36 +01:00
Ilya Dmitrichenko 75fb8f6ebf Revert "need to be in gopath to run make publish"
This reverts commit 130cf6eb2b.
2015-08-24 14:22:29 +01:00
Bryan Boreham 26739463a4 Escape a minus character to stop it being taken as a bullet, and clarify which example we are talking about 2015-08-24 14:19:14 +01:00
Paul Bellamy 130cf6eb2b need to be in gopath to run make publish 2015-08-24 11:10:59 +01:00
Paul Bellamy 5054805ac6 Merge pull request #1337 from weaveworks/1295-push-on-green
Publish latest images from CI on successful builds.
2015-08-24 10:58:14 +01:00
Adam Harrison ca94cfffe7 Detect REJECT rule in FORWARD chain 2015-08-21 16:25:49 +01:00
Matthias Radestock 396df6bcda Merge pull request #1348 from weaveworks/1345-1332-dns-response-size
Unify logic to tell if DNS response is too big.

Fixes #1345, fixes #1332
2015-08-21 14:03:00 +01:00
Matthias Radestock 58dafbfa9d Merge pull request #1344 from weaveworks/refactor-dns-handlers
Refactor dns handlers
2015-08-21 13:44:57 +01:00
Tom Wilkie 8516ab7c69 Unify logic to tell if DNS response is too big. 2015-08-21 12:39:12 +00:00
Matthias Radestock 2284bc2120 move error response construction to handler
and implement it in terms of existing functions, plus provide a
convenience method for sending the rather common NameError.
2015-08-21 12:09:22 +01:00
Matthias Radestock 31a490bfe6 ensure that make publish publishes up-to-date images
otherwise the git revision based image tag doesn't match what is in
the image.
2015-08-21 09:34:08 +01:00
Matthias Radestock 0b5f5fd74e extract response writing 2015-08-20 20:24:12 +01:00
Matthias Radestock 9a7abe68fd optimise response construction when no truncation is required
no need to do a binary search if all the answers fit
2015-08-20 20:18:56 +01:00
Matthias Radestock 2ea27ec08f simplify response truncation 2015-08-20 19:55:19 +01:00
Matthias Radestock 322441d5c8 inline truncateResponse 2015-08-20 19:55:19 +01:00
Matthias Radestock 7c7c302920 extract response construction 2015-08-20 19:17:03 +01:00
Matthias Radestock 69986e77dd cosmetic 2015-08-20 18:46:55 +01:00
Matthias Radestock 8182022d52 refactor dns handlers
introduce a handler struct, which

- enables lifting of all the handle* closures to methods
- eliminates the passing around of defaultMaxResponseSize
- reduces the number of free-floating functions
2015-08-20 18:27:58 +01:00
Matthias Radestock b075e93319 Merge pull request #1324 from weaveworks/1321_elminate_misleading_flag_parsing_error
eliminate misleading flag parsing error

Fixes #1321.
2015-08-20 17:34:32 +01:00
Matthias Radestock fd6d15e712 eliminate misleading flag parsing error
The standard mflags error reporting produces s.t. like
```
flag provided but not defined: -p
See '/home/weave/weaver --help'.
```
on stderr. This is misleading since the program name is container
local, so not something the user actually invoked.

Fixes #1321.
2015-08-20 17:19:48 +01:00
Matthias Radestock f399c3a888 Merge branch '1.0'
Conflicts:
	ipam/ring/ring_test.go
2015-08-20 16:32:09 +01:00
Matthias Radestock 79d1c404a7 Merge pull request #1342 from weaveworks/1336-fix-ring-fuzztest
Fix ring fuzz-test

Fixes #1336.
2015-08-20 16:25:50 +01:00
Adam Harrison 1e83e7a95c Merge branch '1.0' 2015-08-20 15:33:59 +01:00
Adam Harrison 086ab9d126 Correct MaxUDPPacketSize constant 2015-08-20 15:22:10 +01:00
Tom Wilkie 8a7821a15b Publish latest images from CI on successful builds. 2015-08-20 14:20:32 +00:00
Bryan Boreham 135c34a45a Don't remove peer from table until after we've copied its entries out 2015-08-20 13:11:51 +01:00
Bryan Boreham ca6e54890c Fix Transfer() when removing a peer, so we actually target the other peer. 2015-08-20 13:11:51 +01:00
Tom Wilkie dac219b64a Merge pull request #1339 from weaveworks/revert-1338-1336-check-len
Revert "In ipam ring unit test, check if we have any peers with entri…
2015-08-20 12:23:06 +01:00
Tom Wilkie 790a1757e0 Revert "In ipam ring unit test, check if we have any peers with entries to avoid crash " 2015-08-20 12:18:34 +01:00
Tom Wilkie aafa0f6783 Merge pull request #1338 from weaveworks/1336-check-len
In ipam ring unit test, check if we have any peers with entries to avoid crash
2015-08-20 12:15:27 +01:00
Bryan Boreham df5426dd8b Check if we have any peers with entries, to avoid crash 2015-08-20 11:57:56 +01:00
Adam Harrison 3803d79835 Merge pull request #1335 from weaveworks/1328_handle_invalid_status_sub_command
better error handling for invalid `weave status` sub-command
2015-08-20 11:48:01 +01:00
Matthias Radestock ceff3bed28 better error handling for invalid weave status sub-command
instead of showing '404 page not found'

Fixes #1328.
2015-08-20 11:14:59 +01:00
Matthias Radestock eebdbe226b Merge pull request #1325 from weaveworks/1322_handle_usage_locally
deal with `weave --help|help` locally

Fixes #1322.
2015-08-20 10:13:45 +01:00
Matthias Radestock 8ab639ddaa deal with weave --help|help locally
thus making them unaffected by docker breakages

Fixes #1322.
2015-08-20 10:03:21 +01:00
Matthias Radestock 812579a139 Merge pull request #1326 from weaveworks/576_better_version_check
better docker version check

Fixes #576.
2015-08-19 21:31:43 +01:00
Matthias Radestock 08f31886d6 better docker version check
Fixes #567. Supersedes #1298.
2015-08-19 21:20:14 +01:00
Matthias Radestock f080e5aa5c Merge pull request #1331 from weaveworks/clarify-test-160
clarify test 160
2015-08-19 20:41:59 +01:00
Matthias Radestock d61c719e45 cosmetic 2015-08-19 20:39:16 +01:00
Bryan Boreham 4d49bf3ab9 Add comment to clarify the reason why there is no explicit 'assert' test at the end. 2015-08-19 16:54:24 +01:00
Matthias Radestock f34f837106 Merge branch '1.0'
Conflicts:
	weave
2015-08-19 16:38:55 +01:00
Matthias Radestock 1326994a6d Merge pull request #1319 from weaveworks/dns-tcp
Expose dns server on tcp as well as udp

Fixes #1333.
2015-08-19 16:34:09 +01:00
Matthias Radestock 99ab0d3abc Merge branch '1.0' 2015-08-19 11:29:23 +01:00
Matthias Radestock e61f9afb72 Merge pull request #1330 from weaveworks/1329_plug_gossip_sender_leak
plug GossipSender leak

Fixes #1329.
2015-08-19 11:26:25 +01:00
Matthias Radestock 29c64b1f40 document weave env --restore
leftover from #1327
2015-08-19 11:20:10 +01:00
Bryan Boreham db51b772b4 Merge pull request #1327 from weaveworks/1288_restore_env
Closes #1288
2015-08-19 10:54:43 +01:00
Matthias Radestock 0b764a2a27 plug GossipSender leak
GossipChannel.sendDown is the (only) place which creates
GossipSenders. It has two call sites, Send and SendDown, only the
former of which performed a gc of existing senders. Therefore, in
configurations with

- a lot of connection churn, and
- SendDown invocations for these connections, and
- few or no Send invocations

we can leak GossipSenders.

The most likely scenario for this to occur is a peer that, while
having no other connections, is making/accepting connections which get
torn down post handshake, e.g. because IPAM detected a clash.

Fixes #1329.
2015-08-19 10:22:11 +01:00
Matthias Radestock a93a3e98b9 warn when stopping proxy when the env is using it 2015-08-18 18:08:02 +01:00
Matthias Radestock be94dcc8a3 introduce weave env --restore
`weave env` stashes the existing DOCKER_HOST in ORIG_DOCKER_HOST, and
`weave env --restore` sets DOCKER_HOST to $ORIG_DOCKER_HOST.

Closes #1288.
2015-08-18 17:20:21 +01:00
Matthias Radestock a34b31ad12 refactor: make proxy_addr more single purpose 2015-08-18 15:40:58 +01:00
Matthias Radestock e1d07aa264 cosmetic 2015-08-18 15:34:43 +01:00
Matthias Radestock 0d79a8fc43 cosmetic 2015-08-18 15:08:59 +01:00
Matthias Radestock 5d1411830b Merge branch '1.0' 2015-08-18 13:33:36 +01:00
Matthias Radestock dc91ce9ec6 Merge branch 'latest_release_doc_updates' into 1.0 2015-08-18 13:33:15 +01:00
Matthias Radestock 36a857f343 remove "Contact information" link on docs home page
It's broken, and we have a link in the footer anyway.

Fixes #1260.
2015-08-18 13:31:28 +01:00
Matthias Radestock c1c5450caf more idiomatic logger initialisation, with standard default log level 2015-08-18 12:04:48 +01:00
Bryan Boreham 0b4e8b8754 Merge pull request #1318 from weaveworks/1305_suppress_self_reconnection
lgtm. Fixes #1305.
2015-08-18 11:14:15 +01:00
Matthias Radestock f73dcba306 eliminate spurious warning when --ipalloc-range=""
Fixes #1320.
2015-08-18 09:50:42 +01:00
Ilya Dmitrichenko 147a537b82 We are not using gcloud, but it cause a conflict
https://s3.amazonaws.com/archive.travis-ci.org/jobs/76062893/log.txt
2015-08-18 09:47:37 +01:00
Ilya Dmitrichenko a4844b8ee7 We are not using gcloud, but it cause a conflict
https://s3.amazonaws.com/archive.travis-ci.org/jobs/76062893/log.txt
2015-08-18 09:46:09 +01:00
Ilya Dmitrichenko acde6cb0ee We are not using gcloud, but it cause a conflict
https://s3.amazonaws.com/archive.travis-ci.org/jobs/76062893/log.txt
2015-08-18 09:40:03 +01:00
Paul Bellamy b4e13215c7 expose dns server on tcp as well as udp
issue was discovered when testing scope with master vs 1.0 branches.
2015-08-18 09:35:53 +01:00
Matthias Radestock 4ecaa0f508 don't put blank lines around sub-status
not what I had in mind, and it breaks the tests
2015-08-18 09:00:01 +01:00
Matthias Radestock 86b7e4a6db Merge branch '1.0' 2015-08-18 08:50:41 +01:00
Matthias Radestock aa19028583 Merge branch 'latest_release_doc_updates' into 1.0 2015-08-18 08:50:19 +01:00
Matthias Radestock a6e2296e0a Merge branch '1.0' into latest_release_doc_updates 2015-08-18 08:49:31 +01:00
Matthias Radestock 8c5248da43 Add release 1.0.2 2015-08-18 08:27:26 +01:00
Matthias Radestock 59859c4aed clarify allocation subnet overlap failure
Closes #1317.
2015-08-18 01:50:30 +01:00
Matthias Radestock 0e85783257 cosmetic: add blank lines around weave status output
Closes #1315.
2015-08-18 01:38:51 +01:00
Matthias Radestock d8e8cd6e51 don't reconnect to ourself
When we discover that a connection target address leads to ourself we
set the retry time to the zero time.Time value - the only special
time.Time value. We use that marker to a) suppress subsequent
connection attempts, and b) display the next retry as 'never' in
`weave status|report`.

Closes #1305.
2015-08-18 00:51:33 +01:00
Matthias Radestock bbc91c1273 Merge pull request #1307 from weaveworks/1306-dns-recurse-compress
Compress recursive answers if they are too big.

Fixes #1306.
2015-08-17 19:23:46 +01:00
Tom Wilkie 67f974923b Test for recursive DNS queries & compression.
Note this doesn't test the golang resolver, where the original error
was found, as I can't find a way to point the golang resolver at a
custom dns server during a test.  It does check that a compressed
response from an upstream server is correctly handled.
2015-08-17 18:25:36 +01:00
Tom Wilkie 4f00dab4c4 Compress recursive answers if they are too big. 2015-08-17 18:25:26 +01:00
Matthias Radestock 74e29c8990 Merge branch '1.0'
Conflicts:
	nameserver/server.go
	nameserver/server_test.go
2015-08-17 18:10:56 +01:00
Matthias Radestock 7fb597a8bf Merge pull request #1313 from weaveworks/issues/1306-dns-compress-1-0-2
Try to compress too long replies (when forwarding).

Fixes #1306.
2015-08-17 18:05:04 +01:00
Alvaro Saurin c50c90ea8d Try to compress too long replies (when forwarding). 2015-08-17 18:50:16 +02:00
Paul Bellamy 74883c5da8 Merge pull request #1310 from weaveworks/improve_proxy_ignore_messages
Improve log messages for when the proxy ignores a container
2015-08-17 16:27:45 +01:00
Matthias Radestock 375af0a7b9 Improve log messages for when the proxy ignores a container
- 'ignoring' could be misread as 'dropped request', so change that to
  'leaving container alone'
- more elaborate reasons
2015-08-17 16:15:45 +01:00
Matthias Radestock 8c9679b19c Merge branch '1.0'
Conflicts:
	circle.yml
2015-08-17 15:01:44 +01:00
Tom Wilkie d220542491 Add sudo apt-get update to circle.yml 2015-08-17 13:37:41 +00:00
Alvaro 0a80fdcc1d Merge pull request #1312 from weaveworks/circle
Add sudo apt-get update to circle.yml
2015-08-17 13:52:00 +02:00
Tom Wilkie 5fbcb3f905 Add sudo apt-get update to circle.yml 2015-08-17 08:49:40 +00:00
Matthias Radestock 067f96023a correct option name in log message 2015-08-17 09:37:46 +01:00
Matthias Radestock 9de7c9ed0d Merge pull request #1303 from weaveworks/1302-proxy-net-none
[proxy] attach containers with --net=none to weave

Fixes #1302.
2015-08-14 14:03:36 +01:00
Paul Bellamy 0de292c0d4 [proxy] attach containers with --net=none to weave 2015-08-14 13:48:32 +01:00
Matthias Radestock eaa5385069 refactor: use new proxy_start_container function 2015-08-13 19:54:00 +01:00
Matthias Radestock fa2aa1f791 Merge branch '1.0'
Conflicts:
	test/640_proxy_restart_reattaches_test.sh
2015-08-13 19:46:23 +01:00
Matthias Radestock 181ce4cc60 Merge pull request #1296 from weaveworks/1294_extract_proxy_container_start
extract test functions for "start container via proxy"

Fixes #1294.
2015-08-13 19:40:53 +01:00
Matthias Radestock 65578211ef extract test functions for "start container via proxy"
This also eliminates the redefinition of 'start_container' by some of
the tests, which causes failures when multiple tests are run with
`run_all.sh`.

Fixes #1294.
2015-08-13 15:01:15 +01:00
Matthias Radestock 78df99e7e7 Merge branch '1.0' 2015-08-13 14:37:31 +01:00
Matthias Radestock 90ed76853c fix test breakage
on master, and only when running via `run_all.sh`. Weird.
2015-08-13 14:36:30 +01:00
Matthias Radestock 26f4a41900 Merge branch '1.0' 2015-08-13 14:07:22 +01:00
Matthias Radestock a3b4d6cdbf consistency: use $SMALL_IMAGE instead of $BASE_IMAGE
whenever possible
2015-08-13 14:06:43 +01:00
Matthias Radestock 686500b181 Merge branch '1.0' 2015-08-13 14:05:35 +01:00
Matthias Radestock 84c5c52090 fix broken test silliness 2015-08-13 14:05:17 +01:00
Matthias Radestock d5de0b41f9 Merge branch '1.0'
Conflicts:
	proxy/proxy.go
2015-08-13 13:57:02 +01:00
Matthias Radestock bd41d114b5 Merge pull request #1291 from weaveworks/docker-1.8
Docker 1.8 compatibility.

Fixes #1287.
2015-08-13 13:53:03 +01:00
Matthias Radestock 94d2f84364 refactor: use proxy abstraction 2015-08-13 13:50:29 +01:00
Paul Bellamy bf2b456af1 Don't use .Volumes in tests, as it's gone in docker 1.8 2015-08-13 13:36:47 +01:00
Paul Bellamy 4961e83686 [proxy] support new docker 1.8 --net="default" 2015-08-13 13:36:47 +01:00
Paul Bellamy f70dc6660a [proxy] lock docker api usage to 1.15 2015-08-13 13:36:47 +01:00
Matthias Radestock 2bb84ae28b Merge branch '1.0'
Conflicts:
	weave
2015-08-13 13:18:55 +01:00
Paul Bellamy 360c0cb642 Merge commit '74eef4dedae617' into 1.0 2015-08-13 12:38:51 +01:00
Matthias Radestock e319f54d3f Merge branch '1.0'
Conflicts:
	site/proxy.md
2015-08-13 11:36:01 +01:00
Matthias Radestock 1ce9551ba6 Merge branch 'latest_release_doc_updates' into 1.0 2015-08-13 11:34:56 +01:00
Matthias Radestock 4c566c8587 correct typo 2015-08-13 11:34:16 +01:00
Paul Bellamy 74eef4deda [proxy] mount all tls volumes as readonly 2015-08-13 11:13:22 +01:00
Paul Bellamy 2576e819a7 Revert "fix intermittent test failure"
This reverts commit e2fec3d007.

Was causing a failure on circleci
2015-08-13 11:12:50 +01:00
Tom Wilkie cdab6a71b7 Merge pull request #1289 from weaveworks/move-listvar
Move ListVar into common, so it can be reused elsewhere.
2015-08-12 12:56:25 +01:00
Tom Wilkie 45293fdcac Move ListVar into common, so it can be reused elsewhere. 2015-08-12 11:35:57 +00:00
Paul Bellamy e2fec3d007 fix intermittent test failure
Tests were running in the same shell, so function definitions were
leaking between tests.
2015-08-12 11:14:51 +01:00
Matthias Radestock 7656752d87 unbreak test
caused by faulty merge
2015-08-12 07:40:20 +01:00
Matthias Radestock d0ed225184 Merge branch '1.0'
Conflicts:
	weave
2015-08-11 15:01:54 +01:00
Paul Bellamy 12ed5cc9ee Merge pull request #1284 from weaveworks/1279_proxy_weave_run
prevent proxy from interferring with `weave run`
2015-08-11 14:54:27 +01:00
Matthias Radestock f8639a57e7 prevent proxy from interferring with weave run
...which could result in containers unexpectedly getting multiple IP
addresses.

Fixes #1279.
2015-08-11 14:02:23 +01:00
Adam Harrison c2147f80b7 Merge pull request #1283 from weaveworks/1185_report_pcap_stats
add pcap stats to `weave report`
2015-08-11 13:09:34 +01:00
Matthias Radestock f1b9cf4fc0 Merge pull request #1282 from weaveworks/1281-dns-remove-dead-quick
Remove DNS entries for dead containers when adding correctly.

Fixes #1281.
2015-08-11 13:08:46 +01:00
Matthias Radestock c1d799a06c add pcap stats to weave report
Closes #1185.
2015-08-11 12:57:52 +01:00
Tom Wilkie 6a5044fc70 Make nameserver logging more consistent. 2015-08-11 11:32:19 +00:00
Tom Wilkie 7fa568872c Ensure we delete the fqdn is the container fails to start. 2015-08-11 11:32:17 +00:00
Adam Harrison f3ad81d554 Merge pull request #1248 from weaveworks/issues/1025-enhanced-weave-status
Enhanced weave status
2015-08-11 12:29:29 +01:00
Adam Harrison fc9a1c8664 new weave status, weave status ..., and weave report 2015-08-11 12:15:13 +01:00
Adam Harrison 00ecff09f7 Move status HTTP handlers into prog/weaver/http.go 2015-08-11 12:01:27 +01:00
Adam Harrison 278807a199 Move router HTTP handlers into router/http.go 2015-08-11 12:01:27 +01:00
Matthias Radestock 8bce900170 Merge pull request #1247 from weaveworks/test_frag
add test for PMTU discovery and packet fragmentation
2015-08-10 22:02:07 +01:00
Matthias Radestock cf6d6d0cd3 add test for PMTU discovery and packet fragmentation 2015-08-10 17:50:21 +01:00
Matthias Radestock 94af02fbf4 Merge branch '1.0'
Conflicts:
	weave
2015-08-10 17:37:06 +01:00
Matthias Radestock 9e65cc20cb cosmetic 2015-08-10 17:33:45 +01:00
Tom Wilkie 25c4a2961e Merge pull request #1278 from weaveworks/1276_warn_on_iprange_overlap
warn on -iprange overlap
2015-08-10 17:27:47 +01:00
Matthias Radestock 755a949f6d Merge pull request #1277 from weaveworks/1275-negative-dns
Return NXDOMAIN when we don't have entries

and drop the non-authoritative special case for recursive dns.

Fixes #1275.
2015-08-10 17:19:44 +01:00
Tom Wilkie 42debde746 Merge pull request #1259 from weaveworks/1257-proxy-chunking
Proxy chunking should handle arbitrarily large chunks
2015-08-10 15:57:19 +01:00
Tom Wilkie c2d5b35cd6 Add test for negative DNS queries 2015-08-10 13:13:12 +00:00
Matthias Radestock f4e7c85219 warn on -iprange overlap
Closes #1276.
2015-08-10 13:27:06 +01:00
Tom Wilkie 073836905a Return NXDOMAIN when we don't have entries, and drop the non-authoritative special case for recursive dns. 2015-08-10 12:19:21 +00:00
Matthias Radestock fd2a59417d cosmetic 2015-08-10 12:39:00 +01:00
Paul Bellamy 6d7e784c25 review feedback 2015-08-10 11:02:51 +01:00
Matthias Radestock b2a24d1cbe Merge branch '1.0'
Conflicts:
	nameserver/zone_lookup.go
2015-08-10 10:27:10 +01:00
Tom Wilkie 0a2701bbbc Merge pull request #1268 from weaveworks/1243_fix_claim_test
add a sleep to improve test reliability
2015-08-10 10:23:48 +01:00
Alvaro 475daa82b3 Merge pull request #1273 from weaveworks/1270_mdns_result_race
ignore DuplicateError when inserting mDNS answer in zone
2015-08-10 11:15:47 +02:00
Matthias Radestock c3b653b1b9 ignore DuplicateError when inserting mDNS answer in zone
When we issue two concurrent mDNS queries for the same name, the
answer to the second one encounters a DuplicateError when inserting
into the zone. We would return that error, which causes the query
would fail. And we'd cache that fact. All bad.

So ignore DuplicateError.

Fixes #1270.
2015-08-08 22:01:45 +01:00
Matthias Radestock 1a11b989e7 simplify test 2015-08-07 14:40:34 +01:00
Matthias Radestock 010792f714 cosmetic: more sensible order of weave commands 2015-08-07 11:44:47 +01:00
Matthias Radestock 536369bc12 Merge pull request #1265 from weaveworks/1252-proxy-non-root-hosts
[proxy] rewrite /etc/hosts from outside the container

Fixes #1252.
2015-08-06 13:30:02 +01:00
Paul Bellamy f55655216f [proxy] rewrite /etc/hosts from outside the container
Avoids problems when container user is non-root. Added a test for this
and the --no-rewrite-hosts flag.
2015-08-06 13:14:18 +01:00
Matthias Radestock 43ef3b8515 give routers some time to establish connectivity 2015-08-06 11:54:58 +01:00
Paul Bellamy 3180c67151 -no-rewrite-hosts -> --no-rewrite-hosts 2015-08-06 11:01:35 +01:00
Adam Harrison 457e162d0c Merge branch '1.0'
Conflicts:
	site/ipam.md
2015-08-06 09:56:50 +01:00
Adam Harrison ed3207afe7 Merge remote-tracking branch 'upstream/latest_release_doc_updates' into 1.0 2015-08-06 09:51:31 +01:00
Adam Harrison 8e5b0ceceb Merge pull request #1262 from weaveworks/latest_release_doc_udpates
Fix IPAM doc bug
2015-08-06 09:48:14 +01:00
Tom Wilkie f9127e90db Merge pull request #1263 from weaveworks/integration-tests-no-save-file
don't save the python test file when running test/600
2015-08-05 18:50:22 +01:00
Paul Bellamy 94db0beb25 don't save the python test file when running test/600 2015-08-05 18:04:49 +01:00
Adam Harrison 4a1bb1a33a Fix IPAM doc bug 2015-08-05 17:53:24 +01:00
Adam Harrison 77996158ef Merge branch '1.0' 2015-08-05 14:42:09 +01:00
Adam Harrison 93984e1955 Merge pull request #1261 from weaveworks/arp_all_ips
send ARP update for *all* container IPs; not just the first one
2015-08-05 14:38:16 +01:00
Matthias Radestock 67aae051d6 send ARP update for *all* container IPs; not just the first one
This was missed in #498.
2015-08-05 13:35:35 +01:00
Matthias Radestock 6cc7749cf2 cosmetic: remove debug logging from test 2015-08-05 12:31:20 +01:00
Matthias Radestock c47e57cc29 cosmetic 2015-08-05 11:55:11 +01:00
Matthias Radestock de6f5a93e8 refactor: extract build flags 2015-08-05 11:11:57 +01:00
Paul Bellamy 157910ec9a adding integration test for proxy handling large http chunks 2015-08-05 10:52:06 +01:00
Paul Bellamy b85b90ef4e Proxy chunking should handle arbitrarily large chunks 2015-08-04 17:47:51 +01:00
Paul Bellamy 1d1ebc103d Merge remote-tracking branch 'upstream/1.0' 2015-08-04 14:29:12 +01:00
Paul Bellamy eee13e7dd9 Merge pull request #1253 from weaveworks/1241-no-command-specified
[proxy] Handle case where no command is specified.
2015-08-04 13:11:35 +01:00
Paul Bellamy 7cc53c4c68 [proxy] Handle case where no command is specified.
The docker daemon can't do this because we set /w/w.
2015-08-04 12:32:42 +01:00
Matthias Radestock 9c22066bdb Merge pull request #1256 from weaveworks/1245-dns-loadbalancing
Add note to explain DNS shuffle effects.

Fixes #1245.
2015-08-04 12:22:23 +01:00
Tom Wilkie f241e15ec1 Review feedback 2015-08-04 11:00:32 +00:00
Tom Wilkie 881ece3553 Add note to explain dns shuffle effects. 2015-08-04 10:47:36 +00:00
Michael Bridgen 281bf02871 Merge branch 'pr1254' 2015-08-03 15:26:08 +01:00
Michael Bridgen 778038fdd3 Merge branch 'fix-dotted-imports-for-log' of https://github.com/dpw/weave into pr1254 2015-08-03 14:29:53 +01:00
David Wragg e7ee72836d Log -> log 2015-08-03 14:13:24 +01:00
David Wragg 31c0506b85 Eliminate dotted imports of common for the sake of Log 2015-08-03 14:07:16 +01:00
David Wragg 7d6f85424c Move OnOff into router.go 2015-08-03 14:06:23 +01:00
Tom Wilkie ca89cfee3d Merge pull request #1250 from paulbellamy/testrunner-summarize-fails
Some usability tweaks to the test runner
2015-07-31 12:36:50 +01:00
Paul Bellamy 5a37f652c2 add -v to testrunner, and less output in happy path 2015-07-31 12:25:50 +01:00
Paul Bellamy cc7b781bb3 have testrunner summarize failed tests so they are easier to find 2015-07-31 12:12:33 +01:00
Paul Bellamy 55051f0194 Merge remote-tracking branch 'upstream/1.0' 2015-07-31 11:01:19 +01:00
Paul Bellamy 78e0e79b26 Merge pull request #1238 from weaveworks/1232-weavewait-image
[proxy] only mount /w into weavewaited containers
2015-07-31 10:57:26 +01:00
Paul Bellamy b7e8938ab4 scope the test runner call so scope works 2015-07-31 10:49:21 +01:00
Matthias Radestock f8327c3c6c expand test coverage to unicast relaying
by way of an ipam space request from host3 to host1, via host2.
2015-07-30 20:38:00 +01:00
Paul Bellamy e3851e6e55 Merge pull request #1246 from weaveworks/gce-parameterize
parameterize test/gce.sh so scope can override TEMPLATE_NAME and NUM_HOSTS
2015-07-30 16:18:59 +01:00
Matthias Radestock 6a136eebb2 make parallel test running work locally
@tomwilkie assures me this change won't break CircleCI. We shall see.

This now works for me:

make clean
make COVERAGE=true
COVERDIR=test/coverage SLOW=true ./test/units.sh
cd test
./setup.sh
COVERAGE=true PARALLEL=true ./run_all.sh
./gen_coverage_reports.sh
2015-07-30 15:57:49 +01:00
Paul Bellamy 9396c046cd parameterize test/gce.sh so scope can override TEMPLATE_NAME and NUM_HOSTS 2015-07-30 15:48:22 +01:00
Paul Bellamy 4958b8d37a [proxy] Mount /w from the host for weavewaited containers
We set it at container create. Had tried also setting it at start, but
it seems to cause weird behaviour, and there is no way to do it from the
CLI.
2015-07-30 15:11:03 +01:00
Paul Bellamy e2389a4061 [proxy] Don't try to attach if container failed to start 2015-07-30 13:31:47 +01:00
Paul Bellamy dccd199369 Merge pull request #1242 from weaveworks/1239-600-docker-py
Shard 600_proxy_docker_py into 4.
2015-07-30 13:14:54 +01:00
Tom Wilkie a87be89095 Typos. Honestly 2015-07-30 11:07:17 +00:00
Tom Wilkie 5c9a13d343 Shard 600_proxy_docker_py into 4. 2015-07-30 10:38:02 +00:00
Tom Wilkie bfe07fc71a Give each shard 5 VMs 2015-07-30 10:32:16 +00:00
Tom Wilkie fa95b67c92 Add summary to bottom of test runner output.
Also, don't sort tests by #hosts when run sequentially, and fix typo in run_all.sh.

Fixes #1240
2015-07-30 10:29:05 +00:00
Adam Harrison 728203912e .gitignore Vim recovery files 2015-07-30 10:53:59 +01:00
Paul Bellamy 27289e7b7b Merge pull request #1236 from weaveworks/1169-frugal-frugal-frugal
Run integration tests in parallel when possible.
2015-07-30 10:16:39 +01:00
Tom Wilkie 669a0db528 Run integration tests in parallel when possible. 2015-07-29 17:28:58 +00:00
Paul Bellamy 4da999ba86 Merge pull request #1235 from weaveworks/gce
Improve CI run time
2015-07-29 17:38:30 +01:00
Paul Bellamy 5e9bd5424a Removed all the imports from "code.google.com" 2015-07-29 15:47:02 +01:00
Tom Wilkie 3bffd871b8 Merge pull request #1230 from weaveworks/1154-proxy-coverage
Add coverage to proxy for integration tests.
2015-07-29 13:24:21 +01:00
Adam Harrison 2d450ac39d Merge pull request #1225 from weaveworks/21-dns-listen-addr
Allow users to set dns listen address
2015-07-29 13:18:10 +01:00
Tom Wilkie 6fe69590c4 Add coverage to proxy for integration tests. 2015-07-29 12:06:29 +00:00
Tom Wilkie 4df3d7f150 Reduce the amount of parallelism in the ipam tests 2015-07-29 10:13:02 +00:00
Tom Wilkie 51128a6705 Parallelise the copying of /etc/hosts. 2015-07-29 09:58:51 +00:00
Tom Wilkie e7676979ee Parallelise the copying of imaages etc 2015-07-29 09:58:05 +00:00
Tom Wilkie da36c78942 Reduce the number of times gcs.sh ssh's into the VMs 2015-07-29 09:41:57 +00:00
Matthias Radestock 77358140d9 Merge pull request #1219 from squaremo/driveby
Remove misleading comment and lowercase deniedBy
2015-07-28 18:09:01 +01:00
Adam Harrison 1701bc3e92 Merge branch '1.0'
Conflicts:
	weave
2015-07-28 18:06:44 +01:00
Matthias Radestock ba0ffd225e Merge pull request #1231 from weaveworks/issues/1229-explicitly-set-docker-bridge-mac
Enforce docker bridge address assignment type on launch

Fixes #1229.
2015-07-28 17:59:46 +01:00
Adam Harrison a14d4d9f8e Enforce docker bridge address assignment type on launch 2015-07-28 17:51:48 +01:00
Tom Wilkie db6ee9954c Allow users to set dns listen address so we can run the weave router in the host net namespace. 2015-07-28 14:49:09 +00:00
Paul Bellamy 792c4c8de4 Merge pull request #1227 from weaveworks/gce-gc
Extend scheduler to periodically garbage collect stranded VMs
2015-07-28 15:35:28 +01:00
Matthias Radestock 820307dd4e Merge pull request #1228 from weaveworks/1030-150-connect-forget
Don't init the ring if the address is not in range.

Fixes #1030.
2015-07-28 15:29:16 +01:00
Tom Wilkie 88144ac7f8 Review feedback 2015-07-28 14:16:38 +00:00
Tom Wilkie da7ac78694 Don't try and init the ring if the IP address is not in our range. 2015-07-28 14:12:42 +00:00
Tom Wilkie 10dbd9ad4c Merge pull request #1224 from weaveworks/1203_proper_mock_gossip
proper mock periodic gossip
2015-07-28 13:29:20 +01:00
Tom Wilkie 9779e06f66 Extend scheduler to periodically garbage collect stranded VMs 2015-07-28 13:19:38 +01:00
Matthias Radestock f861d22593 do proper mock periodic gossip
instead of abusing broadcast gossip

Fixes #1203.
2015-07-28 10:37:18 +01:00
Paul Bellamy e3f0565517 Merge pull request #1221 from weaveworks/1220_include_test_dir_coverage
include directory of test code in coverage
2015-07-28 10:05:16 +01:00
Matthias Radestock 9a91b3ce9c include directory of test code in coverage
Fixes #1220.
2015-07-27 17:08:31 +01:00
Michael Bridgen c88361473c Remove misleading comment and lowercase deniedBy
* the comment "don't make the caller wait" applies to both branches
   of that if clause, and having it on one branch implies the other
   does make the caller wait
 * there's no reason for DeniedBy to start with a capital: it's a
   method on a private struct and it's not part of an interface.
2015-07-27 16:22:32 +01:00
Michael Bridgen a2e1a2d375 Merge branch 'pr1200' 2015-07-27 15:46:29 +01:00
Matthias Radestock 536ce992fc re-enable test
our switch on CircleCI from Docker 1.7.0 to 1.7.1 should make this
pass now.
2015-07-27 14:47:55 +01:00
Matthias Radestock 8d57b85a3a Merge branch 'master' into weave_1.1_readme_updates
Conflicts:
	README.md
2015-07-27 14:33:05 +01:00
Matthias Radestock ce37b622eb Merge branch '1.0' 2015-07-27 14:25:06 +01:00
Matthias Radestock d5e510f778 Merge branch 'latest_release_doc_updates' into 1.0 2015-07-27 14:24:33 +01:00
Matthias Radestock be8e414d5a Merge pull request #1217 from weaveworks/1186_document_root_docker
document how to run weave when docker access is root-only

Closes #1186.
2015-07-27 14:23:50 +01:00
Matthias Radestock 65e94f624b document how to run weave when docker access is root-only
Closes #1186.
2015-07-27 14:13:50 +01:00
Paul Bellamy ed4fb14f52 Merge pull request #1214 from weaveworks/rebuild-gce-templates
[circle] use new gce templates for docker 1.7.1
2015-07-27 13:38:44 +01:00
Matthias Radestock 32cce0c636 Merge branch 'master' into weave_1.1_readme_updates 2015-07-27 13:31:48 +01:00
Paul Bellamy 34bb7ae84a [circle] use new gce templates for docker 1.7.1 2015-07-27 12:51:12 +01:00
Michael Bridgen 5389e31190 Merge branch '687-detect-ipam-clash' of github.com:weaveworks/weave into pr1200 2015-07-27 11:54:31 +01:00
Bryan Boreham f0915221d1 Rename 'reclaim' to 'noErrorUnknown' which describes the behaviour better 2015-07-23 20:16:30 +01:00
Matthias Radestock aa4efd63ef cosmetic: uncomment test the way ordained by emacs 2015-07-22 16:55:43 +01:00
Tom Wilkie a12d63bb12 Make ipam test less stressful, so it doesn't timeout. 2015-07-22 12:53:27 +00:00
Tom Wilkie 062a25a032 Make unit test timeout 8mins. 2015-07-22 12:19:12 +00:00
Tom Wilkie 9e6d08c606 Put 2m timeout on all unit tests. 2015-07-22 11:56:39 +00:00
Alvaro 976641c3b6 Reverted: Fixed typo 2015-07-22 12:17:29 +02:00
Alvaro 2f80f12001 Fixed typo 2015-07-22 12:14:38 +02:00
Bryan Boreham 647b755b64 Check manually-assigned addresses with IPAM to see if they clash 2015-07-21 17:02:52 +01:00
David Wragg 26dc83dbc2 Merge weaveworks/519_remove_outermost_gob (PR #1189) 2015-07-21 12:01:40 +01:00
Matthias Radestock 3c107d675a Merge branch '1.0' 2015-07-21 11:42:49 +01:00
Matthias Radestock a20ca49344 Merge pull request #1206 from weaveworks/1201-weavewait-ro
[proxy] Mount weavewait readonly so it can't be tampered with

Fixes #1201.
2015-07-21 11:41:48 +01:00
Paul Bellamy aa8466f688 [proxy] Mount weavewait readonly so it can't be tampered with 2015-07-21 11:30:56 +01:00
Tom Wilkie 2980586e76 Put timeout on nameserver test to try and expose #1203 2015-07-21 09:25:04 +00:00
Paul Bellamy dba116bae1 Merge remote-tracking branch 'upstream/1.0' 2015-07-21 10:13:24 +01:00
Matthias Radestock 0b7d462adf enforce a message size limit
so we do not allocate loads of memory when receiving (accidental or
deliberate) garbage
2015-07-20 23:51:04 +01:00
Matthias Radestock 6f8622a1a0 replace outermost framing on tcp connection
switch from gob to length-prefix encoding
2015-07-20 23:51:04 +01:00
Matthias Radestock af049efb40 refactor: s/SimpleTCP{Sender,Receiver}/GobTCP{Sender,Receiver} 2015-07-20 23:51:02 +01:00
Matthias Radestock ad4e4ce6b8 refactor: make 'inner' encoder/decoder explicit 2015-07-20 23:50:10 +01:00
Tom Wilkie 4c9b0be117 Merge pull request #1198 from weaveworks/1146-coverage
When running unit tests, output coverage for imported modules.
2015-07-20 20:34:12 +01:00
Tom Wilkie a9dae46110 When running unit tests, output coverage for imported modules. 2015-07-20 15:47:07 +00:00
Paul Bellamy 53628fdeff Merge pull request #1187 from weaveworks/1182-proxy-tls-close
[proxy] Fully close TLS sockets, as they have no CloseWrite
2015-07-20 16:46:21 +01:00
Bryan Boreham e34093c328 Merge pull request #1197 from weaveworks/1158-flaky
LGTM
2015-07-20 16:02:52 +01:00
Tom Wilkie 9131b46263 Flush gossip messages after each turn, to ensure there isn't a build up of pending messages. 2015-07-20 14:36:40 +00:00
Michael Bridgen 2a9c3fc64a Merge branch 'pr1112' 2015-07-20 15:31:42 +01:00
Michael Bridgen c44c61dc83 Merge branch '1103-proxy-chunking' of github.com:weaveworks/weave into pr1112 2015-07-20 15:09:50 +01:00
Paul Bellamy 11663dd29f [proxy] rewrote chunked response handler
1) We cannot send "Connection: close", because the fsouza docker client
   expects the tcp socket to stay open between requests.

2) Because we cannot force-close the connection, we can't hijack the
   connection (because go's net/http doesn't let use un-hijack it).

3) Because we need to maintain the individual chunking of messages (for
   docker-py), we can't just copy the response body, as Go will remove and
   re-add the chunking willy-nilly.

Therefore, we have to read each chunk one-by-one, and flush the
ResponseWriter after each one.
2015-07-20 14:43:16 +01:00
Bryan Boreham 90b7c3625d Merge pull request #1176 from weaveworks/1156-gossip-timestamp
LGTM
2015-07-20 14:28:01 +01:00
Tom Wilkie 4e29dba5cd Make Gossip DNS reject gossip messages if clockskew > tombstoneTimeout / 2 2015-07-20 13:12:07 +00:00
Matthias Radestock f1b5a00cb3 Merge pull request #1195 from weaveworks/1177-dns-disappear
Don't periodically delete all non-tombstone entries.

Fixes #1177.
2015-07-20 13:07:42 +01:00
Tom Wilkie 1a4b38a10c Don't delete all the non-tombstone entries every 10mins! 2015-07-20 12:03:46 +00:00
Tom Wilkie 9ab2da8deb Add GET /name handler to return all dns entries (including tombstones) for debugging.
Also, make Address marshall into json as an IP string, not a number.
2015-07-20 12:03:33 +00:00
Matthias Radestock 66473e6021 refactor: return slice instead of pointer to slice from tombstone() 2015-07-20 12:37:54 +01:00
Paul Bellamy a9229b9197 weave env should still work with '--log-level=debug' 2015-07-20 11:57:20 +01:00
Paul Bellamy 7b9e59833e test upload_executable helper should mkdir before uploading 2015-07-20 10:58:39 +01:00
Tom Wilkie 5baddb83cb Comment out failing test until we figure out what to do. 2015-07-20 09:45:02 +00:00
Tom Wilkie 4e56247ed9 Merge pull request #1193 from weaveworks/disable_dns_in_multiweave
Disable dns in multiweave
2015-07-20 10:41:56 +01:00
Matthias Radestock ae2348875b disable dns in multiweave
Thus unbreaking it post #1065.
2015-07-19 22:52:42 +01:00
Matthias Radestock fafaeef07d introduce --no-dns option
undocumented, since it is mainly meant for testing
2015-07-19 22:51:40 +01:00
Matthias Radestock 5b62b8c1e3 refactor: inline populate_dns
and ditch the wait_for_status, since we've only just called that
2015-07-19 22:32:33 +01:00
Matthias Radestock 7c17c8aec3 refactor: simplify router arg parsing
using the same "always shift one" approach as in proxy_args
2015-07-19 21:56:04 +01:00
Matthias Radestock 64408b126e weave launch-router wasn't returning the correct container id
since $CONTAINER gets stomped on by various functions, in particular
populate_dns.

Broken by #1065.
2015-07-19 21:50:45 +01:00
Bryan Boreham 2a1f090a88 Merge pull request #1183 from weaveworks/1181-data-race
Correctly stop the test gossip router to prevent complaints of data races in subsequent tests.
2015-07-17 17:14:46 +01:00
Matthias Radestock e2a2a8f18f Merge branch '1.0' 2015-07-17 15:07:45 +01:00
Matthias Radestock dafa30725e Merge branch 'latest_release_doc_updates' into 1.0 2015-07-17 15:07:19 +01:00
Matthias Radestock 24b9a56e0a Merge pull request #1172 from weaveworks/issues/1061-improve-blockquote
Use Boostrap styles for warnings in docs

Fixes #1061.
2015-07-17 15:06:40 +01:00
Tom Wilkie a3d32068d2 Correctly stop the test gossip router to prevent complaints of data races in subsequent tests. 2015-07-17 13:48:39 +00:00
David Kaltschmidt e7f6682593 Use bootstrap alert styles for blockquote 2015-07-17 15:14:29 +02:00
Paul Bellamy 3f4b385b0f [proxy] If we can't CloseWrite a connection (tls), just Close it so clients don't hang 2015-07-17 14:09:27 +01:00
Paul Bellamy 5fc16a8267 [proxy] use a waitgroup as it's more semantically accurate 2015-07-17 14:08:51 +01:00
Bryan Boreham 68afbe31b0 Shrink image file size 2015-07-17 14:07:23 +01:00
Tom Wilkie ba3451e9ca Merge pull request #1180 from weaveworks/1171-210-failures
Don't swallow errors in the docker client.
2015-07-17 13:38:38 +01:00
Tom Wilkie 25c9180fdf Don't swallow errors in the docker client. 2015-07-17 12:35:12 +00:00
Paul Bellamy e03a4bb176 updating boot2docker docs from 1.0 to master 2015-07-17 12:55:39 +01:00
Paul Bellamy 4d594458d2 Merge branch '1.0' 2015-07-17 12:47:23 +01:00
Paul Bellamy 38176dcbb6 Merge remote-tracking branch 'upstream/latest_release_doc_updates' into 1.0 2015-07-17 12:46:48 +01:00
Paul Bellamy 854d83f294 updating boot2docker docs for proxy 2015-07-17 12:41:04 +01:00
Matthias Radestock 358ae3a936 Merge pull request #1174 from weaveworks/1128-iprange-error
More helpful error message when --ip-alloc-range mismatched

Closes #1128.
2015-07-17 12:35:20 +01:00
Paul Bellamy f3131fab57 Merge pull request #1175 from paulbellamy/975-boot2docker-documentation-update
updating boot2docker docs for proxy
2015-07-17 12:32:54 +01:00
Paul Bellamy 397f1398ee updating boot2docker docs for proxy 2015-07-17 12:30:14 +01:00
Bryan Boreham 2dbf83889b Remove unused error messages 2015-07-17 12:19:54 +01:00
Bryan Boreham 9b2c5b8466 Improve description of merge error 2015-07-17 12:19:33 +01:00
Bryan Boreham 1f4b943454 More helpful error message when --ip-alloc-range mismatched 2015-07-17 11:42:30 +01:00
Matthias Radestock 2ca0fb93c9 Merge pull request #1173 from weaveworks/1168-checkandpanic
Make nameserver.checkAndPanic look for dups, call it more often.

Fixes #1168.
2015-07-17 11:37:34 +01:00
Matthias Radestock 47273d424f Merge pull request #1170 from weaveworks/exec-dockerfile
Re-arrange weaveexec Dockerfile to improve download times
2015-07-17 11:36:15 +01:00
Bryan Boreham f4d6e028d4 Simplify 2015-07-17 11:33:29 +01:00
Tom Wilkie f10e0e4add Merge pull request #1159 from weaveworks/1150-claim-from-peer
Implement claiming addresses from other peers
2015-07-17 11:21:48 +01:00
Tom Wilkie 66e58937ca Make nameserver.checkAndPanic look for dups, call it more often. 2015-07-17 10:19:25 +00:00
Matthias Radestock 62c1cad676 Merge remote-tracking branch 'upstream/master' 2015-07-17 10:58:34 +01:00
Matthias Radestock ca523c716c cosmetic 2015-07-17 10:58:01 +01:00
Tom Wilkie e2deff09a6 Nameserver: log when peer goes away. 2015-07-17 09:36:44 +00:00
Ilya Dmitrichenko 19ddb65036 New screencast URL and image 2015-07-16 18:13:34 +01:00
Bryan Boreham 61c7c034e7 Re-arrange weaveexec Dockerfile to improve download times 2015-07-16 17:02:00 +01:00
Bryan Boreham 4c5920a50a Add fuzz-test for Claim 2015-07-16 16:12:23 +01:00
Bryan Boreham c3c40b15f1 Avoid hanging the caller when we try to claim from a dead peer 2015-07-16 16:12:23 +01:00
Bryan Boreham 2fa65f0a22 Smoke-test for claim operation 2015-07-16 16:12:23 +01:00
Bryan Boreham af7ca99dc4 Extend unit-tests for claim operation 2015-07-16 16:12:23 +01:00
Bryan Boreham 2d224d423d Add SpaceRequestDenied message 2015-07-16 16:12:23 +01:00
Bryan Boreham e7088e5daf Extract decodeRange function 2015-07-16 16:12:23 +01:00
Bryan Boreham d65409e97e Improve comment & log for deferred claim 2015-07-16 16:12:23 +01:00
Matthias Radestock 70110e483c Merge pull request #1166 from weaveworks/dns-tests
Make 210_dns_cross_hosts_test.sh excercise gossip dns tombstones.
2015-07-16 16:11:58 +01:00
Paul Bellamy a0876becbc Merge remote-tracking branch 'upstream/1.0'
Conflicts:
	proxy/create_container_interceptor.go
	proxy/create_exec_interceptor.go
	proxy/proxy.go
	proxy/start_container_interceptor.go
2015-07-16 15:29:29 +01:00
Paul Bellamy 1afdac4d2c Merge pull request #1167 from weaveworks/1157-proxy-net-host
[proxy] do not weave attach containers in '--net=host|container:<name|id>'
2015-07-16 15:24:37 +01:00
Paul Bellamy c6fa9cb17e refactor weaveCIDRsFromConfig to return an error message 2015-07-16 15:15:32 +01:00
Matthias Radestock aa3f35e38c remove stale reference to weave launch-dns 2015-07-16 14:41:57 +01:00
Tom Wilkie db77570779 Test is too long 2015-07-16 13:40:39 +00:00
Paul Bellamy 9619a27f8c [proxy] adding log message for ignored containers 2015-07-16 14:01:22 +01:00
Paul Bellamy c13154eea9 updating comment as per golint 2015-07-16 13:58:30 +01:00
Paul Bellamy 1132d26d47 [proxy] do not weave attach containers in '--net=host|container:<name|id>' 2015-07-16 13:58:27 +01:00
Tom Wilkie 6e5995964d Make 210_dns_cross_hosts_test.sh excercise gossip dns tombstones.
Also, make gce.sh less verbose, and do the env dance correctly.
2015-07-16 12:48:00 +00:00
Adam Harrison da13238368 Merge branch '1.0' 2015-07-16 13:45:36 +01:00
Paul Bellamy 0865f4c207 Merge pull request #1099 from weaveworks/1055-proxy-autoconfig
Proxy autoconfig
2015-07-16 13:33:21 +01:00
Adam Harrison 77bc56751b Merge pull request #1164 from weaveworks/1162_detect_host_netns_in_container
fix detection of host netns when weave script runs inside container
2015-07-16 13:33:19 +01:00
Matthias Radestock b32c017f33 fix detection of host netns when weave script runs inside container
$$ refers to the pid in the container's process namespace, so using it
as a reference into /hostproc is entirely wrong.

Fixes #1162.
2015-07-16 13:19:56 +01:00
Paul Bellamy a4a31e632b [proxy] Choose where to listen based on how launching client connected 2015-07-16 12:44:39 +01:00
Matthias Radestock 23ad547db4 Merge pull request #1155 from weaveworks/dns-gossip
Make DNS gossip converge correctly

Fixes #1151
2015-07-15 20:37:43 +01:00
Matthias Radestock 4d887c8a55 Merge branch '1.0'
Conflicts:
	proxy/create_exec_interceptor.go
	test/610_proxy_wait_for_weave_test.sh
2015-07-15 18:07:11 +01:00
Matthias Radestock a982146cbe Merge pull request #1153 from weaveworks/1143-non-weave-exec
[proxy] should not attempt weavewait in on weave container's execs

Fixes #1143.
2015-07-15 17:40:13 +01:00
Tom Wilkie 73210b6a70 Make sure DNS Gossip converges correctly.
- Abide by gossip interface (return nil, not empty gossip) so gossip converges!
- Make sure we don't say an old entry version is new to us; add some more tests.
- Add dns test for 3 (or N) hosts
- Extend mock gossip router to ensure reconsumption off diffs yield nil
- Don't increment version when re-adding existing entry.
2015-07-15 15:54:52 +00:00
Paul Bellamy e4422205e8 [proxy] should not attempt weavewait in on weave container's execs 2015-07-15 16:14:14 +01:00
Tom Wilkie 9df7358ded Add pprof handler to weaver. 2015-07-15 15:10:22 +00:00
Matthias Radestock 9507ea93dd rename test since we have two 250_* 2015-07-15 14:46:51 +01:00
Matthias Radestock 52d3bcfe8b Merge pull request #1149 from weaveworks/1118-respond-late-paxos
Respond to paxos messages when we already have a ring

Fixes #1118.
2015-07-15 13:24:40 +01:00
Bryan Boreham b3979c9505 Respond to paxos message if we already have a ring 2015-07-15 13:05:25 +01:00
Bryan Boreham 273a0fba47 Add sender to mock broadcast messages 2015-07-15 13:05:25 +01:00
Alvaro 7e34ec61f6 Merge pull request #1142 from weaveworks/1113-recursive-dns
Closes #1113
2015-07-15 13:42:59 +02:00
Bryan Boreham acf13dc157 Add sender to OnGossipBroadcast() 2015-07-15 12:07:56 +01:00
Tom Wilkie 91395d6728 Use Msg.Copy! 2015-07-15 11:05:36 +00:00
Paul Bellamy ef4a227ae9 Merge pull request #1144 from weaveworks/restart-docker-after-vagrant-tweaks
Restart docker after vagrant tweaking and refactor tweak-code
2015-07-15 11:34:59 +01:00
Alfonso Acosta 25589562cd Restart docker after vagrant tweaking and refactor tweak-code
Docker needs to be restarted and not started, since it already starts running after installation
2015-07-15 09:31:47 +00:00
Matthias Radestock 7063ca51aa remove dead code 2015-07-15 05:35:05 +01:00
Matthias Radestock 97498d26f7 remove dead code 2015-07-15 05:32:35 +01:00
Matthias Radestock 36292216fd rename tests so they actually run
These tests were introduced in #1065 and #1126.
2015-07-14 19:47:15 +01:00
Matthias Radestock 86448a1025 fix breakage
This wasn't spotted because #1065, which removed launch-dns, got
merged just before #1126, which introduced this test.
2015-07-14 19:42:59 +01:00
Ilya Dmitrichenko b4f53edd91 Merge pull request #1138 from weaveworks/vagrantfile-update
update default vagrant box to ubuntu 15
2015-07-14 18:21:02 +01:00
Matthias Radestock 3a22d2e6a1 Merge pull request #1135 from weaveworks/issues/1028-weave-dns-lookup
Implement `weave dns-lookup`

Closes #1028.
2015-07-14 18:19:26 +01:00
Matthias Radestock 9a4bbb55da Merge branch '1.0'
Conflicts:
	proxy/common.go
	weave
2015-07-14 18:09:25 +01:00
Matthias Radestock b476bd2afe Merge pull request #1137 from weaveworks/1109-docker-bridge-ip
[proxy] respect custom DOCKER_BRIDGE

Fixes #1109.
2015-07-14 17:52:08 +01:00
Tom Wilkie fa73240812 Copy dns requests and give them a fresh id when doing recursive queries. 2015-07-14 16:44:21 +00:00
Adam Harrison a3dc2d0482 Smoketest dns-lookup 2015-07-14 17:36:30 +01:00
Adam Harrison 41834478d7 Rework documentation to use weave dns-lookup 2015-07-14 17:36:30 +01:00
Adam Harrison 6832ae20b0 Implement weave dns-lookup 2015-07-14 17:36:30 +01:00
Matthias Radestock 7ae06ef00d Merge pull request #1126 from weaveworks/issues/1018-more-flexible-hostname-derivation
[proxy] More flexible container name -> hostname derivation

Closes #1018.
2015-07-14 17:31:53 +01:00
Alvaro a1ac0313e5 Merge pull request #1065 from weaveworks/gossipdns
Gossip-based DNS embedded in the router.
2015-07-14 18:30:16 +02:00
Paul Bellamy d9c5d2f940 update default vagrant box to ubuntu 15 2015-07-14 17:08:26 +01:00
Tom Wilkie 361ed1b4a6 Resolve unqualified domains in the weave.local domain. 2015-07-14 16:05:41 +00:00
Alfonso Acosta dfcc9678f3 Make weave usage fit into 80 columns 2015-07-14 16:02:19 +00:00
Tom Wilkie 8b061fd555 Gossip-based DNS embedded in the router.
- Nameserver holds a sorted list of (hostname, peer, container id, ip) tuples.
- Entries are gossiped using topology gossip / broadcast - only new/updated entries
  are propogated as expected.
- Entries for a given peer are removed when the connection to the peer is lost.
- Entries are tombstoned when containers die.  Tombstones are kept for 10mins,
  as they do not need to survive partition, only gossip propagation delay.
2015-07-14 16:00:38 +00:00
Tom Wilkie 01f7201a9e Remove legacy WeaveDNS code. 2015-07-14 16:00:28 +00:00
Paul Bellamy acd8bdec21 [proxy] respect custom DOCKER_BRIDGE 2015-07-14 16:33:35 +01:00
Alfonso Acosta ded3d6b40b Address PR comments 2015-07-14 15:31:44 +00:00
Tom Wilkie f7739ef04c Don't fail if docker rmi fails 2015-07-14 14:50:39 +00:00
Alfonso Acosta 4f82dc97fd [proxy] More flexible container name -> hostname derivation
Provides to new two new flags: `--hostname-match <regexp>`
and `--hostname-replacement <replacement>`. When launching a container, its name
matched against regular expression `<regexp>`. Then, based on that match,
`<replacement>` will be used to generate a hostname, which will ultimately be
handed over to weaveDNS for registration.

The substitution is implemented with
https://golang.org/pkg/regexp/#Regexp.ReplaceAllString . Thus, references to
regexp substitution groups should be prepended with a dollar sign.

For instance, if we provide `--hostname-match '^aws-[0-9]+-(.*)$'` and
`--hostname-replacement 'my-app-$1'`, running a container named
`aws-12798186823-foo` through WeaveProxy will lead to WeaveDNS registering
`my-app-foo`.

Closes #1018
2015-07-14 14:41:44 +00:00
Matthias Radestock 16471d0a28 Merge branch '1.0'
Conflicts:
	site/features.md
	test/110_encryption_test.sh
2015-07-14 15:41:10 +01:00
Matthias Radestock 6386c1bd18 Merge branch 'latest_release_doc_updates' into 1.0 2015-07-14 15:36:55 +01:00
Matthias Radestock 2e93a4c18e Merge pull request #1132 from weaveworks/issues/945-secure-router-config
Improvements to password handling recommendations

Closes #945.
2015-07-14 15:31:20 +01:00
Matthias Radestock 0abd134a0d Merge pull request #1133 from weaveworks/revert-1050-issues/987
Revert "Resolve unqualified names in the local WeaveDNS domain"
2015-07-14 15:06:19 +01:00
Matthias Radestock 18032015b9 Revert "Resolve unqualified names in the local WeaveDNS domain" 2015-07-14 16:06:05 +01:00
Adam Harrison e26e7e29f2 Improvements to password handling recommendations 2015-07-14 14:48:58 +01:00
Matthias Radestock 39a1f2163f Merge pull request #1050 from weaveworks/issues/987
Resolve unqualified names in the local WeaveDNS domain

Closes #987.
2015-07-14 14:43:29 +01:00
Alvaro Saurin 4c519fc761 Smoke tests 2015-07-14 15:12:40 +02:00
Matthias Radestock bbacaa42b8 cosmetic 2015-07-14 13:23:28 +01:00
Matthias Radestock 2f35ad1d42 Merge pull request #1098 from dpw/encrypt-features
New connection protocol version with encrypted features map

Closes #1029. Closes #912.
2015-07-14 13:20:18 +01:00
Bryan Boreham 6d17fe5df5 Need to restart docker after systemd reconfigure 2015-07-14 12:12:35 +01:00
Bryan Boreham 907de67a40 Merge pull request #1125 from weaveworks/smoke-overlay
Upgrade smoke-tests to use 'overlay' storage and Ubuntu 15.04
2015-07-14 11:51:53 +01:00
Tom Wilkie 499d65e715 Build cover tool by default, and do go get -t for tests, irregardless of $SLOW 2015-07-14 10:50:34 +00:00
Alvaro Saurin efb0ead9ad Resolve unqualified names in the local WeaveDNS domain
Unit tests
2015-07-14 10:46:41 +02:00
Matthias Radestock 85b4a05c9a Merge branch '1.0' 2015-07-13 19:07:47 +01:00
Matthias Radestock 1f3d054b09 Merge pull request #1124 from weaveworks/issues/1037-mitigate-entropy-depletion
Mitigate entropy depletion

Closes #1037.
2015-07-13 19:06:54 +01:00
Bryan Boreham 4853a1da05 Tell Docker to use 'overlay' storage driver 2015-07-13 18:28:29 +01:00
Bryan Boreham db8016112e Upgrade to Ubuntu 15.04 2015-07-13 18:28:11 +01:00
Tom Wilkie 4eb6d147b5 Merge pull request #1121 from weaveworks/test_large_packets
test that large packets can be transmitted over weave
2015-07-13 17:06:28 +01:00
Adam Harrison e2f8b6f9fd Document /dev/random blocking issue 2015-07-13 15:02:31 +01:00
Adam Harrison f3c4a9fc6d Limit router accept rate with token bucket 2015-07-13 15:02:31 +01:00
Adam Harrison 9634526a67 Add token bucket implementation 2015-07-13 15:01:04 +01:00
Adam Harrison b67c102fe5 Remove simple sleep from router accept 2015-07-13 15:01:04 +01:00
Tom Wilkie 1e39b2bdcf Merge pull request #1123 from weaveworks/remove-danglies
Remove Docker dangling images in smoke-tests
2015-07-13 14:38:04 +01:00
Adam Harrison d6443809bd Merge pull request #1120 from weaveworks/998-race
Various improvements to unit test running
2015-07-13 14:09:45 +01:00
Bryan Boreham 80e56d67db Remove Docker dangling images after copying new ones 2015-07-13 14:03:47 +01:00
Tom Wilkie 24a4d6501e Merge pull request #1117 from weaveworks/multi_hop_tests
Multi hop tests
2015-07-13 11:39:06 +01:00
Bryan Boreham 5e43f0fd4c Merge pull request #1079 from weaveworks/68-rewrite-etc-hosts
LGTM.  Closes #68, with remainder to #1122
2015-07-13 11:03:20 +01:00
Tom Wilkie 13f1239a3e Various improvements to unit test running
- units.sh should exit on error.
- Add race detector to unit test runner.
- Use local testing/cover/cover tool in gen_coverage_report.sh and units.sh
- Use the scheduler to parallelise the unit tests.
- Make test run id a string and differentiate between unit runs and integration runs in the scheduler.
- Pipe circle env vars through to build container so we can detect when to use the scheduler
- Add python requests to the weave build container.
- Quote test names in urls, make flask handle url segments with slashes in.
2015-07-13 09:33:52 +00:00
Matthias Radestock 9463ceb873 test that large packets can be transmitted over weave 2015-07-13 10:31:20 +01:00
Matthias Radestock 7aab5cc5b4 refactor: simplify NewPeers 2015-07-11 17:46:57 +01:00
Matthias Radestock 2b61bc91df cosmetic: remove blank log lines 2015-07-11 16:43:33 +01:00
Matthias Radestock 57a4371e9f attempt to cure spurious test failures 2015-07-11 16:31:16 +01:00
Matthias Radestock 1d2e6b4c56 test gossip forwarding 2015-07-11 16:17:36 +01:00
Matthias Radestock 2a0bc91f86 add test for peer discovery and multi-hop routing 2015-07-11 14:57:24 +01:00
Matthias Radestock 210b926064 increase number of test VMs from 2 to 3 2015-07-11 14:57:24 +01:00
Matthias Radestock 9a089a111f don't invoke weave stop-router at end of tests
since that removes the container, rather than just stopping it.
2015-07-11 14:56:11 +01:00
Matthias Radestock 710eabeff3 reduce test vm memory 2015-07-11 12:15:46 +01:00
Matthias Radestock 603f34e944 oops, forgot to commit this 2015-07-11 12:14:43 +01:00
Matthias Radestock a3dff7e1e9 cope with more than two test hosts 2015-07-11 11:59:34 +01:00
Matthias Radestock 25486f7b4d cosmetic: s/weave_stop_router/stop_router_on
Also run `weave stop-router` as part of this, so conntrack cleanup
happens.
2015-07-11 11:58:29 +01:00
Matthias Radestock c22d824db8 .gitignore and clean up coverage reports 2015-07-11 11:57:01 +01:00
Matthias Radestock a44a3a4158 cure "./gen_coverage_reports.sh: line 5: CIRCLECI: unbound variable" 2015-07-11 11:46:49 +01:00
Matthias Radestock 982c9d7650 remove dead code 2015-07-11 11:17:44 +01:00
Matthias Radestock 7042c1506a cosmetic: consistency with other tests 2015-07-11 03:36:21 +01:00
Matthias Radestock 76943f0b17 cosmetic 2015-07-11 02:52:30 +01:00
Matthias Radestock c4f516655d refactor: simplify ipam claim try 2015-07-11 02:28:14 +01:00
Matthias Radestock aaba681db1 remove dead code 2015-07-11 01:54:37 +01:00
Tom Wilkie 56a169b50f Merge pull request #1115 from weaveworks/unit-coverage
Run unit tests in circle and include their coverage in the report.
2015-07-10 19:34:48 +01:00
Tom Wilkie 32b2ec4038 Run unit tests in circle and include their coverage in the report. Report coveralls.io from Circle. 2015-07-10 18:19:50 +00:00
Matthias Radestock ce5052305d fix CircleCI breakage 2015-07-10 18:44:30 +01:00
Matthias Radestock 0e94e3e07b complain even less about absent weave container in coverage collection 2015-07-10 18:30:13 +01:00
Matthias Radestock 3d72670105 don't complain in coverage collection when weave container is absent 2015-07-10 18:24:19 +01:00
Tom Wilkie f8718dcc5d Remove duplication of coverage stanza in weave script. 2015-07-10 16:35:46 +00:00
Paul Bellamy fa6777004f Review feedback 2015-07-10 17:34:06 +01:00
Paul Bellamy e3212d462d Merge pull request #1105 from weaveworks/673-smoke-test-coverage
Code coverage for smoke tests.
2015-07-10 16:53:59 +01:00
Tom Wilkie f5186e74c0 Review feedback 2015-07-10 15:33:00 +00:00
Tom Wilkie bf814739ba Reduce repetition in Makefile. 2015-07-10 15:03:54 +00:00
Tom Wilkie 00a7490a1d Code coverage for smoke tests.
Allow non-circle people to build coverage reports.
2015-07-10 14:45:14 +00:00
Adam Harrison da41604229 Fix IPAM test compilation 2015-07-10 15:43:28 +01:00
Adam Harrison d5a4326b9e Merge branch '1.0'
Conflicts:
	ipam/allocator_test.go
	ipam/claim.go
2015-07-10 14:35:41 +01:00
Adam Harrison 835d99e6fe Merge pull request #1104 from weaveworks/1083-async-claim
Don't wait to claim addresses from IPAM
2015-07-10 14:03:05 +01:00
Paul Bellamy e4458372e8 [proxy] weavewait rewrites /etc/hosts so weave ip is returned first from 'hostname -i'
Adds a --no-rewrite-hosts flag to the proxy, and be default, removes the
docker IP from hosts. We only discard the docker host when it exists, so
--net=none works.
2015-07-10 13:57:20 +01:00
Matthias Radestock a05c65c73b simplify feature extraction 2015-07-09 14:38:08 +01:00
David Wragg 14d346db8b Add a --protocol-min-version option to router
To allow users to forgo compatibility with Weave Net 1.0 in order to
avoid #912, by setting --protocol-min-version=2.
Defaults to --protocol-min-version=1, i.e. compatible but vulnerable.
2015-07-09 14:38:06 +01:00
David Wragg 882f14d50c Version 2 protocol, which encrypts the features map
With V1 compatibility
2015-07-09 14:37:42 +01:00
Bryan Boreham 75646d4786 Merge remote-tracking branch 'origin/1.0'
Conflicts:
	router/connection.go
2015-07-09 12:56:37 +01:00
Bryan Boreham b93070f91a Fix log message on peer move 2015-07-09 12:51:52 +01:00
Bryan Boreham cbcd5da680 Fix log message on peer move 2015-07-09 12:33:39 +01:00
Bryan Boreham cf4ce0a07d Don't cancel address claim when caller closes socket 2015-07-09 10:47:34 +01:00
Bryan Boreham 78c4b06492 Don't wait for certainty before responding to Claim
Conflicts:
	ipam/claim.go
2015-07-09 10:47:27 +01:00
David Wragg 501f40d9c1 Consolidate remote peer checks 2015-07-08 17:21:32 +01:00
Matthias Radestock 08463ea403 Merge branch '1.0' 2015-07-08 16:54:24 +01:00
Matthias Radestock 2ced8b9469 Merge branch 'latest_release_doc_updates' into 1.0 2015-07-08 16:53:13 +01:00
Matthias Radestock 26c1bcf866 clarify role of IP allocator range
Part of #1035.
2015-07-08 16:51:25 +01:00
Tom Wilkie 89ad7e1ba2 Move test gossip router out of ipam code for use in gossip dns code. 2015-07-08 15:03:13 +00:00
Matthias Radestock e6828e1aa5 Merge pull request #1101 from weaveworks/misc-changes
Extend router/peers.go to allow multiple OnGC callbacks
2015-07-08 16:00:22 +01:00
Tom Wilkie c8718d85dc Extend router/peers.go to allow multiple OnGC callbacks, and call the callbacks outside of the peers lock. 2015-07-08 14:47:02 +00:00
Tom Wilkie 56863c3d0e Ensure bin/lint doesn't complain about removed files when used as a git precommit script. 2015-07-08 14:43:21 +00:00
David Wragg e50b1f1fe7 Remove the "found" return value from Peers Fetch method
Callers can simply check whether the returned *Peer is nil.
2015-07-08 09:06:33 +01:00
David Wragg 5e10b901a3 Drop the check for duplicate connections in the handshake
Duplicate connections are also detected and handled in AddConnection,
I have not been able to reproduce #182 without the check, so although
it may once have been necessary, it doesn't seem to be needed now.
2015-07-08 09:03:51 +01:00
David Wragg a6a74001bd Make TCPReceiver more closely mirror TCPSender 2015-07-08 08:53:53 +01:00
Bryan Boreham 0b032386c1 Merge pull request #1092 from weaveworks/1082_usage_to_stderr
Note I have rebased this and merged changes.  Closes #1082.
2015-07-07 15:43:23 +01:00
Matthias Radestock 8eb092c754 display usage on stderr
so that the likes of $(weave proxy-typo-env) don't do weird things

Closes #1082.
2015-07-07 15:39:47 +01:00
Alvaro 33921c2cfe Merge pull request #1073 from weaveworks/971_check_container_alive_in_api
make container aliveness check part of the ipam & dns API
2015-07-07 16:08:40 +02:00
Matthias Radestock 6cc566fd3e Merge pull request #1090 from weaveworks/1076-ignore-iface
Don't complain about weave bridge routes clashing

Fixes #1076.
2015-07-07 13:12:41 +01:00
Matthias Radestock 491a4369d7 Merge pull request #1096 from squaremo/issue1087_password_equals
Handle --option=value as well as --option value

Closes #1087.
2015-07-07 12:57:01 +01:00
Bryan Boreham 6666fd14ac Allow multiple --ignore-iface arguments 2015-07-07 12:52:29 +01:00
Bryan Boreham e621aca77e Added --ignore-iface option to netcheck and use it to ignore weave bridge route 2015-07-07 12:52:29 +01:00
Adam Harrison 3e7c20b4ee Merge branch '1.0' 2015-07-07 12:46:00 +01:00
Michael Bridgen 22446ffc19 Handle --option=value as well as --option value
In any place we munge options, treat the form with an equals the same
as the form without.

The exes ultimately invoked by the script handle both forms, so unless
we need to do something with the option in the script, we can just let
options fall through.
2015-07-07 12:44:37 +01:00
Matthias Radestock f39791616d Merge pull request #1095 from weaveworks/move-ipam-address
Move ipam/address to net/address

Closes #1065.
2015-07-07 11:43:47 +01:00
Paul Bellamy d39aaa86ee Merge pull request #1084 from squaremo/issue602_moar_hyphens
Consistent option naming
2015-07-07 11:37:21 +01:00
Tom Wilkie 4fdb14293b Move ipam/address to net/address 2015-07-07 10:33:20 +00:00
Michael Bridgen 4ede33468e Update docs and smoke-tests with new option names 2015-07-07 11:26:09 +01:00
Michael Bridgen ebbb5d379d Usage and deprecation warnings in script
The usage is rearranged slightly to avoid wrapping at 80 characters.

This will tend to overestimate warnings, since it doesn't try very
hard to ignore values that are not intended to be options; in
particular, arguments that are intended for `docker run`. (Using `--`
to delimit these arguments would make this easier!)

It does ignore values that follow known options though, for instance
in

        weave -nickname "-password"

(-nickname results in a deprecation warning, -password does not)

and

        weave --password "-iprange"

(no deprecation warning, since -iprange is ignored)
2015-07-07 11:26:09 +01:00
Michael Bridgen 379b8c4e6b Standardise on options --like-this
Hyphenate words, and always expect double dashes for long options. A
small number of options have entirely new names.

In general I have added aliases for the unhyphenated and single-dashed
varieties, and used mflag's deprecation syntax to make it print a
warning when it sees them. I left some abbreviated options
unhyphenated (e.g., `bufsz`), and the TLS options as-is since they
correspond with Docker options.

NB the deprecation wanrings will mostly get printed to container logs,
since mostly the executables are run in containers. The next step is
to emit warnings from the script for the options mentioned in the
script usage message.
2015-07-07 11:13:30 +01:00
Adam Harrison bde445df9b Merge pull request #1089 from weaveworks/1086_dns_search_without_equals
detect `--dns-search <arg>`
2015-07-07 10:16:21 +01:00
Matthias Radestock 41b0c2a03f Merge pull request #1070 from weaveworks/1036-change-iprange
Change default IP allocation range from 10.128/10 to 10.32/12

Closes #1036.
2015-07-07 02:24:13 +01:00
Matthias Radestock 02715f88d5 cosmetic: re-order option parsing
to match the order in which we susequently process them.
2015-07-06 17:26:04 +01:00
Matthias Radestock 706f9ee352 detect --dns-search <arg>
...and not just `--dns-search=<arg>`

Fixes #1086.
2015-07-06 17:22:42 +01:00
Bryan Boreham 646cf84b4a Merge pull request #1072 from weaveworks/1043_common_launch_args
LGTM
2015-07-06 17:15:51 +01:00
Bryan Boreham f593c2fbf2 Change default IP allocation range from 10.128/10 to 10.32/12 2015-07-06 17:09:05 +01:00
Bryan Boreham bd24307c2b Merge pull request #1069 from 2opremio/fons/fix-tests-OSX
LGTM
2015-07-06 16:43:26 +01:00
Michael Bridgen e0c7054f9b Merge pull request #1075 from weaveworks/fons/test-vagrant-creation
Explain how to create the test VMs from vagrant
2015-07-06 16:31:22 +01:00
Michael Bridgen 59550e0fcb Merge branch 'pr1077' 2015-07-06 16:27:45 +01:00
Michael Bridgen af64c8e2af Merge branch '1063_status_json_accept_header' of github.com:weaveworks/weave into pr1077 2015-07-06 16:25:58 +01:00
Matthias Radestock 03ac7a86c6 make bin/multiweave work again in 'launch launches everything' world 2015-07-06 15:13:21 +01:00
Matthias Radestock 80a232f44c copy latest random_mac() from weave script to bin/multiweave 2015-07-06 15:09:23 +01:00
Matthias Radestock d1041c17d3 Merge pull request #1081 from weaveworks/weave_1.1_readme_updates_proxy_env
Remove weave proxy-(env|config) for just (env|config). Readme changes
2015-07-06 15:05:05 +01:00
Matthias Radestock cf7fff3da2 Merge pull request #1080 from weaveworks/1014-weave-env
rename proxy-(env|config) to (env|config)

Closes #1014.
2015-07-06 15:04:40 +01:00
Alfonso Acosta 373d663b5e Fix integration tests for OSX 2015-07-06 14:48:53 +01:00
Alfonso Acosta e86d255a47 Add comment about running integration tests from the Vagrant build VM 2015-07-06 14:07:16 +01:00
Paul Bellamy 5c44f74475 rename proxy-(env|config) to (env|config)
Keep error message to stderr, as the proxy should be launched with
'weave launch'. 'proxy-(env|config)' are kept with a deprecation
warning, for a while.
2015-07-06 13:41:23 +01:00
Paul Bellamy d649bdddc7 Remove weave proxy-(env|config) for just (env|config). Readme changes 2015-07-06 12:59:07 +01:00
Matthias Radestock 5c7763b075 Merge branch '1.0' 2015-07-06 11:00:15 +01:00
Matthias Radestock e3ab396428 Merge branch 'latest_release_doc_updates' into 1.0 2015-07-06 10:59:34 +01:00
Matthias Radestock df31454c30 Merge commit 'c8032b6' into latest_release_doc_updates 2015-07-06 10:58:18 +01:00
Matthias Radestock c8bd47e948 set correct content-type header when returning json status 2015-07-05 19:25:28 +01:00
Matthias Radestock 098fc81825 output json status based on 'Accept' header
...instead if having a separate URL.

Closes #1063.
2015-07-05 19:19:02 +01:00
Alfonso Acosta cab4a7e1ee Explain how to create the test VMs from vagrant 2015-07-05 18:59:53 +01:00
Matthias Radestock 6596c8b1a7 make container aliveness check part of the ipam & dns API
...rather than conducting it implicitly when an ID "looks like" a
container ID.

Closes #971
2015-07-04 19:13:13 +01:00
Matthias Radestock 5559f1df2e cosmetic: remove some unnecessary quoting
for consistency, since we don't quote the same term anywhere else
2015-07-04 18:14:13 +01:00
Matthias Radestock 156597a965 small simplifying refactor: use shortcircuit boolean expressions 2015-07-04 18:04:25 +01:00
Matthias Radestock d0641062c0 pass common launch args to all three components
ATM that's just --loglevel=...

Closes #1043.
2015-07-04 04:38:18 +01:00
Matthias Radestock cf4fffa394 Merge pull request #1057 from weaveworks/329-log-level-choice
Give the user more choice of log levels

Closes #329
2015-07-04 03:46:59 +01:00
Matthias Radestock b30de63f18 Merge pull request #1067 from paulbellamy/proxy-stderr
print all stderr output from callWeave to the proxy logs, even if successful

Closes #1056
2015-07-04 03:44:50 +01:00
Bryan Boreham ff2d7451e2 Remove -debug option, superceded by --log-level 2015-07-04 03:27:07 +01:00
Matthias Radestock 910b57731d remove stray -debug in tests 2015-07-04 03:19:27 +01:00
Matthias Radestock b8aa1d473b remove dead code 2015-07-04 02:53:36 +01:00
Matthias Radestock ce26f88ee2 Merge branch '1.0'
Conflicts:
	router/crypto.go
	router/ethernet_decoder.go
	router/handshake_test.go
	router/mocks_test.go
	router/peers_test.go
	router/router.go
	router/udp_sender.go
2015-07-03 20:47:29 +01:00
Matthias Radestock bf73029d15 cosmetic: goimports -w . 2015-07-03 20:41:35 +01:00
Matthias Radestock 4dbd657a26 import gopacket from github instaead of google code
The latter redirects to the former, which breaks `go get`.

Closes #1071.
2015-07-03 20:32:27 +01:00
Matthias Radestock 1524796408 Merge pull request #1068 from weaveworks/1004-seed-random
Seed random-number generator from the host time

Fixes #1004.
2015-07-03 16:01:16 +01:00
Paul Bellamy 7821412bf7 [proxy] print all stderr from callWeave to the proxy logs, even if successful 2015-07-03 15:45:09 +01:00
Matthias Radestock b72b7a9ed5 Merge branch '1.0' 2015-07-03 14:28:42 +01:00
Matthias Radestock a3a7a19870 simplifying refactor 2015-07-03 14:28:18 +01:00
Matthias Radestock 8b0089565f Merge branch '1.0' 2015-07-03 14:18:46 +01:00
Matthias Radestock 251c68e85a Merge pull request #1064 from weaveworks/issues/1023-await-ipam-forever
Wait forever for IPAM

Fixes #1023.
2015-07-03 14:17:52 +01:00
Adam Harrison 227cb11f25 Router, DNS and proxy wait forever by default 2015-07-03 14:01:34 +01:00
Adam Harrison 6097dc8aaa Add wait forever capability to EnsureInterface 2015-07-03 14:01:34 +01:00
Bryan Boreham 8c6c692926 Seed the math/rand generator from the time 2015-07-03 13:39:47 +01:00
Matthias Radestock 7554bd3913 Merge branch 'master' into weave_1.1_readme_updates
Conflicts:
	README.md
2015-07-03 11:55:32 +01:00
Matthias Radestock b81fce32f6 one more s/proxy/Weave Docker API proxy 2015-07-03 11:51:12 +01:00
Matthias Radestock 3ea1448c52 cosmetic 2015-07-03 11:49:20 +01:00
Matthias Radestock f31a3c1261 Merge branch '1.0' 2015-07-03 11:48:07 +01:00
Matthias Radestock 9e32605183 Merge branch 'latest_release_doc_updates' into 1.0 2015-07-03 11:45:37 +01:00
Matthias Radestock 9009e0eb7f one more s/proxy/Weave Docker API proxy 2015-07-03 11:45:05 +01:00
Matthias Radestock 243d445d1b Merge branch 'latest_release_doc_updates' into 1.0 2015-07-03 10:59:10 +01:00
Matthias Radestock 65cd2b0e04 missed one 2015-07-03 10:58:22 +01:00
Matthias Radestock 33b230c114 Merge branch 'latest_release_doc_updates' into 1.0 2015-07-03 10:56:00 +01:00
Matthias Radestock 6389af6ea2 Merge pull request #1066 from weaveworks/1019_rename_proxy_in_docs
introduce 'Proxy' as 'Docker API Proxy'

Closes #1066.
2015-07-03 10:55:17 +01:00
Matthias Radestock fcea6fcbf8 Merge branch '1.0' 2015-07-03 10:03:43 +01:00
Matthias Radestock 8b35f5e8fe fix typo in comment 2015-07-03 10:03:07 +01:00
Matthias Radestock 7d5dcfafc9 introduce 'Proxy' as 'Docker API Proxy'
in order to reduce potential confusion about its role.

Closes #1019.
2015-07-03 03:33:23 +01:00
Matthias Radestock 07ee2df822 cosmetic(ish): use dotted import. like everywhere else 2015-07-02 21:07:51 +01:00
Paul Bellamy 5b79e274e8 fixup logging changes, so it compiles 2015-07-02 17:04:21 +01:00
Matthias Radestock e25d1b8365 Merge branch '1.0' 2015-07-02 16:35:40 +01:00
Matthias Radestock af40ebcab4 Merge pull request #1062 from weaveworks/issues/1006-fix-hostname-derivation
Disable hostname derivation when container name is too long

Fixes #1006.
2015-07-02 16:34:40 +01:00
Paul Bellamy 3fd146557a Merge pull request #1060 from weaveworks/issues/1059-remove-google-code-refs
Remove all the imports from "code.google.com"
2015-07-02 15:59:57 +01:00
Adam Harrison d427c99161 Add smoke test 2015-07-02 14:29:38 +01:00
Adam Harrison d1a887857d Enforce maximum length on weave run hostname derivation 2015-07-02 14:29:38 +01:00
Adam Harrison 3914c4c018 Enforce maximum length on proxy hostname derivation 2015-07-02 14:29:34 +01:00
Alvaro Saurin f6ffd604dd Removed all the imports from "code.google.com" 2015-07-02 13:32:52 +02:00
Matthias Radestock 18a4c14670 clarify network/address route overlap error/warning message
Closes #1034.
2015-07-02 11:51:22 +01:00
Matthias Radestock 0f67dbd8bd Merge branch '1.0' 2015-07-02 11:48:22 +01:00
Matthias Radestock 3bd5ae56f0 fix make update
Trying to build the thing for which we want to fetch the dependencies
is rather silly.
2015-07-02 11:46:48 +01:00
Matthias Radestock ecfc5d091d Merge pull request #1053 from weaveworks/437-check-address
Check addresses supplied by users for conflict with existing routes.

Fixes #437. Fixes #480.
2015-07-01 15:03:28 +01:00
Bryan Boreham 84238a0262 Amend error message to reflect usage situation 2015-07-01 14:58:42 +01:00
Matthias Radestock edf3ec9f09 Merge pull request #1044 from weaveworks/664-use-logrus
Use off-the-shelf logger logrus

Closes #664.
2015-07-01 14:47:52 +01:00
Matthias Radestock 2fee0f0184 Merge branch '1.0' 2015-07-01 14:02:57 +01:00
Bryan Boreham a5ddb63fca Remove Info.Print in favour of Log.Info 2015-07-01 14:01:45 +01:00
Matthias Radestock a05a3c4b26 Merge pull request #1054 from weaveworks/1042-reset-first-10
Call `weave reset` earlier in smoke-test set-up, to improve reliability

Fixes #1042.
2015-07-01 14:01:29 +01:00
Bryan Boreham a938608cc8 Call weave reset before deleting containers, to improve clean-down 2015-07-01 13:19:43 +01:00
Bryan Boreham 013380c7e7 Update router code to use common logger 2015-07-01 12:26:23 +01:00
Bryan Boreham 13c0b62f96 When looking at proxy logs, use both stderr and stdout 2015-07-01 12:26:23 +01:00
Bryan Boreham 7276fc1d4b Wire up selection of logging levels to match old behaviour
- note that all output is now going to stderr.
2015-07-01 12:26:23 +01:00
Bryan Boreham 67a6961b4b Adjust logrus feature of padding all lines less than 44 chars with spaces at the end 2015-07-01 12:26:23 +01:00
Bryan Boreham d82578d895 Replace logging calls with new style 2015-07-01 12:26:23 +01:00
Bryan Boreham c786e7eb5a Switch from basic logger to logrus 2015-07-01 12:26:23 +01:00
Bryan Boreham 91099694c9 Send error message to stderr 2015-07-01 12:17:02 +01:00
Bryan Boreham c9a20ae10c Warn user if they are trying to use a specific address that clashes with an existing route 2015-07-01 12:17:02 +01:00
Bryan Boreham f05225da22 Check specific addresses differently to networks
For a network, as used by IPAM, we care if *any* address in the range
could possibly conflict with an existing address routed by the host.
When given a single address, we only care if *that specific address*
conflicts with an existing route.
2015-07-01 12:15:04 +01:00
Matthias Radestock badfdee5e4 Merge branch '1.0' 2015-07-01 11:34:50 +01:00
Matthias Radestock ba1e75405f Merge branch 'latest_release_doc_updates' into 1.0 2015-07-01 11:34:24 +01:00
Matthias Radestock 0f5f671054 Merge pull request #1051 from weaveworks/issues/1046-clarify-weavedns-docs
Draw attention to --hostname precedence

Fixes #1046.
2015-07-01 11:33:24 +01:00
Adam Harrison 5b9d350002 Draw attention to --hostname precedence 2015-07-01 11:15:08 +01:00
Bryan Boreham 6403e7a11c Add missing rule to rebuild netcheck when its source changes 2015-07-01 11:13:40 +01:00
Matthias Radestock 312122e1be fix typo 2015-07-01 10:30:12 +01:00
Matthias Radestock bb1da63bfc Merge pull request #1048 from squaremo/issue1040_eliminate_ipam_mentions
Remove references to 'IPAM' in docs and UI

Closes #1040.
2015-06-29 23:15:58 +01:00
Matthias Radestock d1b2e74985 Merge pull request #1045 from squaremo/issue1041_usage_for_initpeercount
Add -initpeercount to usage

Closes #1041.
2015-06-29 19:48:52 +01:00
Michael Bridgen c0b2fe3e3b Remove references to 'IPAM' in docs and UI
The option `--no-default-ipam` is replaced with
`--no-default-ipalloc`; the former will still work (in a 'last one
wins' way), but its usage message says it's deprecated.
2015-06-29 19:20:26 +01:00
Michael Bridgen 864461918f Add -initpeercount to usage 2015-06-29 17:06:14 +01:00
Matthias Radestock 9127eb8acf cosmetic: make weave run usage fit in 80 columns 2015-06-29 16:56:57 +01:00
Paul Bellamy 8e629d9471 Merge pull request #999 from paulbellamy/scope-tests
Let Scope use these integration test helpers
2015-06-29 13:51:26 +01:00
Matthias Radestock ab943d938a refactor: don't return encoder from handshake()
We did this for symmetry/completeness, but the encoder is actually
"consumed" by handshake() in the creation of the tcp senders, hence it
is in fact dangerous to return it.
2015-06-29 12:38:21 +01:00
David Wragg d6fedc1e6e Remove error return value from DecodeLayers
It is never checked, so add a comment explaining why.
2015-06-29 10:47:56 +01:00
David Wragg 3222478449 Remove the LogFrame when fragmenting
Subsequent commits make this LogFrame call a little tricky to retain,
and it's not clear that it is really needed.
2015-06-29 10:47:26 +01:00
Paul Bellamy 26cb65c357 making test helpers more robust so scope can use them 2015-06-29 10:22:10 +01:00
Paul Bellamy 1ef3bd3a8b add docker version and weave version to test sanity checks 2015-06-29 10:20:55 +01:00
Matthias Radestock 51ecf05091 cosmetic: fix typo in comment 2015-06-27 09:35:14 +01:00
Bryan Boreham 565454d3cb fixed Logf format specifier 2015-06-26 17:22:35 +01:00
Matthias Radestock 906ff210fe Merge pull request #1026 from weaveworks/1003-proxy-unix-socket
Make proxy listen on unix:///var/run/weave.sock

Closes #1003
2015-06-26 16:44:41 +01:00
Tom Wilkie c4565f6ed3 Make proxy listen on /var/run/weave.sock 2015-06-26 15:08:21 +00:00
Matthias Radestock 8dd9020803 Merge branch '1.0'
Conflicts:
	ipam/allocator_test.go
2015-06-26 14:08:09 +01:00
Matthias Radestock ec62029db5 Merge pull request #1024 from weaveworks/982-fix-mock-gossip3
Flush messages from mock gossip in IPAM test

Fixes #982.
2015-06-26 13:44:57 +01:00
Matthias Radestock 55fbffef75 Merge pull request #1009 from awh/weave_launch_all_the_things
Weave launch all the things

Closes #962.
2015-06-26 13:21:44 +01:00
Bryan Boreham c84c5dbcdf Make TestGossipRouter goroutine easier to follow 2015-06-26 11:53:14 +01:00
Bryan Boreham 6725c8355f Check the result of Allocate in TestFakeRouterSimple() 2015-06-26 11:53:03 +01:00
Bryan Boreham c659e3448a Add a flush() function to the mock gossip router, and call it where required 2015-06-26 11:40:58 +01:00
Bryan Boreham 959e5f0082 Use different object types for mock gossip messages 2015-06-26 11:38:25 +01:00
Bryan Boreham 6488b0d11c More idiomatic exitChan 2015-06-26 11:38:07 +01:00
Matthias Radestock 039a2030db refactor: make DF flag passing logic more obvious
...and provide a better abstraction for sending special frames.
2015-06-26 11:11:17 +01:00
Matthias Radestock 33d85aaee5 Merge pull request #1016 from dpw/defer-df
Don't inspect the DF flag earlier than we need to
2015-06-26 11:10:42 +01:00
Adam Harrison d4c1766532 Fix smoke tests 2015-06-26 11:10:08 +01:00
Matthias Radestock 39a6d1c87e refactor: simplify a test 2015-06-25 20:13:03 +01:00
Matthias Radestock ee2a66323e Merge branch '1.0'
Conflicts:
	ipam/ring/ring_test.go
2015-06-25 19:57:09 +01:00
Matthias Radestock 635a3e5b26 cosmetic: remove attempt at pretty shell fragment
there is just no way to format this so it looks good everywhere, due
to different tab widths.
2015-06-25 19:51:39 +01:00
Matthias Radestock 82da45a638 go get test dependencies
The previous 'fix' wasn't working since it `go get -t`'ed the dirs
where our executables live. Which don't contain any tests.
2015-06-25 19:31:38 +01:00
David Wragg 64ac7745fe Don't inspect the DF flag earlier than we need to 2015-06-25 17:11:52 +01:00
David Wragg 5690902173 Move DF indication into LogFrame 2015-06-25 17:11:20 +01:00
David Wragg 4162dcafd3 Make the full EthernetDecoder available to LogFrame
So that LogFrame can include information about the decoded frame beyond
the ethernet layer.
2015-06-25 17:11:20 +01:00
Matthias Radestock 9253d45968 Merge pull request #1010 from bboreham/896-check-gossip
Try other peers for space if we get errors

Fixes #896
2015-06-25 17:05:40 +01:00
Matthias Radestock ea59c6c065 Merge pull request #1015 from weaveworks/go-get-tests
Extend 'go get' to tests
2015-06-25 17:01:26 +01:00
Bryan Boreham 7c741d6f7e Extend 'go get' to tests
The `-t` flag asks Go to fetch anything required for tests, which is more necessary now we have added testify.
2015-06-25 16:42:53 +01:00
Adam Harrison 99dc70ea7a Eliminate references to proxy and weaveDNS 2015-06-25 15:42:06 +01:00
Bryan Boreham 8a4b0d536e Updated after review feedback 2015-06-25 15:41:06 +01:00
Adam Harrison b78d4f05d0 Use new weave launch command introduced by #962 2015-06-25 14:59:43 +01:00
Bryan Boreham c76f640e2e Simpler implementation of weighted-random order 2015-06-25 14:54:44 +01:00
Adam Harrison a9e3320e7c Update documentation 2015-06-25 14:43:29 +01:00
Bryan Boreham b472da18f0 Improve logging of unicast gossip errors 2015-06-25 14:04:37 +01:00
Bryan Boreham c58cc449c0 More complex ChoosePeersToAskForSpace, with better complexity 2015-06-25 13:19:49 +01:00
Bryan Boreham af9668f130 Keep trying peers to ask for space until we find one that doesn't error 2015-06-25 12:47:38 +01:00
Adam Harrison 08f3d5b8e8 Implement new weave stop behaviour 2015-06-25 12:47:18 +01:00
Adam Harrison 0d5aad3e90 Rename stop to stop-router 2015-06-25 12:47:18 +01:00
Adam Harrison fcd79a78af Implement new weave launch behaviour 2015-06-25 12:47:14 +01:00
Bryan Boreham 9f784b3c31 Report errors in unicasting gossip 2015-06-25 12:22:53 +01:00
Matthias Radestock 657bbbffbc tiny refactor: raise abstraction level 2015-06-24 18:17:15 +01:00
Tom Wilkie 7780ac1078 Fix gce.sh destory timeouts. 2015-06-24 17:13:37 +00:00
Matthias Radestock 76a12edf57 Merge pull request #997 from dpw/eliminate-starts
Eliminate most Start methods in router package
2015-06-24 18:04:57 +01:00
David Wragg 7387caa2eb Eliminate forwarder Start methods 2015-06-24 17:37:07 +01:00
David Wragg 576dd69b0b Add a comment to Router's Start method 2015-06-24 17:37:02 +01:00
David Wragg 5e6b107d01 Eliminate the GossipSender Start method 2015-06-24 17:35:26 +01:00
David Wragg f444ae9224 Eliminate ConnectionMaker Start method 2015-06-24 17:35:26 +01:00
David Wragg 8f5d1eec11 Eliminate the Routes Start method 2015-06-24 17:35:17 +01:00
Matthias Radestock 9a6afcd236 Merge branch '1.0' 2015-06-24 17:26:19 +01:00
Matthias Radestock 5f95de19ce Merge branch 'latest_release_doc_updates' into 1.0 2015-06-24 17:25:51 +01:00
Fintan Ryan 8fb00ff206 Merge pull request #1002 from weaveworks/include-css
Include stylesheet in repo, update footer
2015-06-24 17:09:53 +01:00
Matthias Radestock 94a8338be5 fix typo in comment 2015-06-24 17:02:35 +01:00
David Kaltschmidt 00744fb9b4 use relative paths for stylesheet in docs 2015-06-24 17:54:50 +02:00
David Kaltschmidt 8250e05618 include stylesheet in repo, update footer
need because website's custom.css is fingerprinted
2015-06-24 17:18:54 +02:00
David Wragg 12c17d39cc Merge weaveworks/separate_image_build_and_export (PR #994)
separate the image build and export phases
2015-06-24 15:26:11 +01:00
Adam Harrison 409961fff3 Rename launch to launch-router 2015-06-24 14:30:35 +01:00
David Wragg 159d47a934 Merge rade/992_fractional_sleep (PR #993)
Sleep for 0.1s instead of 1s in the wait_for_status loop.

Closes #992.
2015-06-24 12:44:52 +01:00
Matthias Radestock 4b11874390 sleep for 0.1s instead of 1s in the wait_for_status loop
This reduces `weave launch|launch-dns` execution time.

Closes #992.
2015-06-24 12:16:52 +01:00
Matthias Radestock 3c7cff1c9c Merge pull request #996 from weaveworks/rm_dockercon_pointer
Remove Link to Dockercon from README
2015-06-24 10:47:00 +01:00
Fintan Ryan e3574f243f Remove Link to Dockercon from README 2015-06-24 10:35:12 +01:00
Matthias Radestock bdda9d03ae bump docker version in weave-build docker image
to 1.3.1, the same version we use everywhere else, e.g. in weavexec.
2015-06-24 03:24:58 +01:00
Matthias Radestock ae1805de76 separate the image build and export phases
That way the export can be just a single tar, which is more
convenient. And without introducing unnecessary image builds.
2015-06-24 02:54:45 +01:00
Matthias Radestock 0ce123e63a Merge branch '1.0' 2015-06-24 02:53:06 +01:00
Matthias Radestock 9ae45f6674 Merge branch 'latest_release_doc_updates' into 1.0 2015-06-24 02:52:37 +01:00
Matthias Radestock 6c3aa99595 bump min required docker version for build
to the same version we use everywhere else, e.g. in weaveexec
2015-06-24 02:51:24 +01:00
Matthias Radestock 215d96fdc3 Merge branch '1.0' 2015-06-23 23:18:09 +01:00
Matthias Radestock 2c73fd6dde Merge branch 'latest_release_doc_updates' into 1.0 2015-06-23 23:17:38 +01:00
Matthias Radestock 3b27413887 fix broken link 2015-06-23 23:17:05 +01:00
Matthias Radestock eb9e450cf1 cosmetic 2015-06-23 19:10:10 +01:00
David Wragg 72039bbe5a Eliminate the LocalPeer Start method 2015-06-23 16:08:43 +01:00
David Wragg d6bceb139e Eliminate MacCache Start method 2015-06-23 16:08:43 +01:00
David Wragg a311623bb9 Eliminate the Start method on LocalConnection 2015-06-23 16:08:43 +01:00
Paul Bellamy 95d72a57e8 Merge pull request #989 from rade/prefetch_test_images
prefetch test images
2015-06-23 15:14:59 +01:00
Adam Harrison aa8d9eab53 Merge branch '1.0' 2015-06-23 15:05:32 +01:00
Adam Harrison 17ee829299 Merge branch 'latest_release_doc_updates' into 1.0 2015-06-23 15:05:02 +01:00
Adam Harrison 5db39466e8 Fix broken IPAM link 2015-06-23 15:04:37 +01:00
Matthias Radestock 43b8840dd2 Merge branch '1.0' 2015-06-23 14:58:55 +01:00
Matthias Radestock cc4ab80f62 Merge branch 'latest_release_doc_updates' into 1.0 2015-06-23 14:57:51 +01:00
Matthias Radestock 0e47cee661 remove references to dns, ipam and proxy docs from ToCs
Since they are now explained and referenced prominently from the main
Features documentation.
2015-06-23 14:53:49 +01:00
Adam Harrison 59e667801d Merge GCE fix from branch '1.0' 2015-06-23 14:50:58 +01:00
Adam Harrison d6dca80dd7 Merge branch 'latest_release_doc_updates' into 1.0
Pull in GCE fix
2015-06-23 14:49:36 +01:00
Tom Wilkie 74c391f19c Update gce.sh make_template command, and build a fresh template. 2015-06-23 14:48:48 +01:00
Matthias Radestock ae8172ec97 simplify docker image removal 2015-06-23 14:39:29 +01:00
Adam Harrison d6ab78ff03 Merge documentation from branch '1.0' 2015-06-23 14:36:04 +01:00
Adam Harrison 7e36dc01e8 Merge documentation from branch 'latest_release_doc_updates' into 1.0 2015-06-23 14:35:30 +01:00
Adam Harrison 9020c84f81 Merge pull request #990 from awh/feature_docs_proxy_section
Add docker integration section to feature docs
2015-06-23 14:34:25 +01:00
Adam Harrison d0a1514ab6 Removed redundant further reading section from feature docs 2015-06-23 14:33:33 +01:00
Adam Harrison 06c3a0cf2a Add docker integration section to feature docs 2015-06-23 14:33:29 +01:00
Adam Harrison dde5782625 Merge documentation from branch '1.0' 2015-06-23 14:21:05 +01:00
Adam Harrison a1913130e7 Merge documentation from branch 'latest_release_doc_updates' into 1.0 2015-06-23 14:18:42 +01:00
Matthias Radestock 369bd0f4dc prefetch test images
This gives more consistent test execution times, and also make image
fetch failures more apparent and decoupled from the tests.

We could *always* just run pull, but it's an expensive operation, so
we check for the presence of the image first. The downside is that we
could be running old images. That is not a problem on our CI
infrastructure though since it spins up fresh VMs per for every tun.
2015-06-23 14:16:52 +01:00
Tom Wilkie 3b4a47a961 Update gce.sh make_template command, and build a fresh template. 2015-06-23 13:07:09 +00:00
Matthias Radestock f29ee3ad05 Merge pull request #986 from awh/feature_docs_weavedns_section
Add service discovery section to features docs
2015-06-23 13:55:50 +01:00
Adam Harrison 19a3fc6b9e Add naming and discovery section to features docs 2015-06-23 13:50:37 +01:00
Paul Bellamy 0a287217b6 Merge pull request #955 from paulbellamy/testing
Replacing custom testing helpers with testify, where possible
2015-06-23 12:21:24 +01:00
Adam Harrison 043d15355c Restore commit lost to force push 2015-06-23 11:53:22 +01:00
Matthias Radestock 2c4cebe9d9 Merge branch '1.0' 2015-06-23 10:31:40 +01:00
Matthias Radestock 9ec1ce3225 Merge pull request #981 from weaveworks/970_show_docker_py_test_failures
show docker-py test failures
2015-06-23 10:30:57 +01:00
Matthias Radestock cedf9bb82d Merge branch '1.0' 2015-06-23 09:54:24 +01:00
Matthias Radestock 1b4f83c067 Merge branch 'latest_release_doc_updates' into 1.0 2015-06-23 09:53:44 +01:00
Matthias Radestock c126c403e7 Merge pull request #983 from rade/improve_ipam_doc_integration
improve IPAM doc integration
2015-06-23 09:52:39 +01:00
Matthias Radestock 90b9b3c1bf 'address allocation' is the feature
covering both automatic and manual allocation
2015-06-23 09:50:39 +01:00
Matthias Radestock 1a17b41bc3 reduce test output noise 2015-06-23 09:42:46 +01:00
Matthias Radestock 2e5e0a7730 remove pointless assertion 2015-06-23 09:42:46 +01:00
Matthias Radestock b2f62cc635 show test failure output
step towards fixing #970
2015-06-23 09:42:46 +01:00
Matthias Radestock 2a9f8a3d75 Merge branch '1.0' 2015-06-22 23:00:29 +01:00
Matthias Radestock 0a553d6630 Merge branch 'latest_release_doc_updates' into 1.0 2015-06-22 22:59:16 +01:00
Matthias Radestock e51c240ea1 fix broken links 2015-06-22 22:57:48 +01:00
Paul Bellamy acd5b91eb8 Merge remote-tracking branch 'upstream/1.0' 2015-06-22 17:28:50 +01:00
Paul Bellamy f5201469a3 Merge pull request #980 from paulbellamy/only-one-entrypoint
[proxy] Only prepend weavewait if not already present
2015-06-22 17:28:20 +01:00
Paul Bellamy 34aa2e32d8 [proxy] Only prepend weavewait if not already present
This way `docker commit` will not stack up weavewaits.
2015-06-22 15:24:49 +01:00
Adam Harrison bda245bfdb Merge pull request #968 from rade/954_show_attach_expose_addresses
show IP addresses in attach/expose/detach/hide
2015-06-22 14:49:41 +01:00
Bryan Boreham d1eb2917e8 Merge documentation from branch 1.0 into master 2015-06-22 14:32:12 +01:00
Bryan Boreham 9a631d0ecd Merge documentation from branch 'latest_release_doc_updates' into 1.0 2015-06-22 14:25:56 +01:00
Bryan Boreham c8032b6b25 Merge pull request #977 from bboreham/proxy-net-syntax
Change one IPAM proxy example to use a network
2015-06-22 14:24:24 +01:00
Bryan Boreham 68ca6ebe04 Change one IPAM proxy example to use the 'net:' syntax 2015-06-22 14:15:24 +01:00
Matthias Radestock 9f0cd93859 refactor(ish): get the more fundamental setup out of the way first 2015-06-22 12:50:50 +01:00
Matthias Radestock 7cf0d59003 update docs 2015-06-22 12:34:32 +01:00
Adam Harrison 74f8c07674 Merge documentation from branch '1.0' 2015-06-22 12:05:40 +01:00
Adam Harrison 1bbec56cc7 Merge documenation from branch 'latest_release_doc_updates' into 1.0 2015-06-22 12:02:35 +01:00
Adam Harrison b86bae9c86 Fix incorrect address in service import/export examples 2015-06-22 12:00:57 +01:00
Paul Bellamy 6d89867a74 Merge pull request #978 from rade/972_remove_proxy_d_option
remove proxy -D option
2015-06-22 11:36:37 +01:00
Matthias Radestock ebf725d869 add some tests 2015-06-22 11:26:33 +01:00
Matthias Radestock 257774a093 make weave attach|expose|detach|hide show IP addresses
The output contains one IP address for each addr (i.e. [ip:]<cidr> |
net:<cidr> | net:default) supplied on the command line; or, if none
was specified, a single address from the default IPAM subnet.

We don't want `weave run` to output the addresses, since it is meant
to (only) output the container id. Hence we redirect the `attach`
output to stderr in that case.

Closes #954.
2015-06-22 11:26:33 +01:00
Matthias Radestock 6f798f723a Merge remote-tracking branch 'upstream/1.0' into 1.0 2015-06-22 11:17:40 +01:00
Matthias Radestock 5fbda6b094 Merge remote-tracking branch 'upstream/master' 2015-06-22 11:15:58 +01:00
Matthias Radestock b483be8c4b remove the -D proxy option
It doesn't do anything useful since the weave script needs to be able
to talk to docker on the standard unix socket.

Fixes #972.
2015-06-22 11:13:53 +01:00
Adam Harrison a751237164 Merge documentation from branch '1.0' 2015-06-22 11:00:25 +01:00
Matthias Radestock 0c87dcd656 cosmetic 2015-06-22 11:00:12 +01:00
Adam Harrison b1bd1d4d6a Merge pull request #964 from awh/v1.0_use_proxy_doc_updates
Update documentation to leverage proxy
2015-06-22 10:46:00 +01:00
Matthias Radestock e276f3e026 cosmetic 2015-06-22 10:38:59 +01:00
Adam Harrison 1e638be7ac Remove weave run from how it works 2015-06-22 10:36:56 +01:00
Adam Harrison acd2b9abeb Reword proxy hyperlink text in index 2015-06-22 10:36:56 +01:00
Adam Harrison 6ddecfc764 Update proxy docs 2015-06-22 10:36:56 +01:00
Adam Harrison f15ad71354 Update IPAM docs to use proxy 2015-06-22 10:36:56 +01:00
Adam Harrison 4d54af39b0 Update weaveDNS docs to use proxy 2015-06-22 10:36:56 +01:00
Adam Harrison 81d5a8f4f5 Update features docs to use proxy 2015-06-22 10:36:56 +01:00
Adam Harrison 344119f42a Update README examples to use proxy 2015-06-22 10:36:52 +01:00
Matthias Radestock 84e5659090 Merge branch '1.0' 2015-06-22 10:31:50 +01:00
Matthias Radestock 514db8efdd Merge branch 'latest_release_doc_updates' into 1.0 2015-06-22 10:31:20 +01:00
Paul Bellamy 0693ec55c6 Merge pull request #974 from rade/972_remove_proxy_d_option_from_docs
remove the `-D` proxy option from the docs
2015-06-22 10:29:16 +01:00
Matthias Radestock 56c720b078 remove the -D proxy option from the docs
It doesn't do anything useful since the weave script needs to be able
to talk to docker on the standard unix socket.

Also remove dangling reference to 'localhost', and move note about
firwalls to where it is most pertinent.

Part fix to #972.
2015-06-22 10:20:18 +01:00
Matthias Radestock bbeb7966b4 Merge remote-tracking branch 'upstream/1.0' 2015-06-21 10:42:28 +01:00
Matthias Radestock 403b9654f1 Merge remote-tracking branch 'upstream/1.0' into latest_release_doc_updates 2015-06-21 10:41:38 +01:00
Matthias Radestock 84775e414b Add release 1.0.1 2015-06-21 10:05:52 +01:00
Matthias Radestock edb4911bf6 Merge branch '1.0' 2015-06-21 09:39:45 +01:00
Matthias Radestock d913ca869c Merge branch '967_include_image_name_in_inspect_error' into 1.0 2015-06-21 09:38:42 +01:00
Michael Bridgen e3917121fa Merge remote-tracking branch 'upstream/1.0.0' 2015-06-20 16:45:34 -07:00
Michael Bridgen ede288ad38 Merge pull request #969 from rade/967_include_image_name_in_inspect_error
include image name in errors
2015-06-20 16:41:05 -07:00
Michael Bridgen 58f14551dc Merge branch 'pr969' 2015-06-20 16:28:44 -07:00
Michael Bridgen 97816d6c54 Merge branch '967_include_image_name_in_inspect_error' of https://github.com/rade/weave into pr969 2015-06-20 16:21:20 -07:00
Matthias Radestock 3732f968d0 include image name in errors
The go docker client's ErrNoSuchImage does not include the image
name. However, it is imperative that this name appears in error
messages sent to clients, since the docker client, as of version
1.7.0, only automatically pulls images when their name is present in
the error.

Fixes #967.
2015-06-20 23:36:48 +01:00
Matthias Radestock 939a095bf4 Merge pull request #966 from weaveworks/readme_website_sync
README update to point at weave.works
2015-06-19 20:38:37 +01:00
Fintan Ryan ad0d53f7fe Update proposed text 2015-06-19 19:17:43 +01:00
Fintan Ryan e748ddc20d README update to point at weave.works 2015-06-19 13:00:01 +01:00
Matthias Radestock e51ecd73b3 refactor: extract helper 2015-06-18 19:01:10 +01:00
Matthias Radestock 5479d0a54d improve test performance - only fetch status once per check 2015-06-18 18:53:06 +01:00
Matthias Radestock 6830b27317 cosmetic: correct some comments, tweak some arg parsing 2015-06-18 18:13:27 +01:00
Matthias Radestock 4399be7ad2 correct comments 2015-06-18 17:55:42 +01:00
Matthias Radestock 42d2a860eb test more things, more obviously 2015-06-18 17:48:21 +01:00
Matthias Radestock 3ec0200067 cosmetic oops 2015-06-18 16:57:54 +01:00
Matthias Radestock d1e4a8ec8a cosmetic: vertical alignment and consistency for better readability 2015-06-18 16:26:16 +01:00
Paul Bellamy 54b3061c50 Merge gofmt and goimports run 2015-06-18 14:05:51 +01:00
Bryan Boreham 19fe808dff Update links in changelog to point at latest_release 2015-06-18 11:59:44 +01:00
Bryan Boreham b7281cc08d Add release 1.0.0 2015-06-18 11:23:44 +01:00
Adam Harrison 617c3883c7 Merge pull request #948 from awh/v1.0_doc_updates
Update documentation to leverage IPAM and DNS
2015-06-18 10:38:32 +01:00
Adam Harrison 9e20a5256b Rename example containers 2015-06-18 10:32:15 +01:00
Matthias Radestock a3db33cfd9 more helpful error message when weave et al die on startup
Closes #958.
2015-06-17 22:54:11 +01:00
Adam Harrison aa8a86a0b9 Update service binding example 2015-06-17 17:34:15 +01:00
Adam Harrison 7238192150 Update service routing example 2015-06-17 17:34:15 +01:00
Adam Harrison caa0e31be2 Update service import example 2015-06-17 17:34:15 +01:00
Adam Harrison 5a6cafc3da Update service export example 2015-06-17 17:34:15 +01:00
Adam Harrison 1df52350fa Update host network integration example 2015-06-17 17:34:15 +01:00
Adam Harrison 40323202f5 Update dynamic attachment example 2015-06-17 17:34:15 +01:00
Adam Harrison 0d83f24661 Prefer -ti to -t -i in updated examples 2015-06-17 17:34:15 +01:00
Adam Harrison 8550469c9f Update subnet isolation example 2015-06-17 17:34:15 +01:00
Adam Harrison e5679a787c Update subnet isolation example 2015-06-17 17:34:15 +01:00
Adam Harrison e37a4314b3 Add manual addressing to features 2015-06-17 17:34:10 +01:00
Adam Harrison 7b5ee8fb65 Update virtual ethernet switch example 2015-06-17 17:32:40 +01:00
Adam Harrison cf5835f081 Update README examples to use IPAM and DNS 2015-06-17 17:32:40 +01:00
Paul Bellamy aea1a2b08c have travis fetch any test-only dependencies before building tests 2015-06-17 17:00:36 +01:00
Paul Bellamy cdee7a5d6f Replacing custom testing helpers with testify, where possible 2015-06-17 16:58:03 +01:00
Paul Bellamy 21e07c0db9 Running gofmt -s 2015-06-17 15:32:08 +01:00
Paul Bellamy 06101d134b Running goimports 2015-06-17 15:32:08 +01:00
Matthias Radestock 501108e3ae cosmetic: vertical alignment 2015-06-17 15:13:07 +01:00
Matthias Radestock 87fe10ab5d Merge pull request #952 from paulbellamy/950-proxy-launch-router
Add support for WEAVE_CIDR=none

Fixes #950.
2015-06-17 14:57:35 +01:00
Paul Bellamy 6c26a55b5a [proxy] refactor weaveCIDRsFromConfig slightly 2015-06-17 14:51:12 +01:00
Paul Bellamy ca681bce13 [proxy] Add support for WEAVE_CIDR=none
Tells the proxy not to do anything with this container. We set it when
running weaveexec/weave/etc containers, so that the weave script works
through the proxy.
2015-06-17 14:51:12 +01:00
Bryan Boreham ff838a14d9 Fix terminology in error message.
Also removed hint on usage, because `-iprange` is on by default.
2015-06-17 13:16:53 +01:00
Matthias Radestock c45b586a44 Merge pull request #949 from paulbellamy/939-proxy-env-bug
remove spurious output from proxy-(env|config)

Fixes #939.
2015-06-17 12:10:09 +01:00
Matthias Radestock 215312bb5f Merge pull request #946 from bboreham/758-expand-ipam-docs
Expand description of -initpeercount in docs

Closes #758.
2015-06-17 12:06:04 +01:00
Bryan Boreham 34e0e3bb24 Merge pull request #935 from bboreham/weavedns-races
All unit and smoke tests passed.
2015-06-17 12:05:43 +01:00
Paul Bellamy b1f43f0d7c [proxy] Remove spurious output from proxy-(env|config)
`docker log` outputs an error if stdout is closed before it is done, so
silence that
2015-06-17 11:53:28 +01:00
Bryan Boreham 40efc347b6 Cosmetic 2015-06-17 11:41:46 +01:00
Matthias Radestock 71f19469ab Merge pull request #947 from paulbellamy/940-proxy-default-ipam
[proxy] --with-ipam -> --no-default-ipam

Closes #940.
2015-06-17 11:37:25 +01:00
Bryan Boreham 9636675c2c Split part of DNSServer.Start() to a separate ActivateAndServe()
Specifically, move the part that blocks, and call that as necessary.
This means Start() can run on the caller's goroutine and avoid race conditions.
2015-06-17 11:35:53 +01:00
Bryan Boreham 1b57a24789 Don't re-initialise log writers unless the type changed.
This avoids race-conditions in unit tests where the initialisation
is on a different goroutine to the logging.
2015-06-17 11:35:01 +01:00
Bryan Boreham 90fb201a51 Copy the DNS entry going into the cache, to avoid races.
The shuffleAnswers() call ends up acting on the same data,
so it seems simplest to resolve ownership arguments by copying.
2015-06-17 11:35:01 +01:00
Bryan Boreham 657133db50 Move the counting of results from DNS goroutine to test goroutine to avoid race-condition 2015-06-17 11:35:01 +01:00
Bryan Boreham e68364cb3b Don't overwrite recvChan; it just creates a race condition 2015-06-17 11:35:01 +01:00
Bryan Boreham 1f92ae6ba8 Swap the order in which two goroutines are started, since one relies on the other. 2015-06-17 11:35:01 +01:00
Bryan Boreham a6bebd85ed Remove 'running' member that triggers a race warning and isn't necessary. 2015-06-17 11:35:01 +01:00
Paul Bellamy 220c80c3ed [proxy] --with-ipam -> --no-default-ipam 2015-06-17 11:31:33 +01:00
Bryan Boreham 5960ecb3d1 Expand description of -initpeercount 2015-06-17 11:12:10 +01:00
Matthias Radestock c2778d9fa3 improve -neg-ttl usage message 2015-06-17 10:29:39 +01:00
Matthias Radestock cfe14ff568 Merge pull request #943 from inercia/weave-941
make negative TTL default to same value as TTL

Fixes #941.
2015-06-17 10:23:41 +01:00
Matthias Radestock cf5d8874b6 Merge pull request #937 from inercia/weave-934
Ignore empty queries (sent when the server is being shut down)

Fixes #934.
2015-06-17 10:10:18 +01:00
Alvaro Saurin d086867c0c make negative TTL default to same value as TTL 2015-06-17 10:17:12 +02:00
Matthias Radestock d09133c010 cosmetic: less sudoing 2015-06-17 08:30:35 +01:00
Matthias Radestock 6ac2b34a6e Merge pull request #936 from weaveworks/readme_update_2
Update wget to curl
2015-06-17 08:27:46 +01:00
Matthias Radestock 9276df62c2 document weave stop-proxy 2015-06-17 07:09:47 +01:00
Matthias Radestock d4c383b4b7 cosmetic: add weaveDNS TTL customisation to ToC 2015-06-17 07:05:09 +01:00
Matthias Radestock b10a4a2976 cosmetic 2015-06-17 06:59:20 +01:00
Matthias Radestock cfda9ffb8f add load balancing and faul resilience to weaveDNS docs ToC
This was missed in #775
2015-06-17 06:48:12 +01:00
Matthias Radestock ae5c2dab86 take advantage of recent features in proxy example 2015-06-16 23:01:14 +01:00
Matthias Radestock 0452076ebc remove outdated note 2015-06-16 22:58:30 +01:00
Matthias Radestock 7831460eff rely on ipam for launch-dns in proxy example 2015-06-16 22:51:57 +01:00
Matthias Radestock 20d5790358 drop -iprange from proxy docs, since ipam is enabled by default now 2015-06-16 22:51:10 +01:00
Matthias Radestock a52702f76e fix typo 2015-06-16 22:46:41 +01:00
Matthias Radestock 351573c122 simplify proxy launch examples
No need to specify --with-dns if DNS is running.

This should have been changed as part of #898.
2015-06-16 22:38:53 +01:00
Matthias Radestock 809b048abd fix sporadic 650_*.sh test failure caused by undefined variable
This is quite hilarious...

$BASE_IMAGE is not defined in config.sh. So running this test would
actually attempt to run something like

docker -H=... run -e WEAVE_CIDR=10.2.1.4/24  grep ^1$ /sys/class/net/ethwe/carrier

i.e. it would try to run the 'grep' image.

So why does this not fail all the time?

1) 610_*.sh defines $BASE_IMAGE.
2) 620_*.sh builds a 'grep' image.

If either of these tests are included in the same test run before
650_*.sh, then the latter will succeed.

Throw in our fabulous CircleCI scheduler, which distributes tests
according to their execution time, and you end up with sporadic
failures.

Fixes #938.
2015-06-16 21:13:35 +01:00
Alvaro Saurin c697d2a8f9 Ignore empty queries (sent when the server is being shut down) 2015-06-16 20:54:51 +02:00
Adam Harrison b8afefa55f Merge pull request #931 from paulbellamy/weavedns_default_search_domain
[dns] resolve names in `weave.local` by default unless hostname or search path is specified
2015-06-16 18:13:17 +01:00
Fintan Ryan 84bf3c139c Update wget to curl 2015-06-16 17:55:48 +01:00
Paul Bellamy 1bd343f9f0 Merge pull request #932 from bboreham/921-ipam-or-die
Kill the container we just started if we fail to allocate an IP address
2015-06-16 16:18:23 +01:00
Bryan Boreham fd5ac633d3 Use weave attach --or-die in proxy instead of killing it in the proxy. 2015-06-16 16:05:41 +01:00
Paul Bellamy a3e836bbaf Merge pull request #933 from inercia/weave-930
Use read/write-locks instead of read-locks in the Domain*Lookup()
2015-06-16 15:41:44 +01:00
Bryan Boreham ee4daf02b5 Introduce ipam_cidrs_or_die, and call it where we don't want the
container to survive an ipam failure.
2015-06-16 15:27:49 +01:00
Paul Bellamy a8c1832469 updating docs for new dns-search domain behaviour 2015-06-16 15:13:19 +01:00
Paul Bellamy 2c3baa9119 use weavedns search-domain by default
iff no hostname/dns-search specified
2015-06-16 15:13:19 +01:00
Alvaro Saurin f4507e7252 Change read-locks for read/write-locks 2015-06-16 16:02:49 +02:00
Bryan Boreham 32d3909023 Make ipam_cidrs() return failure when call_weave fails 2015-06-16 14:47:47 +01:00
Alvaro Saurin 8a935e3cb2 Launch WeaveDNS with "launch_dns_on" 2015-06-16 15:29:36 +02:00
Matthias Radestock 8b5d0e3fbd Merge pull request #898 from paulbellamy/weavedns_default_resolver
Weavedns default resolver

Fixes #383.
2015-06-16 14:17:14 +01:00
Paul Bellamy af38f07d26 refactoring proxy dns test 2015-06-16 14:08:35 +01:00
Adam Harrison b33d4497fa Fix test broken by new default behaviour 2015-06-16 14:07:42 +01:00
Adam Harrison 03b83811fa Update documentation 2015-06-16 14:07:42 +01:00
Adam Harrison e5b1cc21a9 Smoke test for new weave script DNS behaviour 2015-06-16 14:07:42 +01:00
Adam Harrison fdce84d9d9 New --with-dns/--without-dns behaviour in weave script 2015-06-16 14:07:42 +01:00
Paul Bellamy 8873d69b77 [proxy] Add --without-dns flag, and change meaning of --with-dns flag 2015-06-16 14:07:40 +01:00
Alvaro Saurin a1cb5b16a6 Run the unit tests with GOMAXPROCS=4 2015-06-16 14:58:53 +02:00
Alvaro Saurin 8621aa7cbf Launch WeaveDNS with --debug when DEBUG is set 2015-06-16 14:58:53 +02:00
Bryan Boreham 9a99a51e14 Merge pull request #925 from bboreham/922-remove-dot
Remove trailing dot from hostnames. Fixes #922.
2015-06-16 13:58:12 +01:00
Paul Bellamy bd0aff6a0f Merge pull request #929 from weaveworks/923-fix-deadlock
Fix deadlock in weavedns
2015-06-16 13:31:39 +01:00
Matthias Radestock 25dd60a149 Merge pull request #928 from awh/offset_tcp_nonce
Set high bit in TCP nonce to differentiate from UDP

Fixes #927.
2015-06-16 13:21:09 +01:00
Paul Bellamy 048261c921 Hold lock while accessing mutable variable, but release before sending on chan 2015-06-16 13:06:14 +01:00
Adam Harrison 9149366919 Set high bit in TCP nonce to differentiate from UDP 2015-06-16 12:21:18 +01:00
David Wragg 47d7c22073 Merge rade/871_version_header (PR #920): protocol versioning
Closes #871.
2015-06-16 12:20:40 +01:00
Bryan Boreham 7a106aeb5f Release lock before sending on a channel 2015-06-16 11:44:25 +01:00
Matthias Radestock 3124ffede5 more forward/backward compatible protocol versioning
We now first exchange a very minimal header, consisting of just the
name of the protocol ("weave") and the min and max version (encoded as
a byte each) we support. This leaves all other details of the
protocol, including the contents of of the handshake, open.

Peers agree to communicate using the maximum mutually supported
version. We record this in the LocalConnection, though atm nothing is
making use of that information.

We set a shorter than normal deadline (10s instead of 60s) for the
header exchange, so we detect version mismatches, garbage, or
non-responsive remotes quickly.

We reset the protocol version to '1' since this is a fresh start; old
weaves will not be able to parse the header.

Closes #871.
2015-06-16 11:00:41 +01:00
Bryan Boreham 79db00a90e Strip trailing period from hostname generated from weavedns domain.
We strip at the calling point rather than in the server, because DNS
servers do see the domain as ending with a '.'  representing the root.
2015-06-16 10:26:11 +01:00
Bryan Boreham dd9adf6a65 Don't add a trailing newline to the domain 2015-06-16 10:25:54 +01:00
Matthias Radestock 0206434f62 fix 130_expose_test breakage
introduced by 8ac98f9f6b
2015-06-15 22:25:22 +01:00
Matthias Radestock 657cbaefb9 refactor: simplify dns_args 2015-06-15 21:54:51 +01:00
Matthias Radestock ee61f07d9b refactor: extract rm_containers helper
cherry-picked from #898
2015-06-15 21:41:43 +01:00
Matthias Radestock cba1827d2c Merge pull request #918 from bboreham/916-check-call-weave
Output error message if http call to weave or weavedns fails

Fixes #916.
2015-06-15 21:16:40 +01:00
Matthias Radestock 3d7c18ee70 cosmetic: remove some unnecessary quoting 2015-06-15 21:02:23 +01:00
Matthias Radestock 82c1a58e81 refactor: remove unused weave dns-domain command
and inline dns_domain() function.

Both were introduced in #890.
2015-06-15 19:23:52 +01:00
Matthias Radestock 8ac98f9f6b tiny refactor: replace some 'if's with short-circuit expressions 2015-06-15 18:33:29 +01:00
Bryan Boreham 56e0636b14 Output error message if http call to weave or weavedns fails 2015-06-15 17:58:59 +01:00
Bryan Boreham 6534cc6ae3 Merge pull request #919 from bboreham/915-not-again-again
Fixes #915
2015-06-15 17:06:30 +01:00
Bryan Boreham d25d0810af Extend the 'block' flag to block allocateAgain()
otherwise we can re-insert the entry into the map after a parallel
allocateAgain() has cleared the flag. And it /that/ happens in
parallel with a free() then the data-structure can get out of sync.
2015-06-15 16:51:27 +01:00
Alvaro Saurin ba2f6671a6 Removed a couple of Debug.Printf()s 2015-06-15 17:11:27 +02:00
Alvaro 9284366a3d Merge pull request #917 from bboreham/inercia-flush-sched-queue
Return of the Fix for the zone refresh unit test
2015-06-15 15:45:57 +02:00
Bryan Boreham 7e6a478283 Merge pull request #914 from rade/911_different_gossip_channels
LGTM. Fixes #911
2015-06-15 14:42:22 +01:00
Matthias Radestock e15ebd9118 add unit test for SurrogateGossiper 2015-06-15 14:22:25 +01:00
Bryan Boreham 0ff311356d Flush the background updater goroutines to make tests reliable 2015-06-15 13:48:54 +01:00
Bryan Boreham e49a29197b Add a Flush() routine to synchronize callers 2015-06-15 13:48:24 +01:00
Bryan Boreham 2b7fb699af Clean up a couple of compilation errors 2015-06-15 13:47:01 +01:00
Bryan Boreham f3ad8bd52f Merge branch 'weave-fix-unittest' of https://github.com/inercia/weave into inercia-weave-fix-unittest 2015-06-15 11:51:14 +01:00
Matthias Radestock 36ba838f54 remove ipam.DummyAllocator and rely on new SurrogateGossiper
As a result it is possible to mix nodes with and without IPAM. Note
that the quorum only involves the former, and hence such mixed
environments will typically require a custom `-initpeercount`.
2015-06-15 11:24:07 +01:00
Matthias Radestock dea5bd0639 introduce SurrogateGossiper for dealing with unknown gossip channels
Instead of erroring when we receive gossip for an unknown channel, we
create a GossipChannel on the fly, with a SurrogateGossiper singleton
as the gossiper.

The SurrogateGossiper ignores unicasts and relays broadcasts and
gossip.
2015-06-15 11:23:32 +01:00
Matthias Radestock 0b3584a355 permit encoding of GossipData into multiple messages 2015-06-15 11:14:24 +01:00
Matthias Radestock 94220f22b2 allow dynamic addition of gossip channels 2015-06-15 11:14:24 +01:00
Matthias Radestock ec85da914a simplify dns-add/remove arg checking 2015-06-15 10:34:59 +01:00
Matthias Radestock bfebfecf6c refactor: simplify test router creation 2015-06-15 08:12:35 +01:00
Matthias Radestock e48ec6a35f clean up erroneous comment 2015-06-15 08:03:02 +01:00
Matthias Radestock a844797d67 cosmetic: reduce test noise 2015-06-14 17:48:21 +01:00
Matthias Radestock 72640f1828 refactor: extract helpers for http calls into weave and weavedns 2015-06-14 17:46:56 +01:00
Matthias Radestock 4ce6a76d1f remove outdated piece of docs 2015-06-14 16:36:37 +01:00
Matthias Radestock bf4c75b1cd don't validate -iprange value
We generally don't validate arguments in the script unless we process
them there.
2015-06-14 16:12:53 +01:00
Matthias Radestock 313a95a467 let -iprange "" start the router w/o IPAM 2015-06-14 16:06:20 +01:00
Matthias Radestock 926c85c6ba more idiomatic 'boolean' in shell 2015-06-14 15:48:08 +01:00
Matthias Radestock afb31d3beb cosmetic: remove unused method 2015-06-14 11:49:03 +01:00
Matthias Radestock c4c5ec1e92 refactor: add a little helper to improve abstraction 2015-06-14 11:09:55 +01:00
Matthias Radestock cea85eb05b cosmetic: move GossipChannel code into separate file 2015-06-14 10:35:51 +01:00
Matthias Radestock 3e0d0a1993 refactor: introduce NewGossipChannel 2015-06-14 10:09:36 +01:00
Matthias Radestock 882ed85d77 tiny refactor 2015-06-14 09:37:13 +01:00
Matthias Radestock eae9928e98 don't complain when weave --local run is invoked without args
leave that to docker, same as we do for `weave run`

Fixes #910.
2015-06-13 00:48:01 +01:00
Matthias Radestock 779d1589c5 Merge pull request #907 from bboreham/819-check-its-alive
Free allocated address if container is not running

Fixes #819.
2015-06-12 17:22:16 +01:00
Bryan Boreham 4c01af4e1d Check if container is still running right after allocating 2015-06-12 17:09:11 +01:00
Bryan Boreham 13944f3811 Refactor is-running check so we can re-use more of the code 2015-06-12 17:09:11 +01:00
Matthias Radestock 0f9f1feb87 Merge pull request #906 from awh/weavedns_restrict_http_listen
DNS HTTP API listens on eth0 only

Fixes #899.
2015-06-12 16:56:22 +01:00
Adam Harrison b6889ec8c3 DNS HTTP API listens on eth0 only 2015-06-12 16:55:27 +01:00
Bryan Boreham 5aafbbeff0 Fix compile error introduced in merge of #904 2015-06-12 16:20:35 +01:00
Matthias Radestock 7462fe91d5 Merge pull request #904 from bboreham/inercia-weave-821
Remove stale records for short-lived containers (bis)

Fixes #821. Replaces and hence closes #865.
2015-06-12 15:36:12 +01:00
Matthias Radestock 79c5343ca5 Merge pull request #905 from bboreham/slashten
Use /10 instead of /9 for default iprange

Patching up #893.
2015-06-12 15:21:38 +01:00
Matthias Radestock d5ebd6a0c4 Merge branch 'latest_release_doc_updates'
Conflicts:
	CHANGELOG.md
	nameserver/cache_test.go
	site/features.md
	site/proxy.md
	weave
2015-06-12 15:16:50 +01:00
Bryan Boreham 12de13c678 Use /10 instead of /9 for default iprange, so it doesn't clash with CircleCI address 2015-06-12 15:15:36 +01:00
Matthias Radestock 97d692df80 don't restart docker in test setup
due to that leaving container debris behind that docker takes ages to
clean up.
2015-06-12 15:01:35 +01:00
Bryan Boreham 7941612dad remove one level of indentation 2015-06-12 15:01:06 +01:00
Matthias Radestock c25706f6a5 Merge pull request #902 from bboreham/885-gossip-race
Keep flushing gossip until it's all gone

A better (i.e. working?) fix for #885.
2015-06-12 15:00:02 +01:00
Bryan Boreham 799e202d6d Make error message more readable 2015-06-12 14:52:20 +01:00
Bryan Boreham 658fbac7aa Keep flushing gossip until it's all gone
A better (i.e. working?) fix for #885.
2015-06-12 14:50:37 +01:00
Bryan Boreham 086daca3d0 Fold communication-checking code into docker.Client.NewClient() 2015-06-12 14:45:29 +01:00
Matthias Radestock f48f9ba475 relax documented weavedns subnet requirements somewhat
Following #763.
2015-06-12 14:22:57 +01:00
Bryan Boreham 29c988d42f Merge pull request #900 from rade/762_dns_ipam
LGTM.  Closes #762.
2015-06-12 14:21:07 +01:00
Matthias Radestock 06183bea1a extensive documentation 2015-06-12 14:01:34 +01:00
Matthias Radestock a2146ca5b0 add tests for launching DNS with various ways of obtaining an IP
As a side effect this also tests repopulation on restart, so we can
remove the separate test for that.
2015-06-12 13:50:22 +01:00
Matthias Radestock 727a213ba1 permit weavedns IP addresses to be choosen by IPAM
Same as for ordinary containers.

Closes #762.
2015-06-12 13:50:22 +01:00
Matthias Radestock 38266f2918 fix layout 2015-06-12 13:35:12 +01:00
Matthias Radestock 6d4c1e49fe fix layout 2015-06-12 13:33:32 +01:00
Matthias Radestock fa00b9faf3 add ToC to ipam docs
also shorten some of the headings, and use H2 instead of H3
2015-06-12 12:24:32 +01:00
Matthias Radestock 685d7d2559 Merge pull request #893 from bboreham/743-default-iprange-in-script
Default value for -iprange

Closes #743. Closes #870.
2015-06-12 11:17:12 +01:00
Matthias Radestock df918ab3c4 revamp ipam docs 2015-06-11 22:24:07 +01:00
Bryan Boreham 404ae010e4 Improve flow of IPAM doc 2015-06-11 18:35:18 +01:00
Bryan Boreham 43d42e6f15 Update docs for default -iprange 2015-06-11 18:01:28 +01:00
Bryan Boreham 328e63828e Check whether netcheck exists before relying on it 2015-06-11 17:55:27 +01:00
Bryan Boreham 465b16e668 Give -iprange a default, and check if it is free 2015-06-11 17:34:43 +01:00
Bryan Boreham 0e6569d78c New program netcheck to check if network overlaps routes 2015-06-11 17:27:20 +01:00
Bryan Boreham 8285046962 Function to check if a network overlaps with existing routes 2015-06-11 17:27:20 +01:00
Matthias Radestock 8e04b135bb check that nsenter is present
for `weave --local ...`

Closes #895.
2015-06-11 16:37:28 +01:00
Matthias Radestock 2b7c785386 don't output container id when we kill it
This was missed from #831.
2015-06-11 16:34:46 +01:00
Paul Bellamy 09f491821b making test executable 2015-06-11 13:51:41 +01:00
Adam Harrison c0cfacdb28 Repatriate itinerant smoke test 2015-06-11 12:54:07 +01:00
Matthias Radestock e96cde1872 Merge pull request #890 from awh/use_container_name_as_hostname
Use container name as hostname

Closes #750.
2015-06-11 12:35:46 +01:00
Adam Harrison 12fc760e4a Refocus documentation around --name 2015-06-11 12:17:05 +01:00
Paul Bellamy d2b17690f9 use dnsutils image in one test instead of awk on all the tests. 2015-06-11 12:13:24 +01:00
Adam Harrison 8d7fe2b9e7 Renamed smoke test to avoid number clash 2015-06-11 11:55:21 +01:00
Adam Harrison 1fa7c24a2a Prefer --name to -h in examples 2015-06-11 11:54:05 +01:00
Adam Harrison 6b80dc035a Simplify smoke test 2015-06-11 11:53:24 +01:00
Adam Harrison 35cfafc319 Defer evaluation of $(dns_domain) until necessary 2015-06-11 11:52:33 +01:00
Matthias Radestock e1c07dce3f permit overriding of DOCKERHUB_USER
which in turn enables operation from private registries

Closes #887.
2015-06-11 11:50:55 +01:00
Paul Bellamy 96bda1639e define weavedns http port as a constant 2015-06-11 10:27:35 +01:00
Adam Harrison a424bfe715 Correctly interpret long form arguments that lack an equals symbol 2015-06-11 10:27:34 +01:00
Adam Harrison 4f6ace8b10 Permit optional trailing dot on hostname 2015-06-11 10:27:34 +01:00
Adam Harrison bccb5065db Update docs with new weave run behaviour 2015-06-11 10:27:34 +01:00
Adam Harrison bed6a130d6 Smoke test for new weave run behaviour 2015-06-11 10:27:34 +01:00
Adam Harrison 108eea8b79 Modify weave run to use container name as fqdn
When run is invoked with `--with-dns` and a container name but no
hostname, synthesise a fully qualified hostname from the container name
and the weaveDNS domain and supply that as a hostname for the container.
2015-06-11 10:27:34 +01:00
Paul Bellamy 678063efec updating proxy docs on hostname/domainname dns 2015-06-11 10:27:34 +01:00
Paul Bellamy 99d9ad7042 [proxy] query weavedns for domain 2015-06-11 10:27:34 +01:00
Paul Bellamy b9850ac200 [proxy] Set hostname and domainname based on container name, when dns
Had to make the assert_dns_record test helper more robust against
'getent hosts' returning duplicates
2015-06-11 10:27:29 +01:00
Adam Harrison 9fddad02ad Add dns_domain helper to weave script 2015-06-11 10:26:07 +01:00
Adam Harrison a1007411ad Add /domain resource to DNS REST API 2015-06-11 10:26:07 +01:00
Matthias Radestock 9955de3e9b cosmetic: shorten usage info 2015-06-11 09:23:58 +01:00
Alvaro Saurin 2eb39d962a Use the scheduled queue for refreshing names
Some minor time tweaks...
2015-06-10 22:56:40 +02:00
Alvaro Saurin d6a9fc57cc New scheduled queue, for scheduling calls to callables 2015-06-10 22:56:38 +02:00
Alvaro Saurin 698d352322 Check with Docker whether the container is really running 2015-06-10 22:27:49 +02:00
Alvaro Saurin 1889a4a87f Simplify the arguments passed to nameserver/http.go:ServeHTTP() (by using information found in some other objects we are already passing, like the server) 2015-06-10 22:27:49 +02:00
Alvaro Saurin 5cb2347f77 Use our own Docker client 2015-06-10 22:27:47 +02:00
David Wragg 5ce4b25f38 Merge rade/746_simplify_frag_test (PR #888): simplify fragmentation test
Closes #746.
2015-06-10 18:28:26 +01:00
Matthias Radestock 267716fed5 Merge pull request #827 from bboreham/675-ipam-subnets
multi-subnet ipam

Closes #675. Fixes #861. Closes #862.
2015-06-10 17:44:17 +01:00
Bryan Boreham a89b549c40 Change command-line syntax to net: prefix instead of --subnet 2015-06-10 17:28:06 +01:00
Bryan Boreham 91ad3ef684 Illustrate mix-and-match of ipam and non-ipam CIDRs on command-line 2015-06-10 17:27:07 +01:00
Bryan Boreham 36f793c1f4 Delete entry from addresses map when slice is empty 2015-06-10 17:27:07 +01:00
Bryan Boreham c27cd5b4ab Extract lookup function for owned addresses 2015-06-10 17:27:07 +01:00
Bryan Boreham 7d881a0f0c Check that weave ps returns exactly the CIDRs we are looking for, and comment assumption 2015-06-10 17:27:07 +01:00
Bryan Boreham 487bd2eaf7 Use zero-cidr-args form of 'weave detach' to test #862 2015-06-10 17:24:36 +01:00
Bryan Boreham e9d194327a Reduce level of noise in debug logs from containers dying 2015-06-10 17:24:36 +01:00
Bryan Boreham c301e6a380 Expand test to use IPAM and check addresses are freed 2015-06-10 17:24:36 +01:00
Bryan Boreham a3d73be8b8 General tidying
tidy Address helper functions,
make ordering of members consistent in allocate and claim,
make linter happier,
remove some unused items, and
update some comments
2015-06-10 17:24:36 +01:00
Bryan Boreham fa0b0042b2 Eradicate the last vestiges of CIDR in Allocator 2015-06-10 17:24:36 +01:00
Bryan Boreham e21f66db0a Use address.Range instead of (start,end) pairs 2015-06-10 17:24:36 +01:00
Bryan Boreham 1e71d2dacb Introduce new '<address_spec>' type in usage text 2015-06-10 17:24:36 +01:00
Bryan Boreham a80e5cb028 Corrected space.Donate() to restrict the space donated to be in-range, and associated unit test changes 2015-06-10 17:24:36 +01:00
Bryan Boreham 40c9adc8d3 Extract common freelist-walking code 2015-06-10 17:24:36 +01:00
Bryan Boreham 147962d101 Clarify some unit test operations 2015-06-10 17:24:36 +01:00
Bryan Boreham 6536034ab6 Move default subnet outside of Allocator 2015-06-10 17:24:36 +01:00
Bryan Boreham 7e7a2227a0 Document multi-subnet IPAM 2015-06-10 17:24:36 +01:00
Bryan Boreham e5113889f4 Move NumFreeAddresses() into test 2015-06-10 17:24:36 +01:00
Bryan Boreham 3cdc3522be Don't overwrite CIDR_ARGS; write new variables ALL_CIDRS and IPAM_CIDRS
Handle manual CIDRs and IPAM CIDRs separately, to avoid inconsistencies and misleading messages
2015-06-10 17:24:35 +01:00
Bryan Boreham 2b4eec39cf Support '--subnet default' 2015-06-10 17:21:16 +01:00
Bryan Boreham 3a9af4413d Rationalize ipam arguments
- rename -defaultsubnet to -ipsubnet
- default subnet defaults to whole -iprange
- parse and check inside ipam package
- removes some error cases
2015-06-10 17:21:16 +01:00
Bryan Boreham f956a76455 Tighten error checking in weave script
- --subnet is used but IPAM is off
 - arg that comes after --subnet is not a cidr
2015-06-10 17:21:16 +01:00
Bryan Boreham ecbd514a83 Tighten error checking 2015-06-10 17:21:16 +01:00
Bryan Boreham 50eaccc60c Allocate() and Lookup() now return a CIDR, to simplify output 2015-06-10 17:21:16 +01:00
Bryan Boreham 2a00b0092d Extend 'Lookup' to multi-subnet 2015-06-10 17:21:16 +01:00
Bryan Boreham d18ccac43b Free IPAM addresses individually in 'weave hide', because the user now has the ability to free some subnets and not others 2015-06-10 17:21:16 +01:00
Bryan Boreham a617e7918c Introduce single-address Free() and rename the previous one to Delete() 2015-06-10 17:21:16 +01:00
Bryan Boreham d20d2e9142 Update smoke tests that use IPAM with default subnet 2015-06-10 17:21:16 +01:00
Bryan Boreham 551394a831 Add subnet info to space requests from one peer to another 2015-06-10 17:21:16 +01:00
Bryan Boreham 0613a06653 IPAM: handle multiple CIDRs in weave script 2015-06-10 17:21:16 +01:00
Bryan Boreham ff545feae3 Add some unit tests for multi-subnet 2015-06-10 17:21:16 +01:00
Bryan Boreham c7a9dfbb3f Remember multiple addresses owned by each container, for multi-subnet use 2015-06-10 17:21:16 +01:00
Bryan Boreham 2553850ed4 Initial implementation of multi-subnet IPAM 2015-06-10 17:21:16 +01:00
Bryan Boreham e6d230042d Introduce address.CIDR type 2015-06-10 17:21:16 +01:00
Bryan Boreham ae8985deb5 Merge pull request #884 from rade/881_get_container_id_on_start
LGTM. Fixes #881
2015-06-10 17:20:25 +01:00
Matthias Radestock 18bc3c2b99 "online dictionary attacks" is better terminology 2015-06-10 16:40:25 +01:00
Matthias Radestock afd0275042 Merge pull request #848 from paulbellamy/proxy-env
add proxy-env and proxy-config

Closes #753.
2015-06-10 15:34:32 +01:00
Paul Bellamy 2d5e362115 [proxy] use logs for getting proxy-env and proxy-config
It's ugly, but beats arg-parsing, inspecting the container and piecing
the address together from exposed ports.

We could just use the host from DOCKER_HOST, but that will fail in the
case of a host with multiple IPs, where the proxy only listens on one.
2015-06-10 15:24:53 +01:00
Matthias Radestock 7a2b5c43e7 feed full container id on weave start to DNS/IPAM
We erroneously assumed that `docker start` returned the full container
id. It doesn't and as a result we populated DNS with records with ids
other than full container ids. And that's also what we told IPAM. As a
result, DNS and IPAM didn't clean up after the container's death,
resulting in stale DNS entries and leaked IPs.

Fixes #881.
2015-06-10 14:53:43 +01:00
Matthias Radestock 0ab7c31bbe Merge pull request #886 from bboreham/885-gossip-race
Avoid gossip test race conditions

Fixes #885.
2015-06-10 13:50:40 +01:00
Bryan Boreham 0cd4127087 Factor out GossipSender.flush() 2015-06-10 12:24:19 +01:00
Bryan Boreham 5ebfc9027b Flush gossip inside the channel lock so we know sender hasn't been Stop()-ed 2015-06-10 12:22:38 +01:00
Matthias Radestock fe6d05fccc Merge pull request #875 from paulbellamy/proxy-port
Add support for custom proxy bind address

Closes #805. Closes #810.
2015-06-10 12:19:41 +01:00
Bryan Boreham 35b3a2d6a6 Inline GossipSender.flush() and don't try to send nil 2015-06-10 12:18:09 +01:00
Paul Bellamy 6bb087cecd [proxy] Renaming flags: -H -> -D, -L -> -H
As per discussion in issue 875
2015-06-10 12:13:52 +01:00
Paul Bellamy 447a00ba7e [proxy] Add support for tcp:// prefix on listen addresses 2015-06-10 12:13:52 +01:00
Paul Bellamy 0da9669e5c [proxy] removing unneeded port expose 2015-06-10 12:13:51 +01:00
Paul Bellamy 4a7d4d3555 [proxy] documenting -L option 2015-06-10 12:13:51 +01:00
Paul Bellamy 1ac5d4fe98 renaming dns_restart_test so it is picked up by test runner 2015-06-10 11:52:58 +01:00
Bryan Boreham bd5353024f Helpful suggestions from the linter 2015-06-10 11:05:04 +01:00
Bryan Boreham 569fd784fa Make flushing of GossipSender wait till done in tests,
and take extra locks to avoid race conditions.
Also removed the sendPendingGossip() from mockChannelConnection.SendProtocolMsg(),
because waiting in flush now creates a deadlock.  Tests will have to
call sendPendingGossip() explicitly if they need it.
2015-06-10 10:59:54 +01:00
Matthias Radestock fbf2208e80 simplify fragmentation test
This is how fragmentation tests used to happen:

    A: conn.stackFrag = false
    A->B: StartFragmentationTest
    B->A: send large non-DF UDP packet
    A->B: FragmentationReceived
    B: conn.stackFrag = true

conn.stackFrag = true means "UDP packets sent to the other peer get
correctly fragmented and re-assembled".

Note how A clears its own stackFrag flag, but the test establishes
whether fragmentation works for B->A, and ends up setting stackFrag on
B. Not only is this weird, it also means that if A previously had
stackFrag=true, it will now start fragmenting packets until B
initiates a fragmentation test.

The new sequence is:

    A: conn.stackFrag = false
    A->B: send large non-DF UDP packet
    B->A: FragmentationReceived
    A: conn.stackFrag = true

Closes #746.
2015-06-10 07:41:37 +01:00
Matthias Radestock bd364f787e fix silent failure in assert_dns_record test
If the name cannot be resolved, getent fails, which in turn causes
assert_dns_record to just return with a non-zero status at that point,
which causes the whole test to just terminate with a non-zero status,
but no indication whatsoever what had gone wrong.
2015-06-09 19:08:59 +01:00
Matthias Radestock ca133d6d9b Call LocalPeer.Encode, not Peer.Encode!
We need the former's locking.

Fixes #883.
2015-06-09 17:19:36 +01:00
Matthias Radestock 94f18f7004 remove outdated comment 2015-06-09 15:52:15 +01:00
Paul Bellamy e324c15b04 Merge pull request #879 from rade/proxy_interceptor_refactor
Proxy interceptor refactor
2015-06-09 14:50:54 +01:00
Matthias Radestock 5c70c7c71a simplify interceptor structs by referencing proxy 2015-06-09 14:45:50 +01:00
Matthias Radestock 020a9ea396 rename file 2015-06-09 14:45:50 +01:00
Matthias Radestock 3f9c7a5c00 remove pointless indirection 2015-06-09 14:45:50 +01:00
Matthias Radestock f025b6ea36 refactor interceptor invocation 2015-06-09 14:45:42 +01:00
Paul Bellamy 03fa95fcb5 Merge pull request #878 from rade/877_remove_proxy_status
remove most of proxy status
2015-06-09 14:24:53 +01:00
Matthias Radestock fee2898c05 remove detailed proxy status
There are two problems with obtaining the proxy status via the normal
proxy endpoint...

1. when TLS is enabled, this requires using TLS at the client end

2. the status URL is mixed in with the docker remote API, which is
rather unclean

There is limited value to having some status output from the proxy,
since all the output is constant.

So instead we log the version and command line args, and in `weave
status` only report whether the proxy is running.

We still want a way to wait for the proxy to start up, and log a nice
error if it dies. We do this by polling the docker logs for a startup
message.

Fixes #877. Fixes #876.

Undoes most of #756, #800, #854.
2015-06-09 14:03:52 +01:00
Matthias Radestock 2d70197221 cosmetic: more formal command grammar 2015-06-08 23:57:53 +01:00
Matthias Radestock f9c325c793 refactor: more sensible field order 2015-06-08 21:57:49 +01:00
Matthias Radestock a4b323c5df mention docker logs weavedns for debugging 2015-06-08 18:39:26 +01:00
Fintan Ryan 365edac5d5 Update strapline 2015-06-08 18:37:51 +01:00
Adam Harrison 89cf5939b8 Merge branch '0.11.2' into latest_release_doc_updates 2015-06-08 17:59:55 +01:00
Adam Harrison 93c1e755e6 Add release 0.11.2 to CHANGELOG 2015-06-08 16:31:24 +01:00
David Kaltschmidt b7f69c77f1 [site] header markup needs change after refactor
due to change in stylesheet in website repo

this change does not affect current deployment, but has to be deployed
when the website is deployed again
2015-06-08 15:09:04 +01:00
Ilya Dmitrichenko 235acc16f6 Merge pull request #874 from weaveworks/header-markup-refactor
[site] header markup needs change after refactor
2015-06-08 15:07:08 +01:00
Bryan Boreham 011c86fdca Merge pull request #852 from rade/816_make_launch_args_order_insensitive
LGTM. Fixes #816
2015-06-08 14:34:46 +01:00
Alvaro Saurin 5f4756aa80 Do not overwrite negLocal entries in the cache
Unit tests
2015-06-08 13:53:24 +01:00
Ilya Dmitrichenko 106f1c4434 Respect DOCKER_CLIENT_ARGS in weave run (close #855) 2015-06-08 13:53:24 +01:00
Alvaro Saurin bb3dd55a10 Merge branch '0.11'
Conflicts:
	nameserver/cache_test.go
2015-06-08 13:41:38 +02:00
Matthias Radestock 3f274d02ce make weave launch args insensitive to order
Fixes #816.
2015-06-08 12:41:26 +01:00
David Kaltschmidt f65e02477e [site] header markup needs change after refactor
due to change in stylesheet in website repo

this change does not affect current deployment, but has to be deployed
when the website is deployed again
2015-06-08 13:19:58 +02:00
Matthias Radestock 8a5725fa74 correct docs for proxy's -H 2015-06-08 10:26:21 +01:00
Adam Harrison 0df28f58df Merge pull request #849 from inercia/weave-fix-neg-ttl
Do not overwrite negLocal entries in the cache
2015-06-08 09:59:32 +01:00
Matthias Radestock 9d707b9670 add proxy troubleshooting docs
Closes #854.
2015-06-07 13:32:02 +01:00
Matthias Radestock ad92bbc8c8 get rid of proxy 'Advantages' section
instead point out the main purpose right at the top, and list further
benefits in the 'Usage'.

Also, expand docs on configuring the listener.
2015-06-06 23:57:20 +01:00
Matthias Radestock e1ba892827 go back to using the tcp:// prefix in DOCKER_HOST
seems to be the recommended way
2015-06-06 23:54:46 +01:00
Matthias Radestock 4012b3c011 raise profile of proxy 2015-06-06 23:20:56 +01:00
Matthias Radestock 897f625db9 substantial reworking of the proxy TLS docs 2015-06-06 23:08:08 +01:00
Matthias Radestock 33c03e00e4 rewrite proxy dns docs, and add a complete multi-host example 2015-06-06 21:34:26 +01:00
Matthias Radestock 03ede29e7e ditch proxy 'Limitations' section 2015-06-06 21:22:09 +01:00
Matthias Radestock 4ea451378e cosmetic 2015-06-06 21:18:57 +01:00
Matthias Radestock 542f966dcd typo 2015-06-06 21:14:44 +01:00
Matthias Radestock 198b7db463 simplify examples - don't specify a command on container start 2015-06-06 21:13:46 +01:00
Matthias Radestock b22fbd3734 expand proxy setup documentation 2015-06-06 21:11:39 +01:00
Matthias Radestock 06716ba132 rewrite proxy ipam docs 2015-06-06 16:57:50 +01:00
Matthias Radestock 52509087a7 cosmetic 2015-06-06 16:19:44 +01:00
Matthias Radestock 7e88302d01 mention docker logs weavedns for debugging 2015-06-06 16:17:24 +01:00
David Kaltschmidt 6bf0feeffc Wrapping content area to allow for sticky footer 2015-06-05 16:46:29 +01:00
Ilya Dmitrichenko e7f02c954c Merge pull request #866 from weaveworks/fix-doc-layout
Fix doc layout
2015-06-05 16:44:18 +01:00
David Kaltschmidt cdcd37e15a Wrapping content area to allow for sticky footer 2015-06-05 17:36:30 +02:00
Alvaro Saurin 8b5df952c9 Do not overwrite negLocal entries in the cache
Unit tests
2015-06-05 16:31:57 +02:00
Paul Bellamy 525b7c8eeb Merge pull request #853 from rade/847_option_to_disable_peer_discovery
introduce '-nodiscovery' option to disable automatic peer discovery
2015-06-05 15:31:51 +01:00
Matthias Radestock 8f85a07ecc introduce '-nodiscovery' option to disable automatic peer discovery
Closes #847.
2015-06-05 15:13:48 +01:00
Paul Bellamy 5f5c0079d3 Merge pull request #863 from rade/onoff
introduce OnOff helper, for consistency in status output
2015-06-05 15:05:24 +01:00
Matthias Radestock c10f1dacad introduce OnOff helper, for consistency in status output 2015-06-05 15:00:02 +01:00
Michael Bridgen 1c14660ed5 Merge pull request #859 from weaveworks/website_39_again
Update strapline
2015-06-05 14:51:30 +01:00
Matthias Radestock 87e30d8382 Merge pull request #815 from paulbellamy/proxy-ssl
Proxy TLS Support

Closes #506.
2015-06-05 14:48:54 +01:00
Paul Bellamy edeb531940 [docs] rewording proxy TLS docs 2015-06-05 14:35:00 +01:00
Paul Bellamy 14cc33c512 reordering args to launch-proxy docker command 2015-06-05 14:10:38 +01:00
Paul Bellamy bddf79855d [proxy] refactor tls arg parsing 2015-06-05 13:59:30 +01:00
Fintan Ryan 124d98246b Update strapline 2015-06-05 13:51:03 +01:00
Paul Bellamy 127cffba6f [proxy] add TLS to /status 2015-06-05 13:38:55 +01:00
Matthias Radestock 3e8813e559 Merge pull request #858 from weaveworks/0.11
Respect `DOCKER_CLIENT_ARGS` in `weave run`
2015-06-05 13:36:27 +01:00
Matthias Radestock 4fc0671d0d Merge pull request #857 from weaveworks/weave-run-client-args-855
Respect `DOCKER_CLIENT_ARGS` in `weave run` (close #855)
2015-06-05 13:28:20 +01:00
Ilya Dmitrichenko e1537000c2 Respect DOCKER_CLIENT_ARGS in weave run (close #855) 2015-06-05 13:24:05 +01:00
Paul Bellamy b8595b9062 [proxy] cleaner default args 2015-06-05 13:23:15 +01:00
Paul Bellamy fa6707effa [docs] adding paragraph about docker listening on unix socket for proxy 2015-06-05 13:23:15 +01:00
Paul Bellamy a1ffbb528c adding documentation on using the proxy with tls 2015-06-05 13:23:14 +01:00
Paul Bellamy c0cf9e509c [proxy] Added proxy.Config struct for initialization 2015-06-05 13:23:14 +01:00
Paul Bellamy c31358259f [proxy] Add tls support
Caveats:
* TLS files must be present on the target server (and readable)
* TLS file paths cannot contain spaces
* TLS file paths must be absolute (i.e. cannot be relative)
* Bash argument parsing is terrible
2015-06-05 13:23:14 +01:00
Matthias Radestock 8514172e57 explain meaning of 'Encryption' line in weave status output 2015-06-05 11:27:38 +01:00
Matthias Radestock 3a0a8ba939 cosmetic 2015-06-05 10:31:34 +01:00
Matthias Radestock 16410d6014 refactor: make encryption status part of router status 2015-06-05 10:05:17 +01:00
Matthias Radestock 34e3f0ba3f refactor: simplify topology gossip gob decoder initialisation
Should be more efficient too due to less copying.
2015-06-05 07:39:34 +01:00
Matthias Radestock 62ee70f388 recommend strong password
Closes #641.
2015-06-04 15:19:47 +01:00
Matthias Radestock 441e58e422 excise reference to crypto implementation from main docs
It's too scary for the uninitiated.
2015-06-04 15:19:37 +01:00
Paul Bellamy c917b261f7 Capitalizing and standardizing titles of pages in the docs.
Also re-wording to make them match the page title.
2015-06-04 15:19:26 +01:00
Paul Bellamy d3daaf32f9 [docs] re-ordering proxy doc sections 2015-06-04 15:19:13 +01:00
Bryan Boreham 62bbaf57a2 Merge pull request #839 from rade/837_tcp_accept_delay
LGTM. Closes #837
2015-06-04 14:29:58 +01:00
Bryan Boreham 7acb4be13f Merge pull request #844 from rade/641_recommend_strong_password
LGTM. Closes #641
2015-06-04 14:28:22 +01:00
Matthias Radestock 044992eea0 limit acceptance rate on peer connections
The main purpose of this is to set an upper bound on the rate at which
an attacker might execute a brute force attack on the weave password.

No need to get clever or precise here.

Closes #837.
2015-06-04 14:21:35 +01:00
Bryan Boreham 2a244e5699 Simplify paxos.Consensus() call 2015-06-04 13:30:15 +01:00
Fintan Ryan bee5c300db Docs look and feel update 2015-06-04 12:36:13 +01:00
Matthias Radestock e31233c473 recommend strong password
Closes #641.
2015-06-03 18:15:32 +01:00
Matthias Radestock 720fdf714c excise reference to crypto implementation from main docs
It's too scary for the uninitiated.
2015-06-03 18:10:38 +01:00
Paul Bellamy 6f9c0d6cde Merge pull request #834 from weaveworks/docs_lookandfeel
Docs look and feel update
2015-06-03 17:29:29 +01:00
Paul Bellamy a7782359b1 Merge pull request #838 from paulbellamy/docs-capitals
Capitalizing and standardizing titles of pages in the docs.
2015-06-03 16:29:37 +01:00
Fintan Ryan e96a22bd6d Docs look and feel update 2015-06-03 16:04:01 +01:00
Paul Bellamy a957ed253c Capitalizing and standardizing titles of pages in the docs.
Also re-wording to make them match the page title.
2015-06-03 15:59:40 +01:00
Matthias Radestock dc8a82e913 cosmetic: consistency in launchers 2015-06-03 14:22:20 +01:00
Bryan Boreham c1f8b62e11 Merge pull request #832 from rade/412_min_backoff
LGTM. Fixes #412
2015-06-03 12:47:07 +01:00
Paul Bellamy c91e699a27 Merge pull request #831 from rade/192_kill_container_on_network_config_error
kill container on network config error
2015-06-03 12:45:23 +01:00
Adam Harrison 4a8850afb3 Fix comment 2015-06-03 12:08:13 +01:00
Matthias Radestock d1c41a97b1 encapsulate retry delay calculation 2015-06-03 12:04:33 +01:00
Matthias Radestock fe5102580d fix connection retry delay calculation logic
The delay d(n) for the nth retry was
d(1) = rand(initial_interval)
d(n+1) = d(n) + rand(d(n))

There are two problem with this:

1) The entire sequence is dominated by the random value for d(1). If
this is a small value, lots of retries at short interval ensue. In
extremis, if d(1) is 0, indefinite immediate retries would ensue.

2) The delay is not a true exponential backoff; the upper bound only
increases exponentially *probabilistically*.

The new algorithm calculates

d(n) = random_value in range [i-i/2,i+i/2],
       where i = initial_interval * 1.5^(n-1).

Thus d(1) is at least initial_interval/2, and successive values are
independent of previous values, forming a true exponential sequence of
intervals bounds between which the retry delay is chosen randomly.

Due to the change in algorithm we reduce...

- the initial interval from 5s to 2s. Recall that previously d(1) was
  in the range [0,initial_interval=5s]. It is now [2-2/2=1s,2+2/2=3s].

- the max interval from 10 minutes to 6. Previously, once that was
  reached, it would stay exactly there. It is now randomly chosen in
  the range [6-6/2=3m,6+6/2=9min].

Thus previously delays would range from 0 to 10 minutes, now they
range from 1s to 9 minutes.

Fixes #412.
2015-06-03 11:29:55 +01:00
Matthias Radestock d909a86fb0 Merge pull request #828 from rade/666_no_timeout_on_wait_for_status
no timeout on wait for status

Fixes #666.
2015-06-03 11:20:37 +01:00
Matthias Radestock 4e68bde7b5 don't time out waiting for weave infrastructure containers to start
instead keep trying, until we succeed or the container dies

Fixes #666.
2015-06-03 11:19:51 +01:00
Matthias Radestock 52f31f0596 Merge pull request #830 from rade/829_fix_container_death_check
kill -0 $CONTAINER_PID does not work inside weaveexec container

Fixes #829.
2015-06-03 11:17:23 +01:00
Matthias Radestock fa8b77b060 kill -0 $CONTAINER_PID does not work inside weaveexec container
since $CONTAINER_PID is in a different process namespace.

Fixes #829.
2015-06-03 11:16:08 +01:00
Matthias Radestock 83fe4e59f6 kill container on network config error
but only if we have just started it; killing other containers, e.g. on
`weave attach`, would be rather impolite.

Closes #192.
2015-06-02 20:26:20 +01:00
Matthias Radestock 8de1ddd255 cosmetic 2015-06-02 20:21:56 +01:00
Matthias Radestock 395aea28c6 cosmetic 2015-06-02 20:16:25 +01:00
Matthias Radestock 5e7d1cd0eb Merge remote-tracking branch 'upstream/master' 2015-06-02 18:46:30 +01:00
Michael Bridgen 3344e45a9f Merge pull request #825 from squaremo/discover_ipam_ip
Allow lookup of IPAM-allocated addresses
2015-06-02 18:04:11 +01:00
Matthias Radestock d020999e82 cosmetic: more sensible function order 2015-06-02 17:52:39 +01:00
Matthias Radestock d105d255ca Merge pull request #824 from paulbellamy/weavewait-race-condition
[proxy] We can only rely on receiving a signal when we are a container

This got broken in #820.
2015-06-02 17:39:28 +01:00
Michael Bridgen 791584d9f3 Allow lookup of IPAM-allocated addresses 2015-06-02 17:31:25 +01:00
Matthias Radestock 48b54ef722 cosmetic: consistency 2015-06-02 17:25:56 +01:00
Paul Bellamy e2447721eb [proxy] We can only rely on receiving a signal when we are a container
Due to https://github.com/docker/docker/issues/9098, there is no way to
signal an exec. Instead we'll have to just poll the interface and hope.

Adds `weavewait -s` which is the way to tell weavewait to skip waiting
for a signal, which we use on execs
2015-06-02 17:21:41 +01:00
Bryan Boreham 89074715bc Merge pull request #823 from bboreham/818-claim-no-trunc
Fixes #818
2015-06-02 16:12:47 +01:00
Bryan Boreham 4968f0f3c4 Merge pull request #822 from bboreham/802-test-race
Fix race condition in allocator fuzz-test - fixes #802
2015-06-02 16:12:22 +01:00
Bryan Boreham aa7c7a9a9d Add --no-trunc flag to docker ps for ipam_claim 2015-06-02 16:07:48 +01:00
Bryan Boreham 6a1b658f59 Fix race condition in allocator fuzz-test
It was possible to free an address between deciding to re-test it
and actually re-testing it.  So introduced a flag 'block' to prevent
an address from being freed during the re-test.
2015-06-02 16:04:35 +01:00
Matthias Radestock e2805c01f4 cosmetic: remove stray trailing whitespace 2015-06-02 15:57:50 +01:00
Matthias Radestock 04c10233f7 Merge pull request #820 from paulbellamy/weavewait-race-condition
Use USR2 signal to tell weavewait when to continue

Fixes #812.
2015-06-02 15:52:22 +01:00
Paul Bellamy 92444a75eb [proxy] Use USR2 signal to tell weavewait when to continue
There was a race condition with containers running and exiting before
`weave attach` was finished, which would cause an error.
2015-06-02 15:48:49 +01:00
Paul Bellamy cb92e6b0d2 Merge pull request #817 from rade/420_move_exe_image_to_subdir
move exe and docker image producing dirs to subdir
2015-06-02 14:18:49 +01:00
Matthias Radestock 5b04efac7c move weaver 2015-06-02 14:04:02 +01:00
Matthias Radestock 485db6e48b move weavedns 2015-06-02 13:51:19 +01:00
Matthias Radestock ee28cf91e1 move weaveexec 2015-06-02 13:46:02 +01:00
Matthias Radestock 8d4a97fed5 move sigproxy, weaveproxy, weavewait 2015-06-02 13:19:16 +01:00
Matthias Radestock 46ef954057 cosmetic: rename: trains station 2015-06-02 12:58:09 +01:00
Paul Bellamy f28fe80861 Merge pull request #814 from weaveworks/revert-813-proxy-ssl
Revert "First pass at Proxy SSL Support"
2015-06-02 12:49:08 +01:00
Paul Bellamy 91375a1095 Revert "First pass at Proxy SSL Support" 2015-06-02 12:48:38 +01:00
Paul Bellamy 3586ddcd63 Merge pull request #813 from paulbellamy/proxy-ssl
First pass at Proxy SSL Support
2015-06-02 12:48:31 +01:00
Matthias Radestock fca02eb489 Merge pull request #706 from awh/dns_delete_individual_records
delete individual dns records

Closes #631. Closes #654.
2015-06-02 12:35:50 +01:00
Adam Harrison 56160e0798 Merge pull request #728 from paulbellamy/test-runner
allow test/run_all.sh to run specific test files
2015-06-02 12:30:15 +01:00
Matthias Radestock 0b72338400 Merge remote-tracking branch 'upstream/master' 2015-06-02 11:58:38 +01:00
Matthias Radestock cc1a5548af reduce smoke test noise 2015-06-02 11:57:49 +01:00
Matthias Radestock a8b8a0948f cosmetic(ish): banish double bracket bashism 2015-06-02 11:56:27 +01:00
Paul Bellamy e7f73900cb allow test/run_all.sh to run specific test files 2015-06-02 11:45:43 +01:00
Paul Bellamy 927d40f47f Correctly building the client cert subjectAltNames
Still haven't managed to try it due to:
https://github.com/docker/docker/issues/8943
2015-06-02 11:34:54 +01:00
Matthias Radestock 294aafde14 cosmetic: s/when_has_fqdn/with_container_fqdn 2015-06-02 10:52:58 +01:00
Matthias Radestock dcedf241bb simplify dns-add/remove 2015-06-02 10:52:58 +01:00
Matthias Radestock bf8e82ba16 refactor: get rid of with_full_container_id 2015-06-02 10:52:58 +01:00
Paul Bellamy 79cc2d6d25 add note about not handling args with spaces for proxy tls files 2015-06-02 10:20:06 +01:00
Adam Harrison a4582fb529 Optimise DeleteRecords when explicit values are passed 2015-06-02 10:09:26 +01:00
Adam Harrison 30447ada52 Remove obsolete methods 2015-06-02 10:09:22 +01:00
Adam Harrison 66de361fd6 Smoketest dns-remove -h 2015-06-02 09:57:28 +01:00
Adam Harrison 9da5511484 Support -h argument to dns-remove 2015-06-02 09:57:28 +01:00
Adam Harrison e02bd8bb30 Add HTTP routes for wildcard deletion 2015-06-02 09:57:28 +01:00
Adam Harrison e21592d74f Add DeleteRecords method 2015-06-02 09:57:28 +01:00
Adam Harrison 5ead7eb020 Fix TTL breakage + cosmetic fixes 2015-06-02 09:42:59 +01:00
Adam Harrison 2a5f3f5b2d Merge pull request #737 from inercia/weave-cache-tests
WeaveDNS cache tests
2015-06-02 09:30:36 +01:00
Paul Bellamy d80ffa8ad2 working on bash argument parsing 2015-06-01 16:50:52 +01:00
Paul Bellamy 74afbc5505 Proof-of-Concept for proxy TLS support
The code is quite messy, but particularly the argument parsing and
volume-mounting. Also, it doesn't support args with spaces, or
non-absolute file-paths.
2015-06-01 16:50:52 +01:00
Paul Bellamy 4774043295 Add release 0.11.1 to CHANGELOG 2015-06-01 16:23:40 +01:00
Matthias Radestock 1237bfe840 cosmetic 2015-06-01 14:40:39 +01:00
Paul Bellamy 4f86a20824 Add release 0.11.1 to CHANGELOG 2015-06-01 14:26:33 +01:00
Paul Bellamy c4fc2ecf09 Merge remote-tracking branch 'upstream/0.11' 2015-06-01 13:35:37 +01:00
Matthias Radestock 49ad336a35 Merge pull request #809 from paulbellamy/0.11
Rewording and clarifying proxy docs for 0.11.1 release
2015-06-01 13:32:28 +01:00
Paul Bellamy 6feaa72abd [docs] elaborating on proxy IPAM and DNS interaction 2015-06-01 13:24:55 +01:00
Paul Bellamy 71d6f9cdfb [docs] re-ordering proxy doc sections 2015-06-01 13:24:52 +01:00
Adam Harrison 59f919bda3 Merge pull request #795 from inercia/weave-711
Make the TTL configurable
2015-06-01 12:51:45 +01:00
Matthias Radestock 84c0daa000 remove out-of-date info from ipam docs 2015-06-01 12:41:39 +01:00
Matthias Radestock 415b9ead52 cosmetic 2015-06-01 12:41:32 +01:00
Matthias Radestock 7ccf650e48 cosmetic 2015-06-01 12:41:14 +01:00
Matthias Radestock a29831b7fa expand weavedns example to cover multi-host 2015-06-01 12:41:08 +01:00
Alex Chan 70dc8b027d Minor spelling corrections in docs 2015-06-01 12:41:00 +01:00
Paul Bellamy 4e1d388017 weave status should look for proxy at 127.0.0.1 (since PROXY_HOST is gone) 2015-06-01 12:21:58 +01:00
Matthias Radestock 341d5ecbb6 remove out-of-date info from ipam docs 2015-06-01 12:15:14 +01:00
Matthias Radestock 2e208b4921 cosmetic 2015-06-01 12:15:05 +01:00
Matthias Radestock c983d9f39f cosmetic 2015-06-01 12:14:55 +01:00
Matthias Radestock adc5784fc9 expand weavedns example to cover multi-host 2015-06-01 12:14:35 +01:00
Alex Chan a8c3551040 Minor spelling corrections in docs 2015-06-01 12:14:20 +01:00
Matthias Radestock ac90086fb1 Merge pull request #808 from paulbellamy/proxy-short-docker-host
weave PROXY_HOST is always localhost, so just hardcode it as that

Closes #807.
2015-06-01 11:55:58 +01:00
Paul Bellamy bd94c00e96 weave PROXY_HOST is always localhost, so just hardcode it as that
Because launch-proxy runs in weaveexec container, DOCKER_HOST is never
set.
2015-06-01 11:51:32 +01:00
Bryan Boreham 2698f3f376 Merge remote-tracking branch 'upstream/0.11' 2015-06-01 11:42:30 +01:00
Matthias Radestock 3332f94a00 Merge pull request #801 from bboreham/787-better-status
Improve ipam status message before and after pending consensus

Fixes #787.
2015-06-01 11:17:19 +01:00
Matthias Radestock fd0aa3f6ae remove out-of-date info from ipam docs 2015-06-01 11:13:16 +01:00
Bryan Boreham cd1cca3df2 Reformat output of pending ipam requests 2015-06-01 10:55:01 +01:00
Matthias Radestock b86ead2737 fix tests post 9a1e8c07ac 2015-05-31 13:11:28 +01:00
Matthias Radestock 21ee0fdcf7 Merge branch '0.11' 2015-05-31 11:54:54 +01:00
Matthias Radestock 158fb36c8c Merge remote-tracking branch 'upstream/0.11' into 0.11 2015-05-31 11:54:08 +01:00
Matthias Radestock 5f385a297c remove bashism
This was introduced in #780.
2015-05-31 11:51:34 +01:00
Matthias Radestock 8d0adb9359 refactor(ish): use full container id when we have it 2015-05-31 11:25:13 +01:00
Matthias Radestock 79eb4c298c refactor: extract helper for and simplify get_ipam_cidr 2015-05-31 09:59:32 +01:00
Matthias Radestock 6f57bcb477 tiny refactor - use full container id, since we happen to have it 2015-05-31 09:35:53 +01:00
Matthias Radestock 54dbc2f582 cosmetic 2015-05-31 09:03:45 +01:00
Matthias Radestock 1a8f2117b1 explain Direct Peers status 2015-05-29 17:35:21 +01:00
Bryan Boreham 1d6f1f7561 Improve ipam status message before and after pending consensus 2015-05-29 17:34:17 +01:00
Matthias Radestock 59fd53ee99 Merge pull request #800 from paulbellamy/proxy-status
Add proxy info to 'weave status' output

Closes #756.
2015-05-29 17:13:33 +01:00
Matthias Radestock 8c27d469a1 add Direct Peers to status output example
This was missed in #764.
2015-05-29 17:05:34 +01:00
Paul Bellamy 7f62ba6ebe Add proxy info to './weave status' output 2015-05-29 17:05:25 +01:00
Matthias Radestock 9a1e8c07ac cosmetic: report direct peers on one line in status 2015-05-29 17:03:33 +01:00
Matthias Radestock b7df116491 make weave reset stop the proxy
Missed this in #790.
2015-05-29 16:43:18 +01:00
Paul Bellamy ece1fa1b1b Merge remote-tracking branch 'upstream/0.11' 2015-05-29 16:35:59 +01:00
Matthias Radestock 1769cdad7c Merge pull request #799 from paulbellamy/proxy-attach
Proxy kills container and returns error when attach fails

Fixes #788.
2015-05-29 16:35:25 +01:00
Matthias Radestock 99b1e00a75 Merge pull request #794 from paulbellamy/connect-replace
add 'weave connect --replace ...'

Closes #658.
2015-05-29 15:52:32 +01:00
Matthias Radestock e0f4da4641 Merge pull request #792 from rade/720_auto_assigned_dns_http_test_port
use an automatically assigned port for http tests

Fixes #720.
2015-05-29 15:51:38 +01:00
Paul Bellamy 2be69634df replace proxy /weave endpoint with /status
which is more standard with others, and doesn't conflict with docker api
2015-05-29 15:48:33 +01:00
Paul Bellamy 858356de22 add 'weave connect --replace ...' 2015-05-29 15:44:45 +01:00
Paul Bellamy 4f73ef73bb weavewait should exit if it times out
To match behaviour of proxy when interface fails to attach
2015-05-29 14:37:58 +01:00
Paul Bellamy 0cb2e80e09 Proxy kills container and returns error when attach fails 2015-05-29 14:37:33 +01:00
Matthias Radestock 08a17ec376 no need for sleeps to wait for http server
Since we start the tcp listener in the main thread now, connections
can be made immediately; they will just queue up until there is a
handler.
2015-05-29 14:17:03 +01:00
Matthias Radestock 3ad08c0f2a don't print the test port
it's of no interest whatsoever
2015-05-29 14:05:24 +01:00
Tom Wilkie 6e2bb075a6 Merge pull request #797 from weaveworks/timeout
Various fixes to circleci integration tests
2015-05-29 13:57:42 +01:00
Paul Bellamy 587a56cf31 Merge remote-tracking branch 'upstream/0.11'
Conflicts:
	weave
2015-05-29 13:37:21 +01:00
Tom Wilkie 0a945a04f3 Add smarter scheduler to decide which tests to run on which shard on circle. 2015-05-29 12:35:43 +00:00
Matthias Radestock 10a9f58d13 Merge pull request #780 from paulbellamy/proxy-launch-2
simplifying and standardizing `launch-proxy`

Fixes #767. Fixes #755. Closes #757.
2015-05-29 13:30:30 +01:00
Tom Wilkie 332a1bf58d Checkin the assert.sh script, so we can expose test run time. 2015-05-29 11:48:49 +00:00
Matthias Radestock fb721558af auto-assign port in ipam tests 2015-05-29 12:35:23 +01:00
Bryan Boreham 0ab2263a41 Merge pull request #793 from bboreham/ipam-avoid-unittest-races
[ipam] Fix race conditions in unit tests
2015-05-29 12:30:39 +01:00
Tom Wilkie 3642dc5514 Add some timeouts to the circle/gce tests. 2015-05-29 11:29:43 +00:00
Tom Wilkie 28781ef08c Don't pull weavebuild container from docker hub if you don't have a cached version, build it. 2015-05-29 11:28:58 +00:00
Alvaro Saurin 8a188e23d9 Documented the '--ttl' argument 2015-05-29 12:56:45 +02:00
Alvaro Saurin 343e9b1f5a Provide the '--neg-ttl' argument too... 2015-05-29 12:56:45 +02:00
Alvaro Saurin 858a6dd253 Make the TTL returned for local answers configurable. 2015-05-29 12:50:18 +02:00
Bryan Boreham adeb0d3934 Don't bypass the actor mechanism in unit tests, to avoid race conditions 2015-05-29 10:43:19 +01:00
Bryan Boreham 0ee872def4 Lock round mockGossipComms to avoid race conditions 2015-05-29 10:27:17 +01:00
Matthias Radestock c93e789b6b use an automatically assigned port for http tests
instead of random port, which can clash

Fixes #720.
2015-05-29 10:21:41 +01:00
Paul Bellamy db217cd56f Merge pull request #785 from rade/710_reduce_interface_check_interval
reduce interface availability check interval
2015-05-29 10:17:13 +01:00
Bryan Boreham 2befd9d473 Merge remote-tracking branch 'origin/0.11' 2015-05-29 09:56:53 +01:00
Paul Bellamy 22aa1f8fe3 Merge pull request #790 from rade/754_stop_proxy
add `weave stop_proxy`
2015-05-29 09:53:32 +01:00
Paul Bellamy f919ef12d0 fix bug with WEAVEPROXY_DOCKER_ARGS not being passed through 2015-05-29 09:52:43 +01:00
Paul Bellamy 20c9bc04a7 simplifying and standardizing launch-proxy 2015-05-29 09:52:42 +01:00
Bryan Boreham e37f7de0ad Improve error reporting of too much free space 2015-05-29 09:44:24 +01:00
Bryan Boreham 72b30bc699 Fix incorrect calculation of free space
when granting a range that spans a token and inserting a new token, the
free space on the last token that we are handing over has to change.
Add unit test for this too.
2015-05-29 09:44:14 +01:00
Bryan Boreham 10c3f47bd1 Merge pull request #783 from bboreham/782-space-panic
Fixes #782
2015-05-29 09:42:44 +01:00
Matthias Radestock e31f21a6d2 add weave stop_proxy
Closes #754.
2015-05-29 09:20:56 +01:00
Matthias Radestock c87da5e873 Merge pull request #789 from rade/784_status_fail
make `weave status` fail when weave isn't running

Closes #784.
2015-05-28 23:08:43 +01:00
Matthias Radestock b0633281a1 make weave status fail when weave isn't running
but still always show weavedns status, if present

Closes #784.
2015-05-28 21:41:56 +01:00
Matthias Radestock 8dcdf8d314 reduce interface availability check interval
in order to reduce container startup times

Closes #710.
2015-05-28 19:05:09 +01:00
Bryan Boreham 915bf392e9 Improve error reporting of too much free space 2015-05-28 18:15:04 +01:00
Bryan Boreham af5531cbf6 Fix incorrect calculation of free space
when granting a range that spans a token and inserting a new token, the
free space on the last token that we are handing over has to change.
Add unit test for this too.
2015-05-28 18:12:30 +01:00
Paul Bellamy e3e5934c51 Merge remote-tracking branch 'upstream/0.11'
Conflicts:
	proxy/common.go
2015-05-28 17:43:30 +01:00
Matthias Radestock 32c35da402 Merge pull request #777 from paulbellamy/proxy-versionless
allow proxy to intercept unversioned (i.e. 1.0) urls

Closes #770. Closes #778.
2015-05-28 17:19:46 +01:00
Matthias Radestock 8dc5ae662a simplify dependencies
This isn't as precise, but these common dependencies rarely change.
2015-05-28 17:16:22 +01:00
Matthias Radestock ec6a251b7c fix dependencies 2015-05-28 17:10:42 +01:00
Paul Bellamy 147c7f503c add curl test for proxy v1.0 api support 2015-05-28 16:39:32 +01:00
Paul Bellamy c8b95a2527 fix bug in proxy handling of minimal request bodies 2015-05-28 16:24:04 +01:00
Paul Bellamy 169eb19523 allow proxy to intercept unversioned (i.e. 1.0) urls 2015-05-28 16:24:02 +01:00
Adam Harrison ed133dfe0d Merge pull request #773 from inercia/weave-restore-fallback
Restore the "--fallback" argument in WeaveDNS
2015-05-28 16:12:54 +01:00
Matthias Radestock 374b4c523c cosmetic: remove some more dead code 2015-05-28 15:24:51 +01:00
Matthias Radestock 9c71f7f1e9 cosmetic: remove some more dead code 2015-05-28 15:23:34 +01:00
Matthias Radestock ba064a906e cosmetic: remove dead code 2015-05-28 15:17:20 +01:00
Alvaro 5c1297d560 Merge pull request #775 from rade/736_load_balancing_example
add load balancing example
2015-05-28 16:01:55 +02:00
Matthias Radestock 323229dacf add load balancing example
Closes #736.
2015-05-28 14:58:04 +01:00
Matthias Radestock 0b48ea01d7 cosmetic 2015-05-28 14:48:08 +01:00
Matthias Radestock 54d34ea796 expand weavedns example to cover multi-host 2015-05-28 13:36:32 +01:00
Matthias Radestock ccd7153b9d refactor: remove excessive escaping 2015-05-28 12:29:58 +01:00
Paul Bellamy 4f89ad2342 Merge pull request #769 from rade/768_shorten_proxy_entrypoint
shorten proxy entrypoint
2015-05-28 12:16:24 +01:00
Matthias Radestock 0ed1ef1600 Merge pull request #764 from paulbellamy/status-connection-maker
Add "Direct Peers" to weave status

Closes #659. Closes #660.
2015-05-28 12:02:47 +01:00
Alvaro Saurin 7202f7e1d6 Restored "fallback" argument for forcing a fallback server (used for testing/debugging) 2015-05-28 12:57:24 +02:00
Paul Bellamy 998130fa9e Reformatting connection maker target JSON output
So `LastError` is forced to a string, not an object.
2015-05-28 11:49:46 +01:00
Paul Bellamy 637215fcff add status of connection-maker to status-json 2015-05-28 11:49:46 +01:00
Paul Bellamy d16395b8b9 weave status should include 'Direct Peers' set with 'weave connect' 2015-05-28 11:49:46 +01:00
Adam Harrison e110a8ab9c Fix erroneous use of nil value 2015-05-28 10:23:02 +01:00
Bryan Boreham cb03d1d763 Merge pull request #772 from alexwlchan/master
Thanks!
2015-05-28 10:11:58 +01:00
Alex Chan 2a1150b74e Minor spelling corrections in docs 2015-05-28 10:10:16 +01:00
Alvaro Saurin 91cbd75f92 Unit test for cache expirations 2015-05-28 11:09:07 +02:00
Alvaro Saurin 156f5be4d4 Unit test for cache invalidations as a result of AddRecord()/DeleteRecord()/etc... 2015-05-28 11:09:07 +02:00
Adam Harrison 037b6df385 Merge pull request #761 from inercia/weave-583-2
Background updates for names in the Zone database. Fixes #226.
2015-05-28 10:00:32 +01:00
Alvaro Saurin 0a803c61ff Smoke tests 2015-05-28 10:05:22 +02:00
Alvaro Saurin b8d4068a26 Force 8.8.8.8 as the DNS server in VirtualBoxes 2015-05-28 10:05:22 +02:00
Alvaro Saurin 6d2317d60e Silence "golint" errors
Silence "go vet" errors
Run goimports
2015-05-28 10:05:22 +02:00
Alvaro Saurin 40d7b2a87d Dump the zone database status on "weave status"
Fix the smoke test by updating the regexps used for parsing "weave status" output
2015-05-28 09:12:53 +02:00
Alvaro Saurin 32627eccbf Fix the timeout for the fallback server client connection 2015-05-28 09:12:53 +02:00
Matthias Radestock fc9475592d shorten proxy entrypoint
...so that the default output of the likes of `docker ps` shows some
of the real entrypoint/command.

Closes #768.
2015-05-28 06:35:10 +01:00
Matthias Radestock e950d37ed2 cosmetic: reduce noise 2015-05-28 06:31:05 +01:00
Matthias Radestock 5e0d1e6036 refactor: add some lists
in order to reduce likelihood of adding an item in one place and
forgetting to do so in another.
2015-05-27 19:04:39 +01:00
Matthias Radestock 7fc51a508a cosmetic: consistency 2015-05-27 18:28:16 +01:00
Matthias Radestock cd6ba31cfa add weavewait to 'make update' target 2015-05-27 18:27:56 +01:00
Paul Bellamy ce2e80d861 fixing build on master (broken by 68ef0e) 2015-05-27 17:37:13 +01:00
Matthias Radestock a27516b0bf Merge pull request #765 from rade/run_linter_on_travis
Run linter on travis
2015-05-27 17:31:12 +01:00
Matthias Radestock 4dc2ee034c run linter on travis 2015-05-27 17:27:22 +01:00
Matthias Radestock d68dd39839 build all exes 2015-05-27 17:22:56 +01:00
Bryan Boreham b10c2e554c Remove redundant variable 2015-05-27 16:59:12 +01:00
Matthias Radestock ac8d7a5c55 cosmetic: this is somewhat gross, but the last barrier to linter bliss 2015-05-27 16:58:12 +01:00
Matthias Radestock 68ef0e845a cosmetic: yet more linter happiness 2015-05-27 16:56:38 +01:00
Matthias Radestock c6604a6442 cosmetic: more linter happiness 2015-05-27 16:52:08 +01:00
Matthias Radestock 5920927a52 make the linter (almost) happy with ipam 2015-05-27 16:43:18 +01:00
David Wragg e30ab8abba Reduce obfuscation 2015-05-27 14:50:06 +01:00
Matthias Radestock cee6ea6762 cosmetic: it has been decreed that this was "just silly" 2015-05-27 14:18:22 +01:00
Matthias Radestock 84874b9676 cosmetic: more consistency - then on same line as if 2015-05-27 14:14:51 +01:00
Matthias Radestock f281eeb02a cosmetic: missed one 2015-05-27 13:59:09 +01:00
Alvaro Saurin 6f7e385c74 Unit tests for multiple IPs through the HTTP API 2015-05-27 14:54:30 +02:00
Alvaro Saurin 954c5cb394 Limit the number of answers we provide to users. 2015-05-27 14:54:29 +02:00
Paul Bellamy b9d666d4a5 Merge branch '0.11' 2015-05-27 13:52:13 +01:00
Matthias Radestock b26e2128e4 Merge pull request #752 from paulbellamy/proxy-ipam
Fix bug of --with-ipam not setting entrypoints

Fixes #744.
2015-05-27 13:51:06 +01:00
Matthias Radestock 09feb0c53a Merge pull request #697 from paulbellamy/peer-replacement
weave connect/forget take multiple peers

Closes #759.
2015-05-27 13:39:50 +01:00
Paul Bellamy cb73b73862 Fix bug of --with-ipam not setting entrypoints 2015-05-27 13:38:28 +01:00
Matthias Radestock de27c465ca cosmetic: remove stray semicolon 2015-05-27 13:24:53 +01:00
Matthias Radestock dd33c94188 cosmetic: semicolon shall always be preceded by a space 2015-05-27 13:23:34 +01:00
Matthias Radestock 901f0bf04e cosmetic: replace all implict $@s 2015-05-27 13:20:51 +01:00
Matthias Radestock cfbd9dc310 Merge pull request #748 from dpw/gossip-channel-names
Make gossip use channel names on the wire, not channel name hashes

Closes #745.
2015-05-27 13:03:05 +01:00
David Wragg 530a39350b Make gossip use channel names on the wire, not channel name hashes 2015-05-27 12:51:14 +01:00
Ilya Dmitrichenko 84aa629010 Must quoute WEAVE_CIDR when it is a list 2015-05-27 07:47:57 +01:00
Ilya Dmitrichenko 536d55a226 Cosmetic markdown fix in proxy docs 2015-05-27 07:13:06 +01:00
Alvaro Saurin 2d919d0a5f Remove duplicates in answers 2015-05-26 21:27:53 +02:00
Alvaro Saurin c4b07fd44e Shuffle all the replies, not only the ones from the cache... 2015-05-26 21:24:28 +02:00
Alvaro Saurin b87b942363 Check we can use our mDNS client with our mDNS server 2015-05-26 21:24:26 +02:00
Alvaro Saurin 5fe0a7fbb2 Make Stop() idempotent 2015-05-26 21:20:44 +02:00
Alvaro Saurin a159ac44d9 Unit tests for cache invalidations as a result of names updates 2015-05-26 21:20:44 +02:00
Alvaro Saurin 836d012459 Command line arg for changing the number of background updaters
Default number of background updaters raised to 4
2015-05-26 21:20:44 +02:00
Alvaro Saurin 07d15092aa Do not invoke the mDNS lookups from the server: they are used from the Zone database now...
Improved DNS server:
- simplify the handlers code, removing duplicated code
- more configuration parameters (eg, we can now disable the cache)
- we can now bind to port 0 and let the server get a free port (so we can get rid of the mess we had in the unit tests...)
2015-05-26 21:20:42 +02:00
Alvaro Saurin a23462e830 Command line option for disabling the cache 2015-05-26 21:17:24 +02:00
Alvaro Saurin 6fc6afa6a7 Unit tests for the zone lookups and names updates 2015-05-26 21:17:24 +02:00
Alvaro Saurin 64090d0b95 Improved mocks 2015-05-26 21:17:24 +02:00
Alvaro Saurin dbce56ed30 Background updates for names 2015-05-26 21:17:21 +02:00
Alvaro Saurin ea808bd08b Domain lookups, using mDNS
Added DomainLookup* to the Zone interface
2015-05-26 21:14:09 +02:00
Alvaro Saurin da2d66a6df New zone database 2015-05-26 21:14:06 +02:00
Alvaro Saurin c1d124df4c Interfaces for the mDNS client and server 2015-05-26 21:11:34 +02:00
Paul Bellamy dafe2bbc83 adding basic connect/forget test
The test for multiple connect/forget options is not amazing, because we
only have 2 test VMs to connect to each other
2015-05-26 14:37:22 +01:00
Paul Bellamy b392a7d24d weave connect should be best-effort, and return errors for any fails 2015-05-26 14:37:22 +01:00
Paul Bellamy d18640fc8c use @dpw's shell join, and remove non-posix 'local' 2015-05-26 14:37:22 +01:00
Paul Bellamy 781686ee4a weave connect/forget take multiple peers 2015-05-26 14:37:22 +01:00
223 changed files with 18352 additions and 7883 deletions

No files matched your search

+25 -12
View File
@@ -29,21 +29,34 @@ coverage.html
# Emacs backup files
*~
# Vim backup files
*.swp
# Project specific
weaver/weaver
weavedns/weavedns
weaveproxy/weaveproxy
sigproxy/sigproxy
weavewait/weavewait
prog/weaver/weaver
prog/weavedns/weavedns
prog/weavehosts/weavehosts
prog/weaveproxy/weaveproxy
prog/sigproxy/sigproxy
prog/weavewait/weavewait
prog/netcheck/netcheck
testing/cover/cover
testing/runner/runner
tools/bin
tools/build
releases
.weaver.uptodate
.weavedns.uptodate
.weaveexec.uptodate
weave.tar
weavedns.tar
weaveexec.tar
weaveexec/weave
weaveexec/weaveproxy
weaveexec/weavewait
weaveexec/sigproxy
weaveexec/docker*.tgz
prog/weaveexec/weave
prog/weaveexec/weavehosts
prog/weaveexec/weaveproxy
prog/weaveexec/weavewait
prog/weaveexec/sigproxy
prog/weaveexec/netcheck
prog/weaveexec/docker*.tgz
Vagrantfile.local
test/tls/*.pem
test/coverage
test/coverage.*
+3 -2
View File
@@ -14,9 +14,11 @@ addons:
install:
- go clean -i net
- go install -tags netgo std
- pip install --user --upgrade gcloud gsutil
- go get github.com/golang/lint/golint
- pip install --user --upgrade gsutil
- bin/setup-ci-secrets $encrypted_5ba036e89377_key $encrypted_5ba036e89377_iv
- make travis
- go get ./...
script:
- bin/lint .
@@ -24,5 +26,4 @@ script:
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
+165
View File
@@ -1,3 +1,168 @@
## Release 1.1.2
This release contains a small number of bug fixes. More details in the
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.1.2).
The release is fully compatible with 1.1.0 versions, so existing
clusters can be upgraded incrementally. When upgrading from 1.0.x,
note the compatibility information in the
[Installation & Upgrading instructions for Weave 1.1.0](https://github.com/weaveworks/weave/releases/tag/v1.1.0).
## Release 1.1.1
This release contains a number of bug fixes and minor
enhancements. More details in the
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.1.1).
The release is fully compatible with 1.1.0 versions, so existing
clusters can be upgraded incrementally. When upgrading from 1.0.x,
note the compatibility information in the
[Installation & Upgrading instructions for Weave 1.1.0](https://github.com/weaveworks/weave/releases/tag/v1.1.0).
## Release v1.1.0
**Highlights**:
- `weave launch` now launches all weave components, simplifying
startup.
- `weave status` has been completely revamped, with a much improved
presentation of the information, and the option to select and output
data in JSON.
- weaveDNS has been rewritten and embedded in the router. The new
implementation simplifies configuration, improves performance, and
provides fault resilience for services.
- the weave Docker API proxy now provides an even more seamless user
experience, and enables easier integration of weave with other
systems such as kubernetes.
- many usability improvements
- a few minor bug fixes, including a couple of security
vulnerabilities
More details in the
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.1.0).
## Release 1.0.3
This release contains a weaveDNS feature enhancement as well as minor fixes for
improved stability and robustness.
More details in the
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.0.3).
The release is fully compatible with other 1.0.x versions, so existing
clusters can be upgraded incrementally.
## Release 1.0.2
This release fixes a number of bugs, including some security
vulnerabilities in the Weave Docker API proxy, hangs and failures in
address allocation, and sporadic failures in name resolution.
More details in the
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.0.2).
The release is fully compatible with other 1.0.x versions, so existing
clusters can be upgraded incrementally.
## Release 1.0.1
This is a bug fix release, addressing the following issue:
- executing `docker run` from a Docker 1.7 client against the weave
proxy would report a `no such image` error when the requested image
wasn't present on the Docker host, instead of downloading it.
## Release 1.0.0
**Highlights**:
- It is now easier than ever to start containers and for them to
communicate, across multiple hosts. Automatic IP address allocation,
and name resolution via weaveDNS are now enabled by default, and the
proxy has become more fully-featured. In short, once weave has been
launched the following is possible:
````
host1$ docker run --name=pingme -dti ubuntu
host2$ docker run -ti ubuntu
root@d11e9287f65b:/# ping pingme
````
- Containers can now be
[load-balanced](http://docs.weave.works/weave/latest_release/weavedns.html#load-balancing)
easily.
- IP address allocation is now available across [multiple
subnets](http://docs.weave.works/weave/latest_release/ipam.html#range),
and hence can be employed when running multiple, isolated
applications.
- The proxy now supports [TLS
connections](http://docs.weave.works/weave/latest_release/proxy.html#tls),
enabling its deployment when the communication between docker
clients and the server must be secured.
There are many other new features, plus the usual assortment of bug
fixes and improvements under the hood. More detail below and in the
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.0.0).
*NB: This release changes the weave protocol version. Therefore, when
upgrading an existing installation, all hosts need to be upgraded in
order to for them to be able to communicate and form a network.*
## Release 0.11.2
This is a bug fix release, addressing the following issues:
- `weave run` did not respect DOCKER_CLIENT_ARGS. #855
- Negative result cache did not expire if requeried within TTL. #845
More details in the [change
log](https://github.com/weaveworks/weave/issues?q=milestone%3A0.11.2).
*NB: This release does not change the weave protocol version.
Therefore, when upgrading an existing 0.11 installation incrementally,
connectivity between peers will be retained.*
## Release 0.11.1
This is a bug fix release, addressing the following issues:
- The IP Allocator could crash in some relatively rare
circumstances. #782/#783.
- When the proxy failed to attach a container to the weave network,
there was no failure indication and descriptive error anywhere, and
the application process would still start. Now an error is reported
to the client (i.e. typically the Docker CLI), recorded in the proxy
logs, and the container is terminated. #788/#799.
- `weave launch-proxy --with-ipam` failed to configure the entrypoint
and DNS unless a (possibly blank) `WEAVE_CIDR` was
specified. Affected containers could start the application process
w/o the weave network interface being available, and without
functioning name resolution for the weaveDNS domain.
#744/#747/#751/#752
- The `weave status` output for the IP Allocator was misleadingly
conveying a sense of brokenness when no IP allocation requests had
been made yet. #787/#801
- When invoking `weave launch-proxy` twice, the second invocation
would output a blank line and terminate with a success exit
status. Now it reports that the proxy is already running and exits
with a non-zero status. #767/#780
- `weave launch-proxy` was not respecting WEAVEPROXY_DOCKER_ARGS, so
any user-supplied custom configuration for the weaveproxy container
was ignored. #755/#780
- The proxy was not intercepting API calls to the unversioned (1.0)
Docker Remote API. Hence none of the weave functionality was
available when Docker clients were using that version of the
API. #770/#774/#777,#809
- The proxy would crash when certain elements of the
`/containers/create` JSON were missing. We are not aware of any
Docker clients doing this, but it's been fixed regardless. #778/#777
More details in the [change
log](https://github.com/weaveworks/weave/issues?q=milestone%3A0.11.1).
*NB: This release does not change the weave protocol version.
Therefore, when upgrading an existing 0.11 installation incrementally,
connectivity between peers will be retained.*
## Release 0.11.0
**Highlights**:
+87 -64
View File
@@ -1,4 +1,4 @@
PUBLISH=publish_weave publish_weavedns publish_weaveexec
PUBLISH=publish_weave publish_weaveexec
.DEFAULT: all
.PHONY: all update tests publish $(PUBLISH) clean prerequisites build travis run-smoketests
@@ -8,97 +8,120 @@ SUDO=sudo
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_EXE=prog/weaver/weaver
WEAVEPROXY_EXE=prog/weaveproxy/weaveproxy
SIGPROXY_EXE=prog/sigproxy/sigproxy
WEAVEWAIT_EXE=prog/weavewait/weavewait
WEAVEHOSTS_EXE=prog/weavehosts/weavehosts
NETCHECK_EXE=prog/netcheck/netcheck
COVER_EXE=testing/cover/cover
RUNNER_EXE=testing/runner/runner
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEHOSTS_EXE) $(NETCHECK_EXE) $(COVER_EXE) $(RUNNER_EXE)
WEAVER_UPTODATE=.weaver.uptodate
WEAVEEXEC_UPTODATE=.weaveexec.uptodate
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE)
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
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE)
WEAVE_EXPORT=weave.tar
WEAVEEXEC_DOCKER_VERSION=1.3.1
DOCKER_DISTRIB=weaveexec/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
DOCKER_DISTRIB=prog/weaveexec/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
DOCKER_DISTRIB_URL=https://get.docker.com/builds/Linux/x86_64/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
NETGO_CHECK=@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; \
}
BUILD_FLAGS=-ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)" -tags netgo
all: $(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT)
all: $(WEAVE_EXPORT) $(COVER_EXE) $(RUNNER_EXE)
travis: $(WEAVER_EXE) $(WEAVEDNS_EXE)
travis: $(EXES)
update:
go get -u -f -v -tags -netgo ./$(dir $(WEAVER_EXE)) ./$(dir $(WEAVEDNS_EXE)) ./$(dir $(SIGPROXY_EXE)) ./$(dir $(WEAVEPROXY_EXE))
go get -u -f -v -tags netgo $(addprefix ./,$(dir $(EXES)))
$(WEAVER_EXE) $(WEAVEDNS_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE): common/*.go
$(WEAVER_EXE) $(WEAVEPROXY_EXE): common/*.go common/*/*.go net/*.go
ifeq ($(COVERAGE),true)
$(eval COVERAGE_MODULES := $(shell (go list ./$(@D); go list -f '{{join .Deps "\n"}}' ./$(@D) | grep "weaveworks") | paste -s -d,))
go get -t -tags netgo ./$(@D)
go test -c -o ./$@ $(BUILD_FLAGS) -v -covermode=atomic -coverpkg $(COVERAGE_MODULES) ./$(@D)/
else
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; \
}
go build $(BUILD_FLAGS) -o $@ ./$(@D)
endif
$(NETGO_CHECK)
$(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
$(NETCHECK_EXE): common/*.go common/*/*.go net/*.go
go get -tags netgo ./$(@D)
go build $(BUILD_FLAGS) -o $@ ./$(@D)
$(NETGO_CHECK)
# 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)
$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go nameserver/*.go prog/weaver/*.go
$(WEAVEPROXY_EXE): proxy/*.go prog/weaveproxy/main.go
$(NETCHECK_EXE): prog/netcheck/netcheck.go
$(WEAVER_EXPORT): weaver/Dockerfile $(WEAVER_EXE)
$(SUDO) docker build -t $(WEAVER_IMAGE) weaver
$(SUDO) docker save $(WEAVER_IMAGE):latest > $@
# Sigproxy and weavewait need separate rules as they fail the netgo check in
# the main build stanza due to not importing net package
$(SIGPROXY_EXE): prog/sigproxy/main.go
$(WEAVEWAIT_EXE): prog/weavewait/main.go net/*.go
$(WEAVEHOSTS_EXE): prog/weavehosts/weavehosts.go
$(COVER_EXE): testing/cover/cover.go
$(RUNNER_EXE): testing/runner/runner.go
$(WEAVEDNS_EXPORT): weavedns/Dockerfile $(WEAVEDNS_EXE)
$(SUDO) docker build -t $(WEAVEDNS_IMAGE) weavedns
$(SUDO) docker save $(WEAVEDNS_IMAGE):latest > $@
$(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(WEAVEHOSTS_EXE) $(COVER_EXE) $(RUNNER_EXE):
go get -tags netgo ./$(@D)
go build $(BUILD_FLAGS) -o $@ ./$(@D)
$(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 > $@
$(WEAVER_UPTODATE): prog/weaver/Dockerfile $(WEAVER_EXE)
$(SUDO) docker build -t $(WEAVER_IMAGE) prog/weaver
touch $@
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEHOSTS_EXE) $(NETCHECK_EXE)
cp weave prog/weaveexec/weave
cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy
cp $(WEAVEPROXY_EXE) prog/weaveexec/weaveproxy
cp $(WEAVEWAIT_EXE) prog/weaveexec/weavewait
cp $(WEAVEHOSTS_EXE) prog/weaveexec/weavehosts
cp $(NETCHECK_EXE) prog/weaveexec/netcheck
cp $(DOCKER_DISTRIB) prog/weaveexec/docker.tgz
$(SUDO) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec
touch $@
$(WEAVE_EXPORT): $(IMAGES_UPTODATE)
$(SUDO) docker save $(addsuffix :latest,$(IMAGES)) > $@
$(DOCKER_DISTRIB):
curl -o $(DOCKER_DISTRIB) $(DOCKER_DISTRIB_URL)
tests:
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
tests: $(COVER_EXE)
@test/units.sh
$(PUBLISH): publish_%:
$(PUBLISH): publish_%: $(IMAGES_UPTODATE)
$(SUDO) docker tag -f $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
$(SUDO) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
ifneq ($(UPDATE_LATEST),false)
$(SUDO) docker push $(DOCKERHUB_USER)/$*:latest
endif
publish: $(PUBLISH)
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)
-$(SUDO) docker rmi $(IMAGES)
go clean -r ./...
rm -f $(EXES) $(IMAGES_UPTODATE) $(WEAVE_EXPORT)
rm -rf test/tls/*.pem test/coverage.* test/coverage
build:
$(SUDO) go clean -i net
+62 -54
View File
@@ -1,7 +1,20 @@
# Weave - the Docker network
# Weave - weaving containers into applications
[![Build Status](https://travis-ci.org/weaveworks/weave.svg?branch=master)](https://travis-ci.org/weaveworks/weave) [![Integration Tests](https://circleci.com/gh/weaveworks/weave/tree/master.svg?style=shield&circle-token=4933c7dabb3d0383e62117565cb9d16df7b1a811)](https://circleci.com/gh/weaveworks/weave) [![Coverage Status](https://coveralls.io/repos/weaveworks/weave/badge.svg)](https://coveralls.io/r/weaveworks/weave)
# About Weaveworks
[Weaveworks](http://weave.works) is the company that develops Weave -
the most productive way for developers to connect, observe and control
Docker containers. To learn about our products, including getting
started tutorials, visit our [website](http://weave.works), read about
the
[1.0 release of Weave](http://blog.weave.works/2015/06/18/weave-1-0-is-out-and-it-is-good/),
visit our [documentation](http://docs.weave.works) or continue to read
about some of the more technical aspects of Weave in this readme.
# Weave
Weave creates a virtual network that connects Docker containers
deployed across multiple hosts and enables their automatic discovery.
@@ -32,8 +45,7 @@ capabilities, so these can continue to be used by containers.
Ensure you are running Linux (kernel 3.8 or later) and have Docker
(version 1.3.1 or later) installed. Then install weave with
sudo wget -O /usr/local/bin/weave \
https://github.com/weaveworks/weave/releases/download/latest_release/weave
sudo curl -L git.io/weave -o /usr/local/bin/weave
sudo chmod a+x /usr/local/bin/weave
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.
@@ -43,7 +55,7 @@ it locally to control a weave network on a remote host.
## Quick Start Screencast
<a href="http://youtu.be/k6r7yuSr0hE" alt="Click to watch the screencast" target="_blank">
<a href="https://youtu.be/BF9YJSPQUcY" alt="Click to watch the screencast" target="_blank">
<img src="/docs/hello-screencast.png" />
</a>
@@ -53,50 +65,43 @@ 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
On $HOST1 we run:
host1$ weave launch
host1$ C=$(weave run 10.2.1.1/24 -t -i ubuntu)
host1$ eval $(weave env)
host1$ docker run --name a1 -ti 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.
> NB: If the first command results in an error like
> `http:///var/run/docker.sock/v1.19/containers/create: dial unix
> /var/run/docker.sock: permission denied. Are you trying to connect
> to a TLS-enabled daemon without TLS?` then you likely need to be
> 'root' in order to connect to the Docker daemon. If so, run the
> above and all subsequent commands in a *single* root shell (e.g. one
> created with `sudo -s`). Do *not* prefix individual commands with
> `sudo`, since some commands modify environment entries and hence
> they all need to be executed from the same shell.
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.
The first line runs weave. The second line configures our environment
so that containers launched via the docker command line are
automatically attached to the weave network. Finally, we run our
application container.
If our application consists of more than one container on this host we
simply launch them with a variation on that second line.
That's it! If our application consists of more than one container on
this host we simply launch them with `docker run` as appropriate.
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`...
Next we repeat similar steps on `$HOST2`...
host2$ weave launch $HOST1
host2$ C=$(weave run 10.2.1.2/24 -t -i ubuntu)
host2$ eval $(weave env)
host2$ docker run --name a2 -ti 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.
The only difference, apart from the name of 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;
@@ -105,26 +110,29 @@ 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).
Weave must be started once per host. The relevant container images are
pulled down on demand, but if you wish you can preload them by running
`weave setup` - this is particularly useful for automated deployments,
and ensures that there are no delays during later operations.
Now that we've got everything set up, let's see whether our containers
can talk to each other...
On `$HOST1`...
In the container started 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
root@a1:/# ping -c 1 -q a2
PING a2.weave.local (10.40.0.2) 56(84) bytes of data.
--- a2.weave.local ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.341/0.341/0.341/0.000 ms
Similarly, on `$HOST2`...
Similarly, in the container started 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
root@a2:/# ping -c 1 -q a1
PING a1.weave.local (10.32.0.2) 56(84) bytes of data.
--- a1.weave.local ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.366/0.366/0.366/0.000 ms
So there we have it, two containers on separate hosts happily talking
to each other.
@@ -132,8 +140,8 @@ to each other.
## Find out more
* [Documentation homepage](http://docs.weave.works/weave/latest_release/)
* [Getting Started Guides](http://weave.works/guides/)
* [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)
Vendored
+3 -3
View File
@@ -5,7 +5,7 @@ require './vagrant-common.rb'
vm_ip = "172.16.0.3" # arbitrary private IP
pkgs = %w(
lxc-docker
docker-engine
aufs-tools
build-essential
ethtool
@@ -18,8 +18,8 @@ pkgs = %w(
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
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.box = "ubuntu/ubuntu-15.04-amd64"
config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/vivid/current/vivid-server-cloudimg-amd64-vagrant-disk1.box"
config.vm.network "private_network", ip: vm_ip
config.vm.provider :virtualbox do |vb|
+6 -1
View File
@@ -28,7 +28,7 @@ function lint_go {
# 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}")
lintoutput=$(golint "${filename}" | grep -vE 'comment|dot imports')
if [ "$lintoutput" != "" ]; then
result=1
echo "$lintoutput"
@@ -56,6 +56,11 @@ function lint {
return
fi
# Don't lint files that don't exist
if ! [ -a "${filename}" ]; then
return
fi
case "$ext" in
go) lint_go "${filename}"
;;
+3 -3
View File
@@ -23,14 +23,14 @@ 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 | sed 's|^ ||;s| |:|;s|[0-3]:|2:|;s|[4-7]:|6:|;s|[89ab]:|a:|;s|[c-f]:|e:|;s| |:|g'
od -txC -An -N6 /dev/urandom | ( read a b c d e f && printf "%02x:$b:$c:$d:$e:$f" $((0x$a & ~1 | 2)) )
}
case "$1" in
launch)
shift 1
for i in $(seq $START $FINISH); do
WEAVE_CONTAINER_NAME=weave$i WEAVE_PORT=$((5000+i)) $weavedir/weave launch -iface '' -name $(random_mac) -nickname weave$i -port $PORT "$@"
WEAVE_CONTAINER_NAME=weave$i WEAVE_PORT=$((5000+i)) $weavedir/weave launch-router --iface '' --name $(random_mac) --nickname weave$i --port $PORT --no-dns "$@"
done
if [ $START -ne 1 ] ; then
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$((START-1)))
@@ -48,7 +48,7 @@ case "$1" in
stop)
for i in $(seq $START $FINISH); do
echo Stopping weave$i
WEAVE_CONTAINER_NAME=weave$i $weavedir/weave stop
WEAVE_CONTAINER_NAME=weave$i $weavedir/weave stop-router
done
;;
esac
+156 -65
View File
@@ -10,15 +10,53 @@ RELEASE_DESCRIPTION=${RELEASE_DESCRIPTION:-"Weaving Containers into Applications
PWD=`pwd`
setup() {
## 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
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)
VERSION=${LATEST_TAG#v}
LATEST_RELEASE_COMMIT_SHA=$(git rev-list -1 latest_release)
if [ "$RELEASE_TYPE" != 'PRERELEASE' ] ; then
VERSION=${LATEST_TAG#v}
else
VERSION=${LATEST_TAG}
fi
# NB does not check that this tag is on master
RELEASE_DIR=./releases/$LATEST_TAG
}
@@ -39,8 +77,8 @@ build() {
cd $RELEASE_DIR
## Check that the top changelog entry is this version
if ! latest_changelog=$(grep -o "## Release [0-9].*" -m1 ./CHANGELOG.md) || \
! [ `echo "$latest_changelog" | grep -o '[0-9][^ ]*'` == "$VERSION" ]; then
if ! latest_changelog=$(grep -oP '(?<=^## Release ).*' -m1 ./CHANGELOG.md) || \
! [ `echo "$latest_changelog" = "$VERSION"` ]; then
echo -e "\u2757 Latest changelog entry \"$latest_changelog\" does not match the release version $VERSION" >&2
exit 1
fi
@@ -61,7 +99,7 @@ build() {
fi
## Run tests with the distributables, including version check
v=$(./weaver/weaver --version | grep -o '[0-9].*')
v=$(./prog/weaver/weaver --version | grep -oP '(?<=^weave router ).*')
if ! [ "$v" == "$VERSION" ]; then
echo "Version of distributable "$v" does not match release version $VERSION" >&2
exit 1
@@ -71,24 +109,13 @@ build() {
echo '** Release artefacts in' $RELEASE_DIR
}
publish() {
draft() {
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
@@ -98,19 +125,9 @@ publish() {
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 doesn't already exist by looking at github
## Check that the version does not 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
@@ -120,13 +137,13 @@ publish() {
echo '** Sanity checks OK for publishing tag' $LATEST_TAG as $DOCKERHUB_USER/weave:$VERSION
echo "== Tagging and publishing images on docker hub as user $DOCKERHUB_USER"
make SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER publish
RELEASE_ARGS="--draft"
if [ "$RELEASE_TYPE" = 'PRERELEASE' ] ; then
RELEASE_ARGS="$RELEASE_ARGS --pre-release"
fi
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 \
echo "== Creating GitHub release $RELEASE_ARGS $RELEASE_NAME $VERSION"
github-release release $RELEASE_ARGS \
--user $GITHUB_USER \
--repo weave \
--tag $LATEST_TAG \
@@ -140,52 +157,126 @@ publish() {
--name "weave" \
--file "./weave"
echo "** Published release $RELEASE_NAME $VERSION"
echo "** Draft $TYPE $RELEASE_NAME $VERSION created at"
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/$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
publish() {
setup
cd $PWD/$RELEASE_DIR
UPDATE_LATEST=false
if [ "$RELEASE_TYPE" = 'MAINLINE' ] ; then
UPDATE_LATEST=true
fi
github-release release \
--user $GITHUB_USER \
--repo weave \
--tag latest_release \
--name "$RELEASE_NAME latest ($VERSION)" \
--description "$RELEASE_DESCRIPTION"
if [ "$RELEASE_TYPE" = 'PRERELEASE' ] ; then
echo "== Tagging and pushing images on docker hub as user $DOCKERHUB_USER"
make UPDATE_LATEST=$UPDATE_LATEST 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"
echo "** Published $RELEASE_NAME as latest_release at"
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/latest_release"
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 "== Tagging and pushing images on docker hub as user $DOCKERHUB_USER"
make UPDATE_LATEST=$UPDATE_LATEST 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"
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
}
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 artefacts for the latest tag to GitHub and DockerHub"
echo "-- Publish the GitHub release and update DockerHub"
exit 1
}
case "$1" in
# 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
build)
build
build "$@"
;;
draft)
draft "$@"
;;
publish)
publish
publish "$@"
;;
*)
echo "Unknown command \"$1\""
echo "Unknown command \"$COMMAND\""
usage
;;
esac
+4 -1
View File
@@ -1,6 +1,9 @@
FROM ubuntu
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
RUN apt-get -y update && apt-get -y install --no-install-recommends ca-certificates apt-transport-https
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
RUN echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list
RUN apt-get -y update && apt-get -y install --no-install-recommends build-essential git lxc-docker-1.3.1 mercurial libpcap-dev curl make pkg-config gcc bison flex python-requests
# When doing a build in a container, "apt-get update" happens twice,
# which can be a very significant overhead for incremental builds.
+7 -2
View File
@@ -6,7 +6,7 @@ export GOPATH
WEAVE_SRC=$GOPATH/src/github.com/weaveworks/weave
if [ $# -eq 0 ] ; then
if [ $# -eq 0 -o "$1" = "tests" ] ; then
# No arguments. Expect that the weave repo will be bind-mounted
# into $GOPATH
if ! [ -e $WEAVE_SRC ] ; then
@@ -35,7 +35,12 @@ EOF
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"
if [ "$1" = "tests" ] ; then
su weave -c "PATH=$PATH make -C $WEAVE_SRC tests"
else
su weave -c "PATH=$PATH make -C $WEAVE_SRC build"
fi
else
# There are arguments to pass to git-clone
mkdir -p ${WEAVE_SRC%/*}
+19 -4
View File
@@ -21,11 +21,14 @@ dependencies:
- if [[ -e "$WEAVE_BUILD" ]]; then
docker load -i $WEAVE_BUILD;
else
docker pull weaveworks/weave-build;
docker build -t weaveworks/weave-build build;
mkdir -p $(dirname "$WEAVE_BUILD");
docker save weaveworks/weave-build >$WEAVE_BUILD;
fi
post:
- sudo apt-get update
- sudo apt-get install bc jq
- pip install requests
- curl https://sdk.cloud.google.com | bash
- bin/setup-circleci-secrets "$SECRET_PASSWORD"
- mkdir -p $(dirname $SRCDIR)
@@ -33,14 +36,26 @@ dependencies:
test:
override:
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go weaveworks/weave-build:
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go -e COVERDIR=test/coverage -e SLOW=true -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX weaveworks/weave-build tests:
parallel: true
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go -e COVERAGE=true weaveworks/weave-build:
parallel: true
- cd $SRCDIR/test; ./gce.sh setup:
parallel: true
- cd $SRCDIR/test; . ./gce.sh hosts; ./setup.sh:
- cd $SRCDIR/test; eval $(./gce.sh hosts); ./setup.sh:
parallel: true
- cd $SRCDIR/test; . ./gce.sh hosts; ./run_all.sh:
- cd $SRCDIR/test; eval $(./gce.sh hosts); export COVERAGE=true; ./run_all.sh:
parallel: true
timeout: 300
post:
- cd $SRCDIR/test; ./gce.sh destroy:
parallel: true
teardown:
pre:
- test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; ./gen_coverage_reports.sh):
parallel: true
- test "$CIRCLE_NODE_INDEX" != "0" || (go get github.com/mattn/goveralls && goveralls -repotoken $COVERALLS_REPO_TOKEN -coverprofile=$SRCDIR/test/profile.cov -service=circleci || true):
parallel: true
- test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; cp coverage.* $CIRCLE_ARTIFACTS):
parallel: true
+64
View File
@@ -0,0 +1,64 @@
package docker
import (
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
// An observer for container events
type ContainerObserver interface {
ContainerDied(ident string)
}
type Client struct {
*docker.Client
}
// NewClient creates a new Docker client and checks we can talk to Docker
func NewClient(apiPath string) (*Client, error) {
dc, err := docker.NewClient(apiPath)
if err != nil {
return nil, err
}
client := &Client{dc}
env, err := client.Version()
if err != nil {
return nil, err
}
Log.Infof("[docker] Using Docker API on %s: %v", apiPath, env)
return client, nil
}
// AddObserver adds an observer for docker events
func (c *Client) AddObserver(ob ContainerObserver) error {
events := make(chan *docker.APIEvents)
if err := c.AddEventListener(events); err != nil {
Log.Errorf("[docker] Unable to add listener to Docker API: %s", err)
return err
}
go func() {
for event := range events {
switch event.Status {
case "die":
id := event.ID
ob.ContainerDied(id)
}
}
}()
return 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
}
if _, notThere := err.(*docker.NoSuchContainer); notThere {
return true
}
Log.Errorf("[docker] Could not check container status: %s", err)
return false
}
-20
View File
@@ -1,20 +0,0 @@
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")
}
}
-10
View File
@@ -1,10 +0,0 @@
package common
import (
"io/ioutil"
"os"
)
func init() {
InitLogging(ioutil.Discard, os.Stdout, os.Stdout, os.Stderr)
}
+39 -26
View File
@@ -1,41 +1,54 @@
package common
import (
"io"
"io/ioutil"
"log"
"os"
"bytes"
"fmt"
"strings"
"github.com/Sirupsen/logrus"
)
const (
standardLogFlags = log.Ldate | log.Ltime | log.Lmicroseconds
)
type textFormatter struct {
}
// Largely taken from
// http://www.goinggo.net/2013/11/using-log-package-in-go.html
// 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
}
var (
Debug *log.Logger
Info *log.Logger
Warning *log.Logger
Error *log.Logger
standardTextFormatter = &textFormatter{}
)
func InitLogging(debugHandle io.Writer,
infoHandle io.Writer,
warningHandle io.Writer,
errorHandle io.Writer) {
var (
Log *logrus.Logger
)
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 init() {
Log = logrus.New()
Log.Formatter = standardTextFormatter
}
func InitDefaultLogging(debug bool) {
debugOut := ioutil.Discard
if debug {
debugOut = os.Stderr
func SetLogLevel(levelname string) {
level, err := logrus.ParseLevel(levelname)
if err != nil {
Log.Fatal(err)
}
InitLogging(debugOut, os.Stdout, os.Stdout, os.Stderr)
Log.Level = level
}
+31
View File
@@ -0,0 +1,31 @@
package mflagext
import (
"fmt"
"github.com/docker/docker/pkg/mflag"
)
type listOpts struct {
value *[]string
hasBeenSet bool
}
func ListVar(p *[]string, names []string, value []string, usage string) {
*p = value
mflag.Var(&listOpts{p, false}, names, usage)
}
func (opts *listOpts) Set(value string) error {
if opts.hasBeenSet {
(*opts.value) = append((*opts.value), value)
} else {
(*opts.value) = []string{value}
opts.hasBeenSet = true
}
return nil
}
func (opts *listOpts) String() string {
return fmt.Sprintf("%v", []string(*opts.value))
}
+4 -9
View File
@@ -10,28 +10,23 @@ import (
// 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)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGTERM)
buf := make([]byte, 1<<20)
for {
switch <-sigs {
case syscall.SIGINT:
Info.Printf("=== received SIGINT ===\n*** exiting\n")
case syscall.SIGINT, syscall.SIGTERM:
Log.Infof("=== received SIGINT/SIGTERM ===\n*** exiting")
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())
}
Log.Infof("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end", buf[:stacklen])
}
}
}
-47
View File
@@ -1,47 +0,0 @@
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
}
+20
View File
@@ -0,0 +1,20 @@
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")
}
+6 -6
View File
@@ -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
binary exponential backoff to regulate the period between connection
exponential backoff to regulate the period between connection
attempts.
- Local Peer
@@ -160,7 +160,7 @@ if a node receives a packet that is bigger than the next MTU, it is
required to drop the packet and send back an ICMP 3,4 packet which
informs the sending node of the next hop MTU (RFC 1191). In theory,
these ICMP packets should have all the reverse NAT and so forth
applied, to make it all the way back to the sender. In practise this
applied, to make it all the way back to the sender. In practice, this
does often work, but in some cases all ICMP are sometimes blocked,
often by idiots who don't understand that ICMP is really the error
channel of all network traffic.
@@ -187,16 +187,16 @@ If weave determines that fragmentation is reliable then when weave
sniffs large packets (i.e. packets larger than the MTU and thus don't
have DF set) then weave will encapsulate these packets as necessary
and similarly send them out as-is, without DF set, trusting to the
network to do all necessary fragementation and reassembly.
network to do all necessary fragmentation and reassembly.
However, if weave determines that fragementation is not reliable
However, if weave determines that fragmentation is not reliable
between any two peers then it will manually fragment larger packets
correctly according to the IP spec, and will then send them between
weave peers with the DF flag set (i.e. the fragmentation will ensure
that the encapsulatated traffic will not be greater than the PMTU
that the encapsulated traffic will not be greater than the PMTU
between weave peers). Because the fragmentation is done according to
the IP spec, we don't need to do reassembly ourselves - on the
receiving weave, we jus inject all the fragments and rely on the
receiving weave, we just inject all the fragments and rely on the
stack to do reassembly.
Sometimes PMTU discovery doesn't work as ICMP packets sometimes get
+2 -2
View File
@@ -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:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 76 KiB

+10 -4
View File
@@ -20,11 +20,16 @@ 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
- Allocate: request one IP address for a container in a subnet
- Lookup: fetch the previously-allocated IP address for a container in a subnet
- 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.
@@ -48,16 +53,17 @@ 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 (all owned ranges are full), it will ask another peer for space:
space in a subnet (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
owned by each peer in the subnet
- 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.
copy of the ring tells it all peers are full in that subnet.
### Claiming an address
+145
View File
@@ -0,0 +1,145 @@
# Release Process
## Prerequisites
* Up-to-date versions of `git`, `go` etc
* Install GitHub release tool `go get github.com/weaveworks/github-release`
* Create a [github token for
github-release](https://help.github.com/articles/creating-an-access-token-for-command-line-use/);
set and export `$GITHUB_TOKEN` with this value
* Update all dependencies with `make update`
## 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 version tag upstream, so that an associated
GitHub release may be created:
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
## 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.
-54
View File
@@ -1,54 +0,0 @@
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)
}
+23 -14
View File
@@ -2,7 +2,8 @@ package ipam
import (
"fmt"
"github.com/weaveworks/weave/ipam/address"
"github.com/weaveworks/weave/net/address"
)
type allocateResult struct {
@@ -12,8 +13,9 @@ type allocateResult struct {
type allocate struct {
resultChan chan<- allocateResult
hasBeenCancelled func() bool
ident string
r address.Range // Range we are trying to allocate within
hasBeenCancelled func() bool
}
// Try returns true if the request is completed, false if pending
@@ -23,34 +25,41 @@ func (g *allocate) Try(alloc *Allocator) bool {
return true
}
// If we have previously stored an address for this container, return it.
if addr, found := alloc.owned[g.ident]; found {
if addr, found := alloc.lookupOwned(g.ident, g.r); found {
g.resultChan <- allocateResult{addr, nil}
return true
}
if ok, addr := alloc.space.Allocate(); ok {
alloc.debugln("Allocated", addr, "for", g.ident)
if !alloc.universe.Overlaps(g.r) {
g.resultChan <- allocateResult{0, fmt.Errorf("range %s out of bounds: %s", g.r, alloc.universe)}
return true
}
alloc.establishRing()
if ok, addr := alloc.space.Allocate(g.r); ok {
alloc.debugln("Allocated", addr, "for", g.ident, "in", g.r)
alloc.addOwned(g.ident, addr)
g.resultChan <- allocateResult{addr, nil}
return true
}
// out of space
if donor, err := alloc.ring.ChoosePeerToAskForSpace(); err == nil {
alloc.debugln("Decided to ask peer", donor, "for space")
alloc.sendRequest(donor, msgSpaceRequest)
donors := alloc.ring.ChoosePeersToAskForSpace(g.r.Start, g.r.End)
for _, donor := range donors {
if err := alloc.sendSpaceRequest(donor, g.r); 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
}
}
return false
}
func (g *allocate) Cancel() {
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)
g.resultChan <- allocateResult{0, &errorCancelled{"Allocate", g.ident}}
}
func (g *allocate) ForContainer(ident string) bool {
+190 -140
View File
@@ -3,17 +3,15 @@ package ipam
import (
"bytes"
"encoding/gob"
"errors"
"fmt"
"net"
"sort"
"time"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/address"
"github.com/weaveworks/weave/ipam/paxos"
"github.com/weaveworks/weave/ipam/ring"
"github.com/weaveworks/weave/ipam/space"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/router"
)
@@ -21,8 +19,10 @@ import (
const (
msgSpaceRequest = iota
msgRingUpdate
msgSpaceRequestDenied
paxosInterval = time.Second * 5
MinSubnetSize = 4 // first and last addresses are excluded, so 2 would be too small
)
// operation represents something which Allocator wants to do, but
@@ -33,8 +33,6 @@ type operation interface {
Cancel()
String() string
// Does this operation pertain to the given container id?
// Used for tidying up pending operations when containers die.
ForContainer(ident string) bool
@@ -46,16 +44,14 @@ type operation interface {
type Allocator struct {
actionChan chan<- func()
ourName router.PeerName
subnetStart address.Address // start address of space all peers are allocating from
subnetSize address.Offset // length of space all peers are allocating from
prefixLen int // network prefix length, e.g. 24 for a /24 network
ring *ring.Ring // information on ranges owned by all peers
space space.Space // more detail on ranges owned by us
owned map[string]address.Address // who owns what address, indexed by container-ID
nicknames map[router.PeerName]string // so we can map nicknames for rmpeer
pendingAllocates []operation // held until we get some free space
pendingClaims []operation // held until we know who owns the space
gossip router.Gossip // our link to the outside world for sending messages
universe address.Range // superset of all ranges
ring *ring.Ring // information on ranges owned by all peers
space space.Space // more detail on ranges owned by us
owned map[string][]address.Address // who owns what addresses, indexed by container-ID
nicknames map[router.PeerName]string // so we can map nicknames for rmpeer
pendingAllocates []operation // held until we get some free space
pendingClaims []operation // held until we know who owns the space
gossip router.Gossip // our link to the outside world for sending messages
paxos *paxos.Node
paxosTicker *time.Ticker
shuttingDown bool // to avoid doing any requests while trying to shut down
@@ -63,34 +59,16 @@ type Allocator struct {
}
// NewAllocator creates and initialises a new Allocator
func NewAllocator(ourName router.PeerName, ourUID router.PeerUID, ourNickname string, subnetCIDR string, quorum uint) (*Allocator, error) {
_, subnet, err := net.ParseCIDR(subnetCIDR)
if err != nil {
return nil, err
}
if subnet.IP.To4() == nil {
return nil, errors.New("Non-IPv4 address not supported")
}
// Get the size of the network from the mask
ones, bits := subnet.Mask.Size()
var subnetSize address.Offset = 1 << uint(bits-ones)
if subnetSize < 4 {
return nil, errors.New("Allocation subnet too small")
}
subnetStart := address.FromIP4(subnet.IP)
alloc := &Allocator{
ourName: ourName,
subnetStart: subnetStart,
subnetSize: subnetSize,
prefixLen: ones,
// per RFC 1122, don't allocate the first and last address in the subnet
ring: ring.New(address.Add(subnetStart, 1), address.Add(subnetStart, subnetSize-1), ourName),
owned: make(map[string]address.Address),
func NewAllocator(ourName router.PeerName, ourUID router.PeerUID, ourNickname string, universe address.Range, quorum uint) *Allocator {
return &Allocator{
ourName: ourName,
universe: universe,
ring: ring.New(universe.Start, universe.End, ourName),
owned: make(map[string][]address.Address),
paxos: paxos.NewNode(ourName, ourUID, quorum),
nicknames: map[router.PeerName]string{ourName: ourNickname},
now: time.Now,
}
return alloc, nil
}
// Start runs the allocator goroutine
@@ -115,7 +93,6 @@ func (alloc *Allocator) doOperation(op operation, ops *[]operation) {
op.Cancel()
return
}
alloc.establishRing()
if !op.Try(alloc) {
*ops = append(*ops, op)
}
@@ -149,11 +126,13 @@ func (alloc *Allocator) cancelOps(ops *[]operation) {
// if we found any.
func (alloc *Allocator) cancelOpsFor(ops *[]operation, ident string) bool {
var found bool
for i, op := range *ops {
if op.ForContainer(ident) {
for i := 0; i < len(*ops); {
if op := (*ops)[i]; op.ForContainer(ident) {
found = true
op.Cancel()
*ops = append((*ops)[:i], (*ops)[i+1:]...)
} else {
i++
}
}
return found
@@ -184,6 +163,18 @@ func (alloc *Allocator) tryPendingOps() {
}
}
func (alloc *Allocator) spaceRequestDenied(sender router.PeerName, r address.Range) {
for i := 0; i < len(alloc.pendingClaims); {
claim := alloc.pendingClaims[i].(*claim)
if r.Contains(claim.addr) {
claim.deniedBy(alloc, sender)
alloc.pendingClaims = append(alloc.pendingClaims[:i], alloc.pendingClaims[i+1:]...)
continue
}
i++
}
}
func hasBeenCancelled(cancelChan <-chan bool) func() bool {
return func() bool {
select {
@@ -195,44 +186,63 @@ func hasBeenCancelled(cancelChan <-chan bool) func() bool {
}
}
type errorCancelled struct {
kind string
ident string
}
func (e *errorCancelled) Error() string {
return fmt.Sprintf("%s request for %s cancelled", e.kind, e.ident)
}
// Actor client API
// Allocate (Sync) - get IP address for container with given name
// if there isn't any space we block indefinitely
func (alloc *Allocator) Allocate(ident string, cancelChan <-chan bool) (address.Address, error) {
// Allocate (Sync) - get new IP address for container with given name in range
// if there isn't any space in that range we block indefinitely
func (alloc *Allocator) Allocate(ident string, r address.Range, cancelChan <-chan bool) (address.Address, error) {
resultChan := make(chan allocateResult)
op := &allocate{resultChan: resultChan, ident: ident,
op := &allocate{resultChan: resultChan, ident: ident, r: r,
hasBeenCancelled: hasBeenCancelled(cancelChan)}
alloc.doOperation(op, &alloc.pendingAllocates)
result := <-resultChan
return result.addr, result.err
}
// Lookup (Sync) - get existing IP address for container with given name in range
func (alloc *Allocator) Lookup(ident string, r address.Range) (address.Address, error) {
resultChan := make(chan allocateResult)
alloc.actionChan <- func() {
if addr, found := alloc.lookupOwned(ident, r); found {
resultChan <- allocateResult{addr: addr}
return
}
resultChan <- allocateResult{err: fmt.Errorf("lookup: no address found for %s in range %s", ident, r)}
}
result := <-resultChan
return result.addr, result.err
}
// Claim an address that we think we should own (Sync)
func (alloc *Allocator) Claim(ident string, addr address.Address, cancelChan <-chan bool) error {
func (alloc *Allocator) Claim(ident string, addr address.Address, noErrorOnUnknown bool) error {
resultChan := make(chan error)
op := &claim{resultChan: resultChan, ident: ident, addr: addr,
hasBeenCancelled: hasBeenCancelled(cancelChan)}
op := &claim{resultChan: resultChan, ident: ident, addr: addr, noErrorOnUnknown: noErrorOnUnknown}
alloc.doOperation(op, &alloc.pendingClaims)
return <-resultChan
}
// Free (Sync) - release IP address for container with given name
func (alloc *Allocator) Free(ident string) error {
return alloc.free(ident)
// ContainerDied is provided to satisfy the updater interface; does a 'Delete' underneath. Sync.
func (alloc *Allocator) ContainerDied(ident string) {
if err := alloc.Delete(ident); err == nil {
alloc.debugln("Container", ident, "died; released addresses")
}
}
// ContainerDied is provided to satisfy the updater interface; does a free underneath. Async.
func (alloc *Allocator) ContainerDied(ident string) error {
alloc.debugln("Container", ident, "died; releasing addresses")
return alloc.free(ident)
}
func (alloc *Allocator) free(ident string) error {
// Delete (Sync) - release all IP addresses for container with given name
func (alloc *Allocator) Delete(ident string) error {
errChan := make(chan error)
alloc.actionChan <- func() {
addr, found := alloc.owned[ident]
if found {
addrs, found := alloc.owned[ident]
for _, addr := range addrs {
alloc.space.Free(addr)
}
delete(alloc.owned, ident)
@@ -242,7 +252,7 @@ func (alloc *Allocator) free(ident string) error {
found = alloc.cancelOpsFor(&alloc.pendingClaims, ident) || found
if !found {
errChan <- fmt.Errorf("Free: no addresses for %s", ident)
errChan <- fmt.Errorf("Delete: no addresses for %s", ident)
return
}
errChan <- nil
@@ -250,13 +260,28 @@ func (alloc *Allocator) free(ident string) error {
return <-errChan
}
// Sync.
func (alloc *Allocator) String() string {
resultChan := make(chan string)
// Free (Sync) - release single IP address for container
func (alloc *Allocator) Free(ident string, addrToFree address.Address) error {
errChan := make(chan error)
alloc.actionChan <- func() {
resultChan <- alloc.string()
addrs := alloc.owned[ident]
for i, ownedAddr := range addrs {
if ownedAddr == addrToFree {
alloc.debugln("Freed", addrToFree, "for", ident)
if len(addrs) == 1 {
delete(alloc.owned, ident)
} else {
alloc.owned[ident] = append(addrs[:i], addrs[i+1:]...)
}
alloc.space.Free(addrToFree)
errChan <- nil
return
}
}
errChan <- fmt.Errorf("Free: address %s not found for %s", addrToFree, ident)
}
return <-resultChan
return <-errChan
}
// Shutdown (Sync)
@@ -296,7 +321,7 @@ func (alloc *Allocator) AdminTakeoverRanges(peerNameOrNickname string) error {
}
delete(alloc.nicknames, peername)
err, newRanges := alloc.ring.Transfer(peername, alloc.ourName)
newRanges, err := alloc.ring.Transfer(peername, alloc.ourName)
alloc.space.AddRanges(newRanges)
resultChan <- err
}
@@ -320,13 +345,18 @@ func (alloc *Allocator) lookupPeername(name string) (router.PeerName, error) {
// Restrict the peers in "nicknames" to those in the ring and our own
func (alloc *Allocator) pruneNicknames() {
ringPeers := alloc.ring.PeerNames()
for name, _ := range alloc.nicknames {
for name := range alloc.nicknames {
if _, ok := ringPeers[name]; !ok && name != alloc.ourName {
delete(alloc.nicknames, name)
}
}
}
func decodeRange(msg []byte) (r address.Range, err error) {
decoder := gob.NewDecoder(bytes.NewReader(msg))
return r, decoder.Decode(&r)
}
// OnGossipUnicast (Sync)
func (alloc *Allocator) OnGossipUnicast(sender router.PeerName, msg []byte) error {
alloc.debugln("OnGossipUnicast from", sender, ": ", len(msg), "bytes")
@@ -335,28 +365,37 @@ func (alloc *Allocator) OnGossipUnicast(sender router.PeerName, msg []byte) erro
switch msg[0] {
case msgSpaceRequest:
// some other peer asked us for space
alloc.donateSpace(sender)
resultChan <- nil
r, err := decodeRange(msg[1:])
if err == nil {
alloc.donateSpace(r, sender)
}
resultChan <- err
case msgSpaceRequestDenied:
r, err := decodeRange(msg[1:])
if err == nil {
alloc.spaceRequestDenied(sender, r)
}
resultChan <- err
case msgRingUpdate:
resultChan <- alloc.update(msg[1:])
resultChan <- alloc.update(sender, msg[1:])
}
}
return <-resultChan
}
// OnGossipBroadcast (Sync)
func (alloc *Allocator) OnGossipBroadcast(msg []byte) (router.GossipData, error) {
alloc.debugln("OnGossipBroadcast:", len(msg), "bytes")
func (alloc *Allocator) OnGossipBroadcast(sender router.PeerName, msg []byte) (router.GossipData, error) {
alloc.debugln("OnGossipBroadcast from", sender, ":", len(msg), "bytes")
resultChan := make(chan error)
alloc.actionChan <- func() {
resultChan <- alloc.update(msg)
resultChan <- alloc.update(sender, msg)
}
return alloc.Gossip(), <-resultChan
}
type gossipState struct {
// We send a timstamp along with the information to be
// gossipped in order to do detect skewed clocks
// gossipped in order to detect skewed clocks
Now int64
Nicknames map[router.PeerName]string
@@ -398,7 +437,7 @@ func (alloc *Allocator) OnGossip(msg []byte) (router.GossipData, error) {
alloc.debugln("Allocator.OnGossip:", len(msg), "bytes")
resultChan := make(chan error)
alloc.actionChan <- func() {
resultChan <- alloc.update(msg)
resultChan <- alloc.update(router.UnknownPeerName, msg)
}
return nil, <-resultChan // for now, we never propagate updates. TBD
}
@@ -413,8 +452,8 @@ func (d *ipamGossipData) Merge(other router.GossipData) {
// no-op
}
func (d *ipamGossipData) Encode() []byte {
return d.alloc.Encode()
func (d *ipamGossipData) Encode() [][]byte {
return [][]byte{d.alloc.Encode()}
}
// Gossip returns a GossipData implementation, which in this case always
@@ -454,39 +493,6 @@ func (alloc *Allocator) actorLoop(actionChan <-chan func()) {
// Helper functions
func (alloc *Allocator) string() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "Allocator subnet %s/%d\n", alloc.subnetStart.String(), alloc.prefixLen)
if alloc.ring.Empty() {
fmt.Fprintf(&buf, "Awaiting consensus: %s", alloc.paxos.String())
} else {
localFreeSpace := alloc.space.NumFreeAddresses()
remoteFreeSpace := alloc.ring.TotalRemoteFree()
percentFree := 100 * float64(localFreeSpace+remoteFreeSpace) / float64(alloc.subnetSize)
fmt.Fprintf(&buf, " Free IPs: ~%.1f%%, %d local, ~%d remote\n",
percentFree, localFreeSpace, remoteFreeSpace)
fmt.Fprint(&buf, "Owned Ranges:")
alloc.ring.FprintWithNicknames(&buf, alloc.nicknames)
if len(alloc.pendingAllocates)+len(alloc.pendingClaims) > 0 {
fmt.Fprintf(&buf, "\nPending requests for ")
for _, op := range alloc.pendingAllocates {
fmt.Fprintf(&buf, "%s, ", op.String())
}
for _, op := range alloc.pendingClaims {
fmt.Fprintf(&buf, "%s, ", op.String())
}
}
}
return buf.String()
}
func (alloc *Allocator) consensus() []router.PeerName {
_, val := alloc.paxos.Consensus()
return val.Value
}
// Ensure we are making progress towards an established ring
func (alloc *Allocator) establishRing() {
if !alloc.ring.Empty() || alloc.paxosTicker != nil {
@@ -494,10 +500,10 @@ func (alloc *Allocator) establishRing() {
}
alloc.propose()
if cons := alloc.consensus(); cons != nil {
if ok, cons := alloc.paxos.Consensus(); ok {
// If the quorum was 1, then proposing immediately
// leads to consensus
alloc.createRing(cons)
alloc.createRing(cons.Value)
} else {
// re-try until we get consensus
alloc.paxosTicker = time.NewTicker(paxosInterval)
@@ -562,12 +568,31 @@ func (alloc *Allocator) propose() {
alloc.gossip.GossipBroadcast(alloc.Gossip())
}
func (alloc *Allocator) sendRequest(dest router.PeerName, kind byte) {
msg := router.Concat([]byte{kind}, alloc.encode())
func encodeRange(r address.Range) []byte {
buf := new(bytes.Buffer)
enc := gob.NewEncoder(buf)
if err := enc.Encode(r); err != nil {
panic(err)
}
return buf.Bytes()
}
func (alloc *Allocator) sendSpaceRequest(dest router.PeerName, r address.Range) error {
msg := router.Concat([]byte{msgSpaceRequest}, encodeRange(r))
return alloc.gossip.GossipUnicast(dest, msg)
}
func (alloc *Allocator) sendSpaceRequestDenied(dest router.PeerName, r address.Range) error {
msg := router.Concat([]byte{msgSpaceRequestDenied}, encodeRange(r))
return alloc.gossip.GossipUnicast(dest, msg)
}
func (alloc *Allocator) sendRingUpdate(dest router.PeerName) {
msg := router.Concat([]byte{msgRingUpdate}, alloc.encode())
alloc.gossip.GossipUnicast(dest, msg)
}
func (alloc *Allocator) update(msg []byte) error {
func (alloc *Allocator) update(sender router.PeerName, msg []byte) error {
reader := bytes.NewReader(msg)
decoder := gob.NewDecoder(reader)
var data gossipState
@@ -591,6 +616,10 @@ func (alloc *Allocator) update(msg []byte) error {
// shouldn't get updates for a empty Ring. But tolerate
// them just in case.
if data.Ring != nil {
if data.Ring.Range() != alloc.universe {
return fmt.Errorf("Incompatible IP allocation range %s; ours is %s",
data.Ring.Range().AsCIDRString(), alloc.universe.AsCIDRString())
}
err = alloc.ring.Merge(*data.Ring)
if !alloc.ring.Empty() {
alloc.pruneNicknames()
@@ -599,41 +628,50 @@ func (alloc *Allocator) update(msg []byte) error {
return err
}
if data.Paxos != nil && alloc.ring.Empty() {
if alloc.paxos.Update(data.Paxos) {
if alloc.paxos.Think() {
// If something important changed, broadcast
alloc.gossip.GossipBroadcast(alloc.Gossip())
}
if data.Paxos != nil {
if alloc.ring.Empty() {
if alloc.paxos.Update(data.Paxos) {
if alloc.paxos.Think() {
// If something important changed, broadcast
alloc.gossip.GossipBroadcast(alloc.Gossip())
}
if cons := alloc.consensus(); cons != nil {
alloc.createRing(cons)
if ok, cons := alloc.paxos.Consensus(); ok {
alloc.createRing(cons.Value)
}
}
} else if sender != router.UnknownPeerName {
// Sender is trying to initialize a ring, but we have one
// already - send it straight back
alloc.sendRingUpdate(sender)
}
}
return nil
}
func (alloc *Allocator) donateSpace(to router.PeerName) {
func (alloc *Allocator) donateSpace(r address.Range, to router.PeerName) {
// No matter what we do, we'll send a unicast gossip
// of our ring back to tha chap who asked for space.
// This serves to both tell him of any space we might
// have given him, or tell him where he might find some
// more.
defer alloc.sendRequest(to, msgRingUpdate)
defer alloc.sendRingUpdate(to)
alloc.debugln("Peer", to, "asked me for space")
start, size, ok := alloc.space.Donate()
chunk, ok := alloc.space.Donate(r)
if !ok {
free := alloc.space.NumFreeAddresses()
free := alloc.space.NumFreeAddressesInRange(r)
common.Assert(free == 0)
alloc.debugln("No space to give to peer", to)
// separate message maintains backwards-compatibility:
// down-level peers will ignore this and still get the ring update.
alloc.sendSpaceRequestDenied(to, r)
return
}
end := address.Add(start, size)
alloc.debugln("Giving range", start, end, size, "to", to)
alloc.ring.GrantRangeToHost(start, end, to)
alloc.debugln("Giving range", chunk, "to", to)
alloc.ring.GrantRangeToHost(chunk.Start, chunk.End, to)
alloc.sendRingUpdate(to)
}
func (alloc *Allocator) assertInvariants() {
@@ -661,21 +699,33 @@ func (alloc *Allocator) reportFreeSpace() {
freespace := make(map[address.Address]address.Offset)
for _, r := range ranges {
freespace[r.Start] = alloc.space.NumFreeAddressesInRange(r.Start, r.End)
freespace[r.Start] = alloc.space.NumFreeAddressesInRange(r)
}
alloc.ring.ReportFree(freespace)
}
// Owned addresses
// NB: addr must not be owned by ident already
func (alloc *Allocator) addOwned(ident string, addr address.Address) {
alloc.owned[ident] = addr
alloc.owned[ident] = append(alloc.owned[ident], addr)
}
func (alloc *Allocator) lookupOwned(ident string, r address.Range) (address.Address, bool) {
for _, addr := range alloc.owned[ident] {
if r.Contains(addr) {
return addr, true
}
}
return 0, false
}
func (alloc *Allocator) findOwner(addr address.Address) string {
for ident, candidate := range alloc.owned {
if candidate == addr {
return ident
for ident, addrs := range alloc.owned {
for _, candidate := range addrs {
if candidate == addr {
return ident
}
}
}
return ""
@@ -684,11 +734,11 @@ func (alloc *Allocator) findOwner(addr address.Address) string {
// Logging
func (alloc *Allocator) infof(fmt string, args ...interface{}) {
common.Info.Printf("[allocator %s] "+fmt, append([]interface{}{alloc.ourName}, args...)...)
common.Log.Infof("[allocator %s] "+fmt, append([]interface{}{alloc.ourName}, args...)...)
}
func (alloc *Allocator) debugln(args ...interface{}) {
common.Debug.Println(append([]interface{}{fmt.Sprintf("[allocator %s]:", alloc.ourName)}, args...)...)
common.Log.Debugln(append([]interface{}{fmt.Sprintf("[allocator %s]:", alloc.ourName)}, args...)...)
}
func (alloc *Allocator) debugf(fmt string, args ...interface{}) {
common.Debug.Printf("[allocator %s] "+fmt, append([]interface{}{alloc.ourName}, args...)...)
common.Log.Debugf("[allocator %s] "+fmt, append([]interface{}{alloc.ourName}, args...)...)
}
+230 -117
View File
@@ -7,10 +7,11 @@ import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/address"
"github.com/weaveworks/weave/router"
wt "github.com/weaveworks/weave/testing"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/testing/gossip"
)
const (
@@ -24,41 +25,53 @@ func TestAllocFree(t *testing.T) {
container1 = "abcdef"
container2 = "baddf00d"
container3 = "b01df00d"
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
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
)
alloc := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
alloc, subnet := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
defer alloc.Stop()
_, cidr1, _ := address.ParseCIDR(subnet1)
_, cidr2, _ := address.ParseCIDR(subnet2)
alloc.claimRingForTesting()
addr1, _ := alloc.Allocate(container1, nil)
wt.AssertEqualString(t, addr1.String(), testAddr1, "address")
addr1, err := alloc.Allocate(container1, cidr1.HostRange(), nil)
require.NoError(t, err)
require.Equal(t, testAddr1, addr1.String(), "address")
addr2, err := alloc.Allocate(container1, cidr2.HostRange(), nil)
require.NoError(t, err)
require.Equal(t, testAddr2, addr2.String(), "address")
// Ask for another address for a different container and check it's different
addr2, _ := alloc.Allocate(container2, nil)
if addr2.String() == testAddr1 {
t.Fatalf("Expected different address but got %s", addr2.String())
addr1b, _ := alloc.Allocate(container2, cidr1.HostRange(), nil)
if addr1b.String() == testAddr1 {
t.Fatalf("Expected different address but got %s", addr1b.String())
}
// 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")
// Ask for the first container again and we should get the same addresses again
addr1a, _ := alloc.Allocate(container1, cidr1.HostRange(), nil)
require.Equal(t, testAddr1, addr1a.String(), "address")
addr2a, _ := alloc.Allocate(container1, cidr2.HostRange(), nil)
require.Equal(t, testAddr2, addr2a.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")
// Now delete the first container, and we should get its addresses back
require.NoError(t, alloc.Delete(container1))
addr3, _ := alloc.Allocate(container3, cidr1.HostRange(), nil)
require.Equal(t, testAddr1, addr3.String(), "address")
addr4, _ := alloc.Allocate(container3, cidr2.HostRange(), nil)
require.Equal(t, testAddr2, addr4.String(), "address")
alloc.ContainerDied(container2)
alloc.ContainerDied(container3)
alloc.String() // force sync-up after async call
wt.AssertEquals(t, alloc.space.NumFreeAddresses(), address.Offset(spaceSize))
require.Equal(t, address.Offset(spaceSize), alloc.NumFreeAddresses(subnet))
}
func TestBootstrap(t *testing.T) {
common.InitDefaultLogging(false)
const (
donateSize = 5
donateStart = "10.0.1.7"
@@ -66,21 +79,21 @@ func TestBootstrap(t *testing.T) {
peerNameString = "02:00:00:02:00:00"
)
alloc1 := makeAllocatorWithMockGossip(t, ourNameString, testStart1+"/22", 2)
alloc1, subnet := 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.encode())
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
alloc1.tryPendingOps()
alloc1.actionChan <- func() { alloc1.tryPendingOps() }
ExpectBroadcastMessage(alloc1, nil) // alloc1 will try to form consensus
done := make(chan bool)
go func() {
alloc1.Allocate("somecontainer", nil)
alloc1.Allocate("somecontainer", subnet, nil)
done <- true
}()
time.Sleep(100 * time.Millisecond)
@@ -88,26 +101,26 @@ func TestBootstrap(t *testing.T) {
CheckAllExpectedMessagesSent(alloc1, alloc2)
alloc1.tryPendingOps()
alloc1.actionChan <- func() { alloc1.tryPendingOps() }
AssertNothingSent(t, done)
CheckAllExpectedMessagesSent(alloc1, alloc2)
// alloc2 receives paxos update and broadcasts its reply
ExpectBroadcastMessage(alloc2, nil)
alloc2.OnGossipBroadcast(alloc1.encode())
alloc2.OnGossipBroadcast(alloc1.ourName, alloc1.Encode())
ExpectBroadcastMessage(alloc1, nil)
alloc1.OnGossipBroadcast(alloc2.encode())
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
// both nodes will get consensus now so initialize the ring
ExpectBroadcastMessage(alloc2, nil)
ExpectBroadcastMessage(alloc2, nil)
alloc2.OnGossipBroadcast(alloc1.encode())
alloc2.OnGossipBroadcast(alloc1.ourName, alloc1.Encode())
CheckAllExpectedMessagesSent(alloc1, alloc2)
alloc1.OnGossipBroadcast(alloc2.encode())
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
// now alloc1 should have space
AssertSent(t, done)
@@ -118,26 +131,45 @@ func TestBootstrap(t *testing.T) {
func TestAllocatorClaim(t *testing.T) {
const (
container1 = "abcdef"
container2 = "baddf00d"
container3 = "b01df00d"
universe = "10.0.3.0/30"
testAddr1 = "10.0.3.1" // first address allocated should be .1 because .0 is network addr
universe = "10.0.3.0/24"
testAddr1 = "10.0.3.2"
testAddr2 = "10.0.4.2"
)
alloc := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
allocs, _, subnet := makeNetworkOfAllocators(2, universe)
alloc := allocs[1]
defer alloc.Stop()
addr1, _ := address.ParseIP(testAddr1)
alloc.claimRingForTesting()
addr1, _ := alloc.Allocate(container1, nil)
alloc.Allocate(container2, nil)
// First claim should trigger "dunno, I'm going to wait"
err := alloc.Claim(container3, addr1, true)
require.NoError(t, err)
// 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")
// Do one allocate to ensure paxos is all done
alloc.Allocate("unused", subnet, nil)
// Do an allocate on the other peer, which we will try to claim later
addrx, err := allocs[0].Allocate(container1, subnet, nil)
// Now try the claim again
err = alloc.Claim(container3, addr1, true)
require.NoError(t, err)
// Check we get this address back if we try an allocate
addr3, _ := alloc.Allocate(container3, subnet, nil)
require.Equal(t, testAddr1, addr3.String(), "address")
// one more claim should still work
err = alloc.Claim(container3, addr1, true)
require.NoError(t, err)
// claim for a different container should fail
err = alloc.Claim(container1, addr1, true)
require.Error(t, err)
// claiming the address allocated on the other peer should fail
err = alloc.Claim(container1, addrx, true)
require.Error(t, err, "claiming address allocated on other peer should fail")
// Check an address outside of our universe
addr2, _ := address.ParseIP(testAddr2)
err = alloc.Claim(container1, addr2, true)
require.NoError(t, err)
}
func (alloc *Allocator) pause() func() {
@@ -153,18 +185,17 @@ func (alloc *Allocator) pause() func() {
}
func TestCancel(t *testing.T) {
common.InitDefaultLogging(false)
const (
CIDR = "10.0.1.7/26"
)
router := TestGossipRouter{make(map[router.PeerName]chan gossipMessage), 0.0}
router := gossip.NewTestRouter(0.0)
alloc1 := makeAllocator("01:00:00:02:00:00", CIDR, 2)
alloc1.SetInterfaces(router.connect(alloc1.ourName, alloc1))
alloc1, subnet := 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))
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)
@@ -172,15 +203,15 @@ func TestCancel(t *testing.T) {
alloc2.Start()
// tell peers about each other
alloc1.OnGossipBroadcast(alloc2.encode())
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
// Get some IPs, so each allocator has some space
res1, _ := alloc1.Allocate("foo", nil)
common.Debug.Printf("res1 = %s", res1.String())
res2, _ := alloc2.Allocate("bar", nil)
common.Debug.Printf("res2 = %s", res2.String())
res1, _ := alloc1.Allocate("foo", subnet, nil)
common.Log.Debugf("res1 = %s", res1.String())
res2, _ := alloc2.Allocate("bar", subnet, nil)
common.Log.Debugf("res2 = %s", res2.String())
if res1 == res2 {
wt.Fatalf(t, "Error: got same ips!")
require.FailNow(t, "Error: got same ips!")
}
// Now we're going to pause alloc2 and ask alloc1
@@ -188,24 +219,56 @@ 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.space.NumFreeAddresses() > 0; i++ {
alloc1.Allocate(fmt.Sprintf("tmp%d", i), nil)
for i := 0; alloc1.NumFreeAddresses(subnet) > 0; i++ {
alloc1.Allocate(fmt.Sprintf("tmp%d", i), subnet, nil)
}
cancelChan := make(chan bool, 1)
doneChan := make(chan bool)
go func() {
_, ok := alloc1.Allocate("baz", cancelChan)
_, ok := alloc1.Allocate("baz", subnet, cancelChan)
doneChan <- ok == nil
}()
AssertNothingSent(t, doneChan)
time.Sleep(100 * time.Millisecond)
AssertNothingSent(t, doneChan)
cancelChan <- true
unpause()
if <-doneChan {
wt.Fatalf(t, "Error: got result from Allocate")
require.FailNow(t, "Error: got result from Allocate")
}
}
func TestCancelOnDied(t *testing.T) {
const (
CIDR = "10.0.1.7/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, nil)
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")
}
}
@@ -214,20 +277,18 @@ 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 := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
alloc, subnet := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
defer alloc.Stop()
alloc.claimRingForTesting()
addr1, _ := alloc.Allocate(container1, nil)
wt.AssertEqualString(t, addr1.String(), testAddr1, "address")
alloc.Allocate(container1, subnet, nil)
alloc.Shutdown()
_, err := alloc.Allocate(container2, nil) // trying to allocate after shutdown should fail
wt.AssertFalse(t, err == nil, "no address")
_, err := alloc.Allocate(container2, subnet, nil) // trying to allocate after shutdown should fail
require.False(t, err == nil, "no address")
CheckAllExpectedMessagesSent(alloc)
}
@@ -236,59 +297,61 @@ func TestTransfer(t *testing.T) {
const (
cidr = "10.0.1.7/22"
)
allocs, router := makeNetworkOfAllocators(3, cidr)
allocs, router, subnet := makeNetworkOfAllocators(3, cidr)
alloc1 := allocs[0]
alloc2 := allocs[1]
alloc3 := allocs[2] // This will be 'master' and get the first range
_, err := alloc2.Allocate("foo", nil)
wt.AssertTrue(t, err == nil, "Failed to get address")
_, err := alloc2.Allocate("foo", subnet, nil)
require.True(t, err == nil, "Failed to get address")
_, err = alloc3.Allocate("bar", nil)
wt.AssertTrue(t, err == nil, "Failed to get address")
_, err = alloc3.Allocate("bar", subnet, nil)
require.True(t, err == nil, "Failed to get address")
router.GossipBroadcast(alloc2.Gossip())
router.GossipBroadcast(alloc3.Gossip())
router.removePeer(alloc2.ourName)
router.removePeer(alloc3.ourName)
alloc2.gossip.GossipBroadcast(alloc2.Gossip())
router.Flush()
alloc2.gossip.GossipBroadcast(alloc3.Gossip())
router.Flush()
router.RemovePeer(alloc2.ourName)
router.RemovePeer(alloc3.ourName)
alloc2.Stop()
alloc3.Stop()
wt.AssertSuccess(t, alloc1.AdminTakeoverRanges(alloc2.ourName.String()))
wt.AssertSuccess(t, alloc1.AdminTakeoverRanges(alloc3.ourName.String()))
router.Flush()
require.NoError(t, alloc1.AdminTakeoverRanges(alloc2.ourName.String()))
require.NoError(t, alloc1.AdminTakeoverRanges(alloc3.ourName.String()))
router.Flush()
wt.AssertEquals(t, alloc1.space.NumFreeAddresses(), address.Offset(1022))
require.Equal(t, address.Offset(1022), alloc1.NumFreeAddresses(subnet))
_, err = alloc1.Allocate("foo", nil)
wt.AssertTrue(t, err == nil, "Failed to get address")
_, err = alloc1.Allocate("foo", subnet, nil)
require.True(t, err == nil, "Failed to get address")
alloc1.Stop()
}
func TestFakeRouterSimple(t *testing.T) {
common.InitDefaultLogging(false)
const (
cidr = "10.0.1.7/22"
)
allocs, _ := makeNetworkOfAllocators(2, cidr)
allocs, _, subnet := makeNetworkOfAllocators(2, cidr)
defer stopNetworkOfAllocators(allocs)
alloc1 := allocs[0]
//alloc2 := allocs[1]
addr, _ := alloc1.Allocate("foo", nil)
println("Got addr", addr)
_, err := alloc1.Allocate("foo", subnet, nil)
require.NoError(t, err, "Failed to get address")
}
func TestAllocatorFuzz(t *testing.T) {
common.InitDefaultLogging(false)
const (
firstpass = 1000
secondpass = 5000
nodes = 10
secondpass = 10000
nodes = 5
maxAddresses = 1000
concurrency = 10
concurrency = 5
cidr = "10.0.1.7/22"
)
allocs, _ := makeNetworkOfAllocators(nodes, cidr)
allocs, _, subnet := makeNetworkOfAllocators(nodes, cidr)
defer stopNetworkOfAllocators(allocs)
// Test state
@@ -298,6 +361,7 @@ func TestAllocatorFuzz(t *testing.T) {
type result struct {
name string
alloc int32
block bool
}
stateLock := sync.Mutex{}
state := make(map[string]result)
@@ -316,28 +380,19 @@ func TestAllocatorFuzz(t *testing.T) {
return xs[:ls]
}
// Do a Allocate and check the address
// is unique. Needs a unique container
// name.
allocate := func(name string) {
bumpPending := func() bool {
stateLock.Lock()
if len(addrs)+numPending >= maxAddresses {
stateLock.Unlock()
return
return false
}
numPending++
stateLock.Unlock()
return true
}
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)
noteAllocation := func(allocIndex int32, name string, addr address.Address) {
//common.Log.Infof("Allocate: got address %s for name %s", addr, name)
addrStr := addr.String()
stateLock.Lock()
@@ -348,11 +403,31 @@ func TestAllocatorFuzz(t *testing.T) {
name, res.name))
}
state[addrStr] = result{name, allocIndex}
state[addrStr] = result{name, allocIndex, false}
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.Allocate(name, subnet, nil)
if err != nil {
panic(fmt.Sprintf("Could not allocate addr"))
}
noteAllocation(allocIndex, name, addr)
}
// Free a random address.
free := func() {
stateLock.Lock()
@@ -365,14 +440,22 @@ 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.Info.Printf("Freeing %s on allocator %d", addr, res.alloc)
//common.Log.Infof("Freeing %s (%s) on allocator %d", res.name, addr, res.alloc)
wt.AssertSuccess(t, alloc.Free(res.name))
oldAddr, err := address.ParseIP(addr)
if err != nil {
panic(err)
}
require.NoError(t, alloc.Free(res.name, oldAddr))
}
// Do a Allocate on an existing container & allocator
@@ -382,15 +465,41 @@ 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.Info.Printf("Asking for %s on allocator %d again", addr, res.alloc)
//common.Log.Infof("Asking for %s (%s) on allocator %d again", res.name, addr, res.alloc)
newAddr, _ := alloc.Allocate(res.name, nil)
newAddr, _ := alloc.Allocate(res.name, subnet, nil)
oldAddr, _ := address.ParseIP(addr)
if newAddr != oldAddr {
panic(fmt.Sprintf("Got different address for repeat request"))
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.Start, address.Offset(addressIndex))
err := alloc.Claim(name, addr, true)
if err == nil {
noteAllocation(allocIndex, name, addr)
}
}
@@ -433,21 +542,25 @@ func TestAllocatorFuzz(t *testing.T) {
// free a random addr
free()
case 0.8 <= r && r < 1.0:
case 0.8 <= r && r < 0.95:
// 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 TestGossipSkew(t *testing.T) {
alloc1 := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", "10.0.1.0/22", 2)
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, _ := 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()
if _, err := alloc1.OnGossipBroadcast(alloc2.encode()); err == nil {
if _, err := alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode()); err == nil {
t.Fail()
}
}
+67 -43
View File
@@ -3,75 +3,99 @@ package ipam
import (
"fmt"
"github.com/weaveworks/weave/ipam/address"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/router"
)
type claim struct {
resultChan chan<- error
hasBeenCancelled func() bool
ident string
addr address.Address
noErrorOnUnknown bool
}
// 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())
}
}
// 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)() {
c.Cancel()
return true
}
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
alloc.infof("Ignored address %s claimed by %s - not in our universe", c.addr, c.ident)
c.sendResult(nil)
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)
alloc.establishRing()
switch owner := alloc.ring.Owner(c.addr); owner {
case alloc.ourName:
// success
case router.UnknownPeerName:
// If our ring doesn't know, it must be empty.
if c.noErrorOnUnknown {
alloc.infof("Claim %s for %s: address allocator still initializing; will try later.", c.addr, c.ident)
c.sendResult(nil)
} else {
c.sendResult(fmt.Errorf("%s is in the range %s, but the allocator is not initialized yet", c.addr, alloc.universe.AsCIDRString()))
}
return false
default:
alloc.debugf("requesting address %s from other peer %s", c.addr, owner)
err := alloc.sendSpaceRequest(owner, address.NewRange(c.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.addr, c.ident, err)
c.sendResult(nil)
} else { // just tell the user they can't do this.
c.deniedBy(alloc, 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.addr)
if existingIdent == c.ident {
// same identifier is claiming same address; that's OK
c.resultChan <- nil
return true
}
if existingIdent == "" {
err := alloc.space.Claim(c.addr)
if err != nil {
c.resultChan <- err
return true
switch existingIdent := alloc.findOwner(c.addr); existingIdent {
case "":
if err := alloc.space.Claim(c.addr); err == nil {
alloc.debugln("Claimed", c.addr, "for", c.ident)
alloc.addOwned(c.ident, c.addr)
c.sendResult(nil)
} else {
c.sendResult(err)
}
alloc.addOwned(c.ident, c.addr)
c.resultChan <- nil
return true
case c.ident:
// same identifier is claiming same address; that's OK
c.sendResult(nil)
default:
// Addr already owned by container on this machine
c.sendResult(fmt.Errorf("address %s is already owned by %s", c.addr.String(), existingIdent))
}
// 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) Cancel() {
c.resultChan <- fmt.Errorf("Operation cancelled.")
func (c *claim) deniedBy(alloc *Allocator, owner router.PeerName) {
name, found := alloc.nicknames[owner]
if found {
name = " (" + name + ")"
}
c.sendResult(fmt.Errorf("address %s is owned by other peer %s%s", c.addr.String(), owner, name))
}
func (c *claim) String() string {
return fmt.Sprintf("Claim %s -> %s", c.ident, c.addr.String())
func (c *claim) Cancel() {
c.sendResult(&errorCancelled{"Claim", c.ident})
}
func (c *claim) ForContainer(ident string) bool {
-36
View File
@@ -1,36 +0,0 @@
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
}
+83 -11
View File
@@ -7,25 +7,61 @@ import (
"github.com/gorilla/mux"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/address"
"github.com/weaveworks/weave/common/docker"
"github.com/weaveworks/weave/net/address"
)
func badRequest(w http.ResponseWriter, err error) {
http.Error(w, err.Error(), http.StatusBadRequest)
common.Warning.Println("[allocator]:", err.Error())
common.Log.Warningln("[allocator]:", err.Error())
}
func parseCIDR(w http.ResponseWriter, cidrStr string) (address.CIDR, bool) {
subnetAddr, cidr, err := address.ParseCIDR(cidrStr)
if err != nil {
badRequest(w, err)
return address.CIDR{}, false
}
if cidr.Start != subnetAddr {
badRequest(w, fmt.Errorf("Invalid subnet %s - bits after network prefix are not all zero", cidrStr))
return address.CIDR{}, false
}
return cidr, true
}
func (alloc *Allocator) handleHTTPAllocate(dockerCli *docker.Client, w http.ResponseWriter, ident string, checkAlive bool, subnet address.CIDR) {
closedChan := w.(http.CloseNotifier).CloseNotify()
addr, err := alloc.Allocate(ident, subnet.HostRange(), closedChan)
if err != nil {
if _, ok := err.(*errorCancelled); ok { // cancellation is not really an error
common.Log.Infoln("[allocator]:", err.Error())
fmt.Fprint(w, "cancelled")
return
}
badRequest(w, err)
return
}
if checkAlive && dockerCli != nil && dockerCli.IsContainerNotRunning(ident) {
common.Log.Infof("[allocator] '%s' is not running: freeing %s", ident, addr)
alloc.Free(ident, addr)
fmt.Fprint(w, "cancelled")
return
}
fmt.Fprintf(w, "%s/%d", addr, subnet.PrefixLen)
}
// HandleHTTP wires up ipams HTTP endpoints to the provided mux.
func (alloc *Allocator) HandleHTTP(router *mux.Router) {
func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, dockerCli *docker.Client) {
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"]
noErrorOnUnknown := r.FormValue("noErrorOnUnknown") == "true"
if ip, err := address.ParseIP(ipStr); err != nil {
badRequest(w, err)
return
} else if err := alloc.Claim(ident, ip, closedChan); err != nil {
} else if err := alloc.Claim(ident, ip, noErrorOnUnknown); err != nil {
badRequest(w, fmt.Errorf("Unable to claim: %s", err))
return
}
@@ -33,21 +69,57 @@ func (alloc *Allocator) HandleHTTP(router *mux.Router) {
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)
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"]); ok {
addr, err := alloc.Lookup(vars["id"], subnet.HostRange())
if err != nil {
http.NotFound(w, r)
return
}
fmt.Fprintf(w, "%s/%d", addr, subnet.PrefixLen)
}
})
router.Methods("GET").Path("/ip/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
addr, err := alloc.Lookup(mux.Vars(r)["id"], defaultSubnet.HostRange())
if err != nil {
http.NotFound(w, r)
return
}
fmt.Fprintf(w, "%s/%d", addr, defaultSubnet.PrefixLen)
})
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"]); 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) {
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))
return
}
fmt.Fprintf(w, "%s/%d", newAddr.String(), alloc.prefixLen)
w.WriteHeader(204)
})
router.Methods("DELETE").Path("/ip/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ident := mux.Vars(r)["id"]
if err := alloc.Free(ident); err != nil {
if err := alloc.Delete(ident); err != nil {
badRequest(w, err)
return
}
+77 -57
View File
@@ -4,22 +4,31 @@ import (
"fmt"
"io"
"io/ioutil"
"math/rand"
"net"
"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)
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
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)
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
return string(body)
@@ -30,60 +39,76 @@ func doHTTP(method string, url string) (resp *http.Response, err error) {
return http.DefaultClient.Do(req)
}
func listenHTTP(port int, alloc *Allocator) {
func listenHTTP(alloc *Allocator, subnet address.CIDR) int {
router := mux.NewRouter()
router.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, fmt.Sprintln(alloc))
})
alloc.HandleHTTP(router)
alloc.HandleHTTP(router, subnet, nil)
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)
httpListener, err := net.Listen("tcp", ":0")
if err != nil {
common.Log.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 allocURL(port int, containerID string) string {
func identURL(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"
testAddr1 = "10.0.3.9"
netSuffix = "/29"
testCIDR1 = "10.0.3.8" + netSuffix
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"
)
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, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", universe, 1)
_, cidr, _ := address.ParseCIDR(universe)
port := listenHTTP(alloc, cidr)
alloc.claimRingForTesting()
// Ask the http server for a new address
cidr1 := HTTPPost(t, allocURL(port, containerID))
wt.AssertEqualString(t, cidr1, testAddr1+netSuffix, "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")
// 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 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")
// 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")
// 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")
// 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")
// Would like to shut down the http server at the end of this test
// but it's complicated.
@@ -96,28 +121,27 @@ 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()
port := rand.Intn(10000) + 32768
fmt.Println("BadHttp test on port", port)
go listenHTTP(port, alloc)
_, cidr, _ := address.ParseCIDR(testCIDR1)
port := listenHTTP(alloc, cidr)
alloc.claimRingForTesting()
cidr1 := HTTPPost(t, allocURL(port, containerID))
cidr1 := HTTPPost(t, allocURL(port, testCIDR1, 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))
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusNotFound, "http response")
require.NoError(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode, "http response")
// Mis-spelled URL
resp, err = doHTTP("POST", fmt.Sprintf("http://localhost:%d/xip/%s/", port, containerID))
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusNotFound, "http response")
require.NoError(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode, "http response")
// Malformed URL
resp, err = doHTTP("POST", fmt.Sprintf("http://localhost:%d/ip/%s/foo/bar/baz", port, containerID))
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusNotFound, "http response")
require.NoError(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode, "http response")
}
func TestHTTPCancel(t *testing.T) {
@@ -127,27 +151,23 @@ func TestHTTPCancel(t *testing.T) {
}
func impTestHTTPCancel(t *testing.T) {
common.InitDefaultLogging(true)
var (
containerID = "deadbeef"
testCIDR1 = "10.0.3.5/29"
testCIDR1 = "10.0.3.0/29"
)
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()
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
_, cidr, _ := address.ParseCIDR(testCIDR1)
port := listenHTTP(alloc, cidr)
// Stop the alloc so nothing actually works
unpause := alloc.pause()
// Ask the http server for a new address
done := make(chan *http.Response)
req, _ := http.NewRequest("POST", allocURL(port, containerID), nil)
req, _ := http.NewRequest("POST", allocURL(port, testCIDR1, containerID), nil)
go func() {
res, _ := http.DefaultClient.Do(req)
done <- res
@@ -159,6 +179,6 @@ func impTestHTTPCancel(t *testing.T) {
unpause()
res := <-done
if res != nil {
wt.Fatalf(t, "Error: Allocate returned non-nil")
require.FailNow(t, "Error: Allocate returned non-nil")
}
}
+10 -8
View File
@@ -1,7 +1,6 @@
package paxos
import (
"fmt"
"github.com/weaveworks/weave/router"
)
@@ -117,9 +116,8 @@ 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
@@ -252,10 +250,14 @@ func (node *Node) Consensus() (bool, AcceptedValue) {
return false, AcceptedValue{}
}
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)
type Status struct {
KnownNodes int
Quorum uint
}
func NewStatus(node *Node) *Status {
if node == nil {
return nil
}
return &Status{len(node.knows), node.quorum}
}
+2 -1
View File
@@ -2,10 +2,11 @@ package paxos
import (
"fmt"
"github.com/weaveworks/weave/router"
"math/rand"
"testing"
"time"
"github.com/weaveworks/weave/router"
)
type TestNode struct {
+1 -5
View File
@@ -4,7 +4,7 @@ import (
"sort"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/address"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/router"
)
@@ -68,10 +68,6 @@ 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
+59 -53
View File
@@ -10,9 +10,10 @@ import (
"io"
"math/rand"
"sort"
"time"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/address"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/router"
)
@@ -32,17 +33,14 @@ 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")
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!")
ErrTooMuchFreeSpace = errors.New("Entry reporting too much free space!")
ErrInvalidTimeout = errors.New("dt must be greater than 0")
ErrNotFound = errors.New("No entries for peer found")
ErrNotSorted = errors.New("Ring not sorted")
ErrTokenRepeated = errors.New("Token appears twice in ring")
ErrTokenOutOfRange = errors.New("Token is out of range")
ErrDifferentRange = errors.New("Received range differs from ours!")
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!")
ErrNotFound = errors.New("No entries for peer found")
)
func (r *Ring) checkInvariants() error {
@@ -76,7 +74,7 @@ func (r *Ring) checkInvariants() error {
distance := r.distance(entry.Token, next.Token)
if entry.Free > distance {
return ErrTooMuchFreeSpace
return fmt.Errorf("Entry %s-%s reporting too much free space: %d > %d", entry.Token, next.Token, entry.Free, distance)
}
}
@@ -92,16 +90,8 @@ func New(start, end address.Address, peer router.PeerName) *Ring {
return ring
}
// 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
func (r *Ring) Range() address.Range {
return address.Range{Start: r.Start, End: r.End}
}
// Returns the distance between two tokens on this ring, dealing
@@ -167,7 +157,7 @@ func (r *Ring) GrantRangeToHost(start, end address.Address, peer router.PeerName
pos := preceedingPos + 1
for ; pos < len(r.Entries) && r.Entries.entry(pos).Token < end; pos++ {
entry := r.Entries.entry(pos)
entry.update(peer, entry.Free)
entry.update(peer, address.Min(entry.Free, r.distance(entry.Token, end)))
}
// There is never an entry with a token of r.End, as the end of
@@ -199,7 +189,7 @@ func (r *Ring) Merge(gossip Ring) error {
}
if r.Start != gossip.Start || r.End != gossip.End {
return ErrDifferentSubnets
return ErrDifferentRange
}
// Now merge their ring with yours, in a temporary ring.
@@ -230,7 +220,7 @@ func (r *Ring) Merge(gossip Ring) error {
switch {
case mine.Version >= theirs.Version:
if mine.Version == theirs.Version && !mine.Equal(theirs) {
common.Debug.Printf("Error merging entries at %s - %v != %v\n", mine.Token, mine, theirs)
common.Log.Debugf("Error merging entries at %s - %v != %v", mine.Token, mine, theirs)
return ErrInvalidEntry
}
addToResult(*mine)
@@ -342,10 +332,6 @@ func (r *Ring) ClaimForPeers(peers []router.PeerName) {
common.Assert(pos == r.End)
}
func (r *Ring) ClaimItAll() {
r.ClaimForPeers([]router.PeerName{r.Peer})
}
func (r *Ring) FprintWithNicknames(w io.Writer, m map[router.PeerName]string) {
for _, entry := range r.Entries {
nickname, found := m[entry.Peer]
@@ -365,9 +351,9 @@ func (r *Ring) String() 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 ChoosePeerToAskForSpace
// can make more intelligent decisions.
// 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.
func (r *Ring) ReportFree(freespace map[address.Address]address.Offset) {
r.assertInvariants()
defer r.assertInvariants()
@@ -409,16 +395,34 @@ func (r *Ring) ReportFree(freespace map[address.Address]address.Offset) {
}
}
// ChoosePeerToAskForSpace chooses a weighted-random peer to ask
// for space.
func (r *Ring) ChoosePeerToAskForSpace() (result router.PeerName, err error) {
type weightedPeer struct {
weight float64
peername router.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) []router.PeerName {
var (
sum address.Offset
totalSpacePerPeer = make(map[router.PeerName]address.Offset) // Compute total free space per peer
)
// iterate through tokens
for _, entry := range r.Entries {
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
}
// Ignore ranges with no free space
if entry.Free <= 0 {
continue
@@ -433,21 +437,19 @@ func (r *Ring) ChoosePeerToAskForSpace() (result router.PeerName, err error) {
sum += entry.Free
}
if sum == 0 {
err = ErrNoFreeSpace
return
}
// Pick random peer, weighted by total free space
rn := rand.Int63n(int64(sum))
// 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))
for peername, space := range totalSpacePerPeer {
rn -= int64(space)
if rn < 0 {
return peername, nil
}
ws = append(ws, weightedPeer{weight: float64(space) * rand.Float64(), peername: peername})
}
panic("Should never reach this point")
sort.Sort(ws)
result := make([]router.PeerName, len(ws))
for i, wp := range ws {
result[i] = wp.peername
}
return result
}
func (r *Ring) PickPeerForTransfer() router.PeerName {
@@ -461,7 +463,7 @@ func (r *Ring) PickPeerForTransfer() router.PeerName {
// 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 router.PeerName) (error, []address.Range) {
func (r *Ring) Transfer(from, to router.PeerName) ([]address.Range, error) {
r.assertInvariants()
defer r.assertInvariants()
defer r.updateExportedVariables()
@@ -479,10 +481,10 @@ func (r *Ring) Transfer(from, to router.PeerName) (error, []address.Range) {
}
if !found {
return ErrNotFound, nil
return nil, ErrNotFound
}
return nil, r.splitRangesOverZero(newRanges)
return r.splitRangesOverZero(newRanges), nil
}
// Contains returns true if addr is in this ring
@@ -519,3 +521,7 @@ func (r *Ring) PeerNames() map[router.PeerName]struct{} {
return res
}
func init() {
rand.Seed(time.Now().UTC().UnixNano())
}
+154 -121
View File
@@ -7,10 +7,10 @@ import (
"sort"
"testing"
"github.com/stretchr/testify/require"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/address"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/router"
wt "github.com/weaveworks/weave/testing"
)
var (
@@ -34,26 +34,26 @@ func TestInvariants(t *testing.T) {
// Check ring is sorted
ring.Entries = []*entry{{Token: dot245, Peer: peer1name}, {Token: dot10, Peer: peer2name}}
wt.AssertTrue(t, ring.checkInvariants() == ErrNotSorted, "Expected error")
require.True(t, ring.checkInvariants() == ErrNotSorted, "Expected error")
// Check tokens don't appear twice
ring.Entries = []*entry{{Token: dot245, Peer: peer1name}, {Token: dot245, Peer: peer2name}}
wt.AssertTrue(t, ring.checkInvariants() == ErrTokenRepeated, "Expected error")
require.True(t, ring.checkInvariants() == ErrTokenRepeated, "Expected error")
// Check tokens are in bounds
ring = New(dot10, dot245, peer1name)
ring.Entries = []*entry{{Token: start, Peer: peer1name}}
wt.AssertTrue(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
require.True(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
ring.Entries = []*entry{{Token: end, Peer: peer1name}}
wt.AssertTrue(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
require.True(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
}
func TestInsert(t *testing.T) {
ring := New(start, end, peer1name)
ring.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
wt.AssertPanic(t, func() {
require.Panics(t, func() {
ring.Entries.insert(entry{Token: start, Peer: peer1name})
})
@@ -61,11 +61,11 @@ func TestInsert(t *testing.T) {
ring.Entries.insert(entry{Token: dot245, Peer: peer1name})
ring2 := New(start, end, peer1name)
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Free: 0}, {Token: dot245, Peer: peer1name}}
wt.AssertEquals(t, ring, ring2)
require.Equal(t, ring2, ring)
ring.Entries.insert(entry{Token: dot10, Peer: peer1name})
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Free: 0}, {Token: dot10, Peer: peer1name}, {Token: dot245, Peer: peer1name}}
wt.AssertEquals(t, ring, ring2)
require.Equal(t, ring2, ring)
}
func TestBetween(t *testing.T) {
@@ -76,7 +76,7 @@ func TestBetween(t *testing.T) {
// between should return true for everything
for i := 1; i <= 255; i++ {
ip := ParseIP(fmt.Sprintf("10.0.0.%d", i))
wt.AssertTrue(t, ring1.Entries.between(ip, 0, 1), "between should be true!")
require.True(t, ring1.Entries.between(ip, 0, 1), "between should be true!")
}
// Now, construct a ring with entries at +10 and -10
@@ -87,25 +87,25 @@ func TestBetween(t *testing.T) {
for i := 10; i <= 244; i++ {
ipStr := fmt.Sprintf("10.0.0.%d", i)
ip := ParseIP(ipStr)
wt.AssertTrue(t, ring1.Entries.between(ip, 0, 1),
require.True(t, ring1.Entries.between(ip, 0, 1),
fmt.Sprintf("Between should be true for %s!", ipStr))
wt.AssertFalse(t, ring1.Entries.between(ip, 1, 2),
require.False(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)
wt.AssertFalse(t, ring1.Entries.between(ip, 0, 1),
require.False(t, ring1.Entries.between(ip, 0, 1),
fmt.Sprintf("Between should be false for %s!", ipStr))
wt.AssertTrue(t, ring1.Entries.between(ip, 1, 2),
require.True(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)
wt.AssertFalse(t, ring1.Entries.between(ip, 0, 1),
require.False(t, ring1.Entries.between(ip, 0, 1),
fmt.Sprintf("Between should be false for %s!", ipStr))
wt.AssertTrue(t, ring1.Entries.between(ip, 1, 2),
require.True(t, ring1.Entries.between(ip, 1, 2),
fmt.Sprintf("Between should be true for %s!", ipStr))
}
}
@@ -116,32 +116,33 @@ func TestGrantSimple(t *testing.T) {
// Claim everything for peer1
ring1.ClaimItAll()
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 255}})
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 255}}, ring1.Entries)
// Now grant everything to peer2
ring1.GrantRangeToHost(start, end, peer2name)
ring2.Entries = []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}}
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
require.Equal(t, ring2.Entries, ring1.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: 245}}
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
require.Equal(t, ring2.Entries, ring1.Entries)
// And spint back to peer 2 again
ring1.GrantRangeToHost(dot245, end, peer2name)
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 2},
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 2},
{Token: dot10, Peer: peer1name, Free: 235, Version: 1},
{Token: dot245, Peer: peer2name, Free: 10}})
{Token: dot245, Peer: peer2name, Free: 10}}, ring1.Entries)
// 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)
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 10, Version: 2},
require.Equal(t, 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}})
{Token: dot245, Peer: peer2name, Free: 10, Version: 1}}, ring1.Entries)
}
func TestGrantSplit(t *testing.T) {
@@ -151,13 +152,26 @@ func TestGrantSplit(t *testing.T) {
// Claim everything for peer1
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
ring2.Merge(*ring1)
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
require.Equal(t, ring2.Entries, ring1.Entries)
// Now grant a split range to peer2
ring1.GrantRangeToHost(dot10, dot245, peer2name)
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 10, Version: 1},
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 10, Version: 1},
{Token: dot10, Peer: peer2name, Free: 235},
{Token: dot245, Peer: peer1name, Free: 10}})
{Token: dot245, Peer: peer1name, Free: 10}}, ring1.Entries)
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},
{Token: dot10, Peer: peer2name, Free: 118, Version: 1},
{Token: middle, Peer: peer2name, Free: 117, Version: 1},
{Token: dot245, Peer: peer1name, Free: 10, Version: 0}}, ring1.Entries)
ring1.assertInvariants()
}
func TestMergeSimple(t *testing.T) {
@@ -167,23 +181,25 @@ func TestMergeSimple(t *testing.T) {
// Claim everything for peer1
ring1.ClaimItAll()
ring1.GrantRangeToHost(middle, end, peer2name)
wt.AssertSuccess(t, ring2.Merge(*ring1))
require.NoError(t, ring2.Merge(*ring1))
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)
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 128, Version: 1},
{Token: middle, Peer: peer2name, Free: 127}}, ring1.Entries)
require.Equal(t, ring2.Entries, ring1.Entries)
// Now to two different operations on either side,
// check we can Merge again
ring1.GrantRangeToHost(start, middle, peer2name)
ring2.GrantRangeToHost(middle, end, peer1name)
wt.AssertSuccess(t, ring2.Merge(*ring1))
wt.AssertSuccess(t, ring1.Merge(*ring2))
require.NoError(t, ring2.Merge(*ring1))
require.NoError(t, ring1.Merge(*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)
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 128, Version: 2},
{Token: middle, Peer: peer1name, Free: 127, Version: 1}}, ring1.Entries)
require.Equal(t, ring2.Entries, ring1.Entries)
}
func TestMergeErrors(t *testing.T) {
@@ -191,28 +207,28 @@ func TestMergeErrors(t *testing.T) {
ring1 := New(start, end, peer1name)
ring2 := New(start, end, peer2name)
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}, {Token: start, Peer: peer2name}}
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrNotSorted, "Expected ErrNotSorted")
require.True(t, ring1.Merge(*ring2) == ErrNotSorted, "Expected ErrNotSorted")
// Should Merge two rings for different ranges
ring2 = New(start, middle, peer2name)
ring2.Entries = []*entry{}
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrDifferentSubnets, "Expected ErrDifferentSubnets")
require.True(t, ring1.Merge(*ring2) == ErrDifferentRange, "Expected ErrDifferentRange")
// 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")
require.True(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}}
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrInvalidEntry, "Expected ErrInvalidEntry")
require.True(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}}
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrEntryInMyRange, "Expected ErrEntryInMyRange")
require.True(t, ring1.Merge(*ring2) == ErrEntryInMyRange, "Expected ErrEntryInMyRange")
}
func TestMergeMore(t *testing.T) {
@@ -220,7 +236,7 @@ func TestMergeMore(t *testing.T) {
ring2 := New(start, end, peer2name)
assertRing := func(ring *Ring, entries entries) {
wt.AssertEquals(t, ring.Entries, entries)
require.Equal(t, entries, ring.Entries)
}
assertRing(ring1, []*entry{})
@@ -232,7 +248,7 @@ func TestMergeMore(t *testing.T) {
assertRing(ring2, []*entry{})
// Check the Merge sends it to the other ring
wt.AssertSuccess(t, ring2.Merge(*ring1))
require.NoError(t, ring2.Merge(*ring1))
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
@@ -241,7 +257,7 @@ func TestMergeMore(t *testing.T) {
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
wt.AssertSuccess(t, ring2.Merge(*ring1))
require.NoError(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}})
@@ -252,14 +268,14 @@ func TestMergeMore(t *testing.T) {
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
// And Merge back
wt.AssertSuccess(t, ring1.Merge(*ring2))
require.NoError(t, ring1.Merge(*ring2))
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
{Token: middle, Peer: peer1name, Free: 127}})
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
{Token: middle, Peer: peer1name, Free: 127}})
// This should be a no-op
wt.AssertSuccess(t, ring2.Merge(*ring1))
require.NoError(t, ring2.Merge(*ring1))
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
{Token: middle, Peer: peer1name, Free: 127}})
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
@@ -272,19 +288,21 @@ func TestMergeSplit(t *testing.T) {
// Claim everything for peer2
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 255}}
wt.AssertSuccess(t, ring2.Merge(*ring1))
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
require.NoError(t, ring2.Merge(*ring1))
require.Equal(t, ring2.Entries, ring1.Entries)
// Now grant a split range to peer1
ring2.GrantRangeToHost(dot10, dot245, peer1name)
wt.AssertEquals(t, ring2.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
{Token: dot10, Peer: peer1name, Free: 235},
{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: dot245, Peer: peer2name, Free: 10}}, ring2.Entries)
require.NoError(t, ring1.Merge(*ring2))
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
{Token: dot10, Peer: peer1name, Free: 235},
{Token: dot245, Peer: peer2name, Free: 10}})
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
{Token: dot245, Peer: peer2name, Free: 10}}, ring1.Entries)
require.Equal(t, ring2.Entries, ring1.Entries)
}
func TestMergeSplit2(t *testing.T) {
@@ -293,19 +311,21 @@ func TestMergeSplit2(t *testing.T) {
// Claim everything for peer2
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 250}, {Token: dot250, Peer: peer2name, Free: 5}}
wt.AssertSuccess(t, ring2.Merge(*ring1))
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
require.NoError(t, ring2.Merge(*ring1))
require.Equal(t, ring2.Entries, ring1.Entries)
// Now grant a split range to peer1
ring2.GrantRangeToHost(dot10, dot245, peer1name)
wt.AssertEquals(t, ring2.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
require.Equal(t, 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}})
wt.AssertSuccess(t, ring1.Merge(*ring2))
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}}, ring2.Entries)
require.NoError(t, ring1.Merge(*ring2))
require.Equal(t, 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}})
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}}, ring1.Entries)
require.Equal(t, ring2.Entries, ring1.Entries)
}
// A simple test, very similar to above, but using the marshalling to byte[]s
@@ -314,7 +334,7 @@ func TestGossip(t *testing.T) {
ring2 := New(start, end, peer2name)
assertRing := func(ring *Ring, entries entries) {
wt.AssertEquals(t, ring.Entries, entries)
require.Equal(t, entries, ring.Entries)
}
assertRing(ring1, []*entry{})
@@ -326,42 +346,51 @@ func TestGossip(t *testing.T) {
assertRing(ring2, []*entry{})
// Check the Merge sends it to the other ring
wt.AssertSuccess(t, ring2.Merge(*ring1))
require.NoError(t, ring2.Merge(*ring1))
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
}
func assertPeersWithSpace(t *testing.T, ring *Ring, start, end address.Address, expected int) []router.PeerName {
peers := ring.ChoosePeersToAskForSpace(start, end)
require.Equal(t, expected, len(peers))
return peers
}
func TestFindFree(t *testing.T) {
ring1 := New(start, end, peer1name)
_, err := ring1.ChoosePeerToAskForSpace()
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
assertPeersWithSpace(t, ring1, start, end, 0)
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
_, err = ring1.ChoosePeerToAskForSpace()
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
assertPeersWithSpace(t, ring1, start, end, 0)
// We shouldn't return outselves
ring1.ReportFree(map[address.Address]address.Offset{start: 10})
_, err = ring1.ChoosePeerToAskForSpace()
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
assertPeersWithSpace(t, ring1, start, end, 0)
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 1},
{Token: start, Peer: peer1name, Free: 1}}
_, err = ring1.ChoosePeerToAskForSpace()
wt.AssertTrue(t, err == ErrNoFreeSpace, "Expected ErrNoFreeSpace")
{Token: middle, Peer: peer1name, Free: 1}}
assertPeersWithSpace(t, ring1, start, end, 0)
ring1.assertInvariants()
// We should return others
var peers []router.PeerName
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1}}
peer, err := ring1.ChoosePeerToAskForSpace()
wt.AssertSuccess(t, err)
wt.AssertEquals(t, peer, peer2name)
peers = assertPeersWithSpace(t, ring1, start, end, 1)
require.Equal(t, peer2name, peers[0])
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1},
{Token: start, Peer: peer2name, Free: 1}}
peer, err = ring1.ChoosePeerToAskForSpace()
wt.AssertSuccess(t, err)
wt.AssertEquals(t, peer, peer2name)
{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()
}
func TestReportFree(t *testing.T) {
@@ -370,7 +399,7 @@ func TestReportFree(t *testing.T) {
ring1.ClaimItAll()
ring1.GrantRangeToHost(middle, end, peer2name)
wt.AssertSuccess(t, ring2.Merge(*ring1))
require.NoError(t, ring2.Merge(*ring1))
freespace := make(map[address.Address]address.Offset)
for _, r := range ring2.OwnedRanges() {
@@ -382,7 +411,7 @@ func TestReportFree(t *testing.T) {
func TestMisc(t *testing.T) {
ring := New(start, end, peer1name)
wt.AssertTrue(t, ring.Empty(), "empty")
require.True(t, ring.Empty(), "empty")
ring.ClaimItAll()
println(ring.String())
@@ -394,7 +423,7 @@ func TestEmptyGossip(t *testing.T) {
ring1.ClaimItAll()
// This used to panic, and it shouldn't
wt.AssertSuccess(t, ring1.Merge(*ring2))
require.NoError(t, ring1.Merge(*ring2))
}
func TestMergeOldMessage(t *testing.T) {
@@ -402,10 +431,10 @@ func TestMergeOldMessage(t *testing.T) {
ring2 := New(start, end, peer2name)
ring1.ClaimItAll()
wt.AssertSuccess(t, ring2.Merge(*ring1))
require.NoError(t, ring2.Merge(*ring1))
ring1.GrantRangeToHost(middle, end, peer1name)
wt.AssertSuccess(t, ring1.Merge(*ring2))
require.NoError(t, ring1.Merge(*ring2))
}
func TestSplitRangeAtBeginning(t *testing.T) {
@@ -413,33 +442,32 @@ func TestSplitRangeAtBeginning(t *testing.T) {
ring2 := New(start, end, peer2name)
ring1.ClaimItAll()
wt.AssertSuccess(t, ring2.Merge(*ring1))
require.NoError(t, ring2.Merge(*ring1))
ring1.GrantRangeToHost(start, middle, peer2name)
wt.AssertSuccess(t, ring2.Merge(*ring1))
require.NoError(t, ring2.Merge(*ring1))
}
func TestOwnedRange(t *testing.T) {
ring1 := New(start, end, peer1name)
ring1.ClaimItAll()
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{Start: start, End: end}})
require.Equal(t, []address.Range{{Start: start, End: end}}, ring1.OwnedRanges())
ring1.GrantRangeToHost(middle, end, peer2name)
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{Start: start, End: middle}})
require.Equal(t, []address.Range{{Start: start, End: middle}}, ring1.OwnedRanges())
ring2 := New(start, end, peer2name)
ring2.Merge(*ring1)
wt.AssertEquals(t, ring2.OwnedRanges(), []address.Range{{Start: middle, End: end}})
require.Equal(t, []address.Range{{Start: middle, End: end}}, ring2.OwnedRanges())
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}}
wt.AssertEquals(t, ring2.OwnedRanges(),
[]address.Range{{Start: start, End: middle}, {Start: middle, End: end}})
require.Equal(t, []address.Range{{Start: start, End: middle}, {Start: middle, End: end}}, ring2.OwnedRanges())
ring2.Entries = []*entry{{Token: dot10, Peer: peer2name}, {Token: middle, Peer: peer2name}}
wt.AssertEquals(t, ring2.OwnedRanges(),
[]address.Range{{Start: start, End: dot10}, {Start: dot10, End: middle},
{Start: middle, End: end}})
require.Equal(t, []address.Range{{Start: start, End: dot10}, {Start: dot10, End: middle},
{Start: middle, End: end}}, ring2.OwnedRanges())
}
func TestTransfer(t *testing.T) {
@@ -448,7 +476,7 @@ func TestTransfer(t *testing.T) {
ring1.ClaimItAll()
ring1.GrantRangeToHost(middle, end, peer2name)
ring1.Transfer(peer2name, peer1name)
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{start, middle}, {middle, end}})
require.Equal(t, []address.Range{{start, middle}, {middle, end}}, ring1.OwnedRanges())
// Second test is what happens when a token exists at the end of a range but is transferred
// - does it get resurrected correctly?
@@ -457,23 +485,24 @@ func TestTransfer(t *testing.T) {
ring1.GrantRangeToHost(middle, end, peer2name)
ring1.Transfer(peer2name, peer1name)
ring1.GrantRangeToHost(dot10, middle, peer2name)
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{start, dot10}, {middle, end}})
require.Equal(t, []address.Range{{start, dot10}, {middle, end}}, ring1.OwnedRanges())
}
func TestOwner(t *testing.T) {
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.True(t, ring1.Contains(start), "start should be in ring")
require.False(t, ring1.Contains(end), "end should not be in ring")
wt.AssertEquals(t, ring1.Owner(start), router.UnknownPeerName)
require.Equal(t, router.UnknownPeerName, ring1.Owner(start))
ring1.ClaimItAll()
ring1.GrantRangeToHost(middle, end, peer2name)
wt.AssertEquals(t, ring1.Owner(start), peer1name)
wt.AssertEquals(t, ring1.Owner(middle), peer2name)
wt.AssertPanic(t, func() {
require.Equal(t, peer1name, ring1.Owner(start))
require.Equal(t, peer2name, ring1.Owner(middle))
require.Panics(t, func() {
ring1.Owner(end)
})
}
type addressSlice []address.Address
@@ -568,7 +597,7 @@ func TestFuzzRing(t *testing.T) {
}
func TestFuzzRingHard(t *testing.T) {
//common.InitDefaultLogging(true)
//common.SetLogLevel("debug")
var (
numPeers = 100
iterations = 3000
@@ -579,7 +608,7 @@ func TestFuzzRingHard(t *testing.T) {
addPeer := func() {
peer, _ := router.PeerNameFromString(fmt.Sprintf("%02d:%02d:00:00:00:00", nextPeerID/10, nextPeerID%10))
common.Debug.Printf("%s: Adding peer", peer)
common.Log.Debugf("%s: Adding peer", peer)
nextPeerID++
peers = append(peers, peer)
rings = append(rings, New(start, end, peer))
@@ -615,27 +644,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, otherRing.Merge(*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 {
wt.AssertSuccess(t, ring.Merge(*otherRing))
require.NoError(t, ring.Merge(*otherRing))
theRanges[i] = ring.OwnedRanges()
}
}
@@ -658,13 +687,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.Debug.Printf("%s: Granting [%v, %v) to %s", ring.Peer, ipInRange, rangeToSplit.End, peerToGiveTo)
common.Log.Debugf("%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.Debug.Printf("%s: 'Gossiping' to %s", ring.Peer, otherRing.Peer)
wt.AssertSuccess(t, otherRing.Merge(*ring))
common.Log.Debugf("%s: 'Gossiping' to %s", ring.Peer, otherRing.Peer)
require.NoError(t, otherRing.Merge(*ring))
theRanges[indexWithRanges] = ring.OwnedRanges()
theRanges[otherIndex] = otherRing.OwnedRanges()
@@ -682,8 +711,8 @@ func TestFuzzRingHard(t *testing.T) {
}
ring1 := ringsWithEntries[rand.Intn(len(ringsWithEntries))]
ring2index, _, ring2 := randomPeer(-1)
common.Debug.Printf("%s: 'Gossiping' to %s", ring1.Peer, ring2.Peer)
wt.AssertSuccess(t, ring2.Merge(*ring1))
common.Log.Debugf("%s: 'Gossiping' to %s", ring1.Peer, ring2.Peer)
require.NoError(t, ring2.Merge(*ring1))
theRanges[ring2index] = ring2.OwnedRanges()
}
@@ -699,6 +728,10 @@ func TestFuzzRingHard(t *testing.T) {
}
}
func (r *Ring) ClaimItAll() {
r.ClaimForPeers([]router.PeerName{r.Peer})
}
func (es entries) String() string {
var buffer bytes.Buffer
fmt.Fprintf(&buffer, "[")
+74 -64
View File
@@ -6,11 +6,9 @@ import (
"sort"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam/address"
"github.com/weaveworks/weave/net/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
@@ -37,15 +35,47 @@ func (s *Space) Clear() {
s.ours = s.ours[:0]
}
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
// 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
}
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 {
@@ -58,29 +88,12 @@ func (s *Space) Claim(addr address.Address) error {
return nil
}
func (s *Space) NumFreeAddresses() address.Offset {
func (s *Space) NumFreeAddressesInRange(r address.Range) address.Offset {
res := address.Offset(0)
for i := 0; i < len(s.free); i += 2 {
res += address.Subtract(s.free[i+1], s.free[i])
}
return res
}
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)
}
s.walkFree(r, func(chunk address.Range) bool {
res += chunk.Size()
return false
})
return res
}
@@ -97,35 +110,32 @@ func (s *Space) Free(addr address.Address) error {
return nil
}
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
func (s *Space) biggestFreeRange(r address.Range) (biggest address.Range) {
biggestSize := address.Offset(0)
s.walkFree(r, func(chunk address.Range) bool {
if size := chunk.Size(); size >= biggestSize {
biggest = chunk
biggestSize = size
}
}
return pos, biggest
return false
})
return
}
func (s *Space) Donate() (address.Address, address.Offset, bool) {
if len(s.free) == 0 {
return 0, 0, false
func (s *Space) Donate(r address.Range) (address.Range, bool) {
biggest := s.biggestFreeRange(r)
if biggest.Size() == 0 {
return address.Range{}, false
}
pos, biggest := s.biggestFreeRange()
// Donate half of that biggest free range. 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, biggest.Size()/2)
// 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
s.ours = subtract(s.ours, biggest.Start, biggest.End)
s.free = subtract(s.free, biggest.Start, biggest.End)
return biggest, true
}
func firstGreater(a []address.Address, x address.Address) int {
@@ -152,25 +162,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 {
start_pos := firstGreaterOrEq(addrs, start)
end_pos := firstGreater(addrs[start_pos:], end) + start_pos
startPos := firstGreaterOrEq(addrs, start)
endPos := firstGreater(addrs[startPos:], end) + startPos
// Boundaries up to start_pos are unaffected
res := make([]address.Address, start_pos, len(addrs)+2)
// Boundaries up to startPos are unaffected
res := make([]address.Address, startPos, len(addrs)+2)
copy(res, addrs)
// Include start and end as new boundaries if they lie
// outside/inside existing ranges (according to sense).
if start_pos&1 == sense {
if startPos&1 == sense {
res = append(res, start)
}
if end_pos&1 == sense {
if endPos&1 == sense {
res = append(res, end)
}
// Boundaries after end_pos are unaffected
return append(res, addrs[end_pos:]...)
// Boundaries after endPos are unaffected
return append(res, addrs[endPos:]...)
}
func (s *Space) String() string {
+118 -104
View File
@@ -1,10 +1,10 @@
package space
import (
"reflect"
"testing"
"github.com/weaveworks/weave/ipam/address"
"github.com/stretchr/testify/require"
"github.com/weaveworks/weave/net/address"
wt "github.com/weaveworks/weave/testing"
)
@@ -19,100 +19,115 @@ func ip(s string) address.Address {
return addr
}
// Helper function to avoid 'NumFreeAddressesInRange(start, end)'
// dozens of times in tests
func (s *Space) NumFreeAddresses() address.Offset {
res := address.Offset(0)
for i := 0; i < len(s.free); i += 2 {
res += address.Subtract(s.free[i+1], s.free[i])
}
return res
}
func TestLowlevel(t *testing.T) {
a := []address.Address{}
a = add(a, 100, 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), "")
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), "")
a = add(a, 700, 800)
wt.AssertEquals(t, a, []address.Address{100, 200, 700, 800})
require.Equal(t, []address.Address{100, 200, 700, 800}, a)
a = add(a, 300, 400)
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 400, 700, 800})
require.Equal(t, []address.Address{100, 200, 300, 400, 700, 800}, a)
a = add(a, 400, 500)
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 700, 800})
require.Equal(t, []address.Address{100, 200, 300, 500, 700, 800}, a)
a = add(a, 600, 700)
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 600, 800})
require.Equal(t, []address.Address{100, 200, 300, 500, 600, 800}, a)
a = add(a, 500, 600)
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 800})
require.Equal(t, []address.Address{100, 200, 300, 800}, a)
a = subtract(a, 500, 600)
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 600, 800})
require.Equal(t, []address.Address{100, 200, 300, 500, 600, 800}, a)
a = subtract(a, 600, 700)
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 700, 800})
require.Equal(t, []address.Address{100, 200, 300, 500, 700, 800}, a)
a = subtract(a, 400, 500)
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 400, 700, 800})
require.Equal(t, []address.Address{100, 200, 300, 400, 700, 800}, a)
a = subtract(a, 300, 400)
wt.AssertEquals(t, a, []address.Address{100, 200, 700, 800})
require.Equal(t, []address.Address{100, 200, 700, 800}, a)
a = subtract(a, 700, 800)
wt.AssertEquals(t, a, []address.Address{100, 200})
require.Equal(t, []address.Address{100, 200}, a)
a = subtract(a, 100, 200)
wt.AssertEquals(t, a, []address.Address{})
require.Equal(t, []address.Address{}, a)
s := New()
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(0))
ok, got := s.Allocate()
wt.AssertFalse(t, ok, "allocate in empty space should fail")
require.Equal(t, address.Offset(0), s.NumFreeAddresses())
ok, got := s.Allocate(address.NewRange(0, 1000))
require.False(t, ok, "allocate in empty space should fail")
s.Add(100, 100)
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")
require.Equal(t, address.Offset(100), s.NumFreeAddresses())
ok, got = s.Allocate(address.NewRange(0, 1000))
require.True(t, ok && got == 100, "allocate")
require.Equal(t, address.Offset(99), s.NumFreeAddresses())
require.NoError(t, s.Claim(150))
require.Equal(t, address.Offset(98), s.NumFreeAddresses())
require.NoError(t, s.Free(100))
require.Equal(t, address.Offset(99), s.NumFreeAddresses())
wt.AssertErrorInterface(t, (*error)(nil), s.Free(0), "free not allocated")
wt.AssertErrorInterface(t, (*error)(nil), s.Free(100), "double free")
start, size, ok := s.Donate()
wt.AssertTrue(t, ok && start == 125 && size == 25, "donate")
r, ok := s.Donate(address.NewRange(0, 1000))
require.True(t, ok && r.Start == 125 && r.Size() == 25, "donate")
// test Donate when addresses are scarce
s = New()
start, size, ok = s.Donate()
wt.AssertTrue(t, !ok, "donate on empty space should fail")
r, ok = s.Donate(address.NewRange(0, 1000))
require.True(t, !ok, "donate on empty space should fail")
s.Add(0, 3)
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")
require.NoError(t, s.Claim(0))
require.NoError(t, s.Claim(2))
r, ok = s.Donate(address.NewRange(0, 1000))
require.True(t, ok && r.Start == 1 && r.End == 2, "donate")
r, ok = s.Donate(address.NewRange(0, 1000))
require.True(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(ip(testAddr1), 20)
wt.AssertEquals(t, space1.NumFreeAddresses(), address.Offset(20))
space1 := makeSpace(start, size)
require.Equal(t, address.Offset(20), space1.NumFreeAddresses())
space1.assertInvariants()
_, addr1 := space1.Allocate()
wt.AssertEqualString(t, addr1.String(), testAddr1, "address")
wt.AssertEquals(t, space1.NumFreeAddresses(), address.Offset(19))
_, addr1 := space1.Allocate(address.NewRange(start, size))
require.Equal(t, testAddr1, addr1.String(), "address")
require.Equal(t, address.Offset(19), space1.NumFreeAddresses())
space1.assertInvariants()
_, 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))
_, addr2 := space1.Allocate(address.NewRange(start, size))
require.False(t, addr2.String() == testAddr1, "address")
require.Equal(t, address.Offset(18), space1.NumFreeAddresses())
require.Equal(t, address.Offset(13), space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddrx)}))
require.Equal(t, address.Offset(18), space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddry)}))
space1.assertInvariants()
space1.Free(addr2)
space1.assertInvariants()
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")
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")
space1.assertInvariants()
}
@@ -125,91 +140,90 @@ func TestSpaceFree(t *testing.T) {
containerID = "deadbeef"
)
entireRange := address.NewRange(ip(testAddr1), 20)
space := makeSpace(ip(testAddr1), 20)
// Check we are prepared to give up the entire space
pos, size := space.biggestFreeRange()
wt.AssertTrue(t, space.free[pos] == ip(testAddr1) && size == 20, "Wrong space")
r := space.biggestFreeRange(entireRange)
require.True(t, r.Start == ip(testAddr1) && r.Size() == 20, "Wrong space")
for i := 0; i < 20; i++ {
ok, _ := space.Allocate()
wt.AssertTrue(t, ok, "Failed to get address")
ok, _ := space.Allocate(entireRange)
require.True(t, ok, "Failed to get address")
}
// Check we are full
ok, _ := space.Allocate()
wt.AssertTrue(t, !ok, "Should have failed to get address")
_, size, ok = space.Donate()
wt.AssertTrue(t, size == 0, "Wrong space")
ok, _ := space.Allocate(entireRange)
require.True(t, !ok, "Should have failed to get address")
r, ok = space.Donate(entireRange)
require.True(t, r.Size() == 0, "Wrong space")
// Free in the middle
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")
require.NoError(t, space.Free(ip("10.0.3.13")))
r = space.biggestFreeRange(entireRange)
require.True(t, r.Start == ip("10.0.3.13") && r.Size() == 1, "Wrong space")
// Free one at the end
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")
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")
// Now free a few at the end
wt.AssertSuccess(t, space.Free(ip("10.0.3.22")))
wt.AssertSuccess(t, space.Free(ip("10.0.3.21")))
require.NoError(t, space.Free(ip("10.0.3.22")))
require.NoError(t, space.Free(ip("10.0.3.21")))
wt.AssertEquals(t, space.NumFreeAddresses(), address.Offset(4))
require.Equal(t, address.Offset(4), space.NumFreeAddresses())
// 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")
r = space.biggestFreeRange(entireRange)
require.True(t, r.Start == ip("10.0.3.21") && r.Size() == 3, "Wrong space")
// Now free a few in the middle
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.NoError(t, space.Free(ip("10.0.3.12")))
require.NoError(t, space.Free(ip("10.0.3.11")))
require.NoError(t, space.Free(ip("10.0.3.10")))
wt.AssertEquals(t, space.NumFreeAddresses(), address.Offset(7))
require.Equal(t, address.Offset(7), space.NumFreeAddresses())
// 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")
r = space.biggestFreeRange(entireRange)
require.True(t, r.Start == ip("10.0.3.10") && r.Size() == 4, "Wrong space")
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)
require.Equal(t, []address.Range{{Start: ip("10.0.3.4"), End: ip("10.0.3.24")}}, space.OwnedRanges())
}
func TestDonateSimple(t *testing.T) {
const (
testAddr1 = "10.0.1.0"
testAddr2 = "10.0.1.32"
size = 48
)
var (
ipAddr1 = ip(testAddr1)
)
ps1 := makeSpace(ipAddr1, 48)
ps1 := makeSpace(ipAddr1, size)
// Empty space set should split in two and give me the second half
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))
r, ok := ps1.Donate(address.NewRange(ip(testAddr1), size))
numGivenUp := r.Size()
require.True(t, ok, "Donate result")
require.Equal(t, "10.0.1.24", r.Start.String(), "Invalid start")
require.Equal(t, address.Offset(size/2), numGivenUp)
require.Equal(t, address.Offset(size/2), ps1.NumFreeAddresses())
// Now check we can give the rest up.
count := 0 // count to avoid infinite loop
for ; count < 1000; count++ {
_, size, ok := ps1.Donate()
r, ok := ps1.Donate(address.NewRange(ip(testAddr1), size))
if !ok {
break
}
numGivenUp += size
numGivenUp += r.Size()
}
wt.AssertEquals(t, ps1.NumFreeAddresses(), address.Offset(0))
wt.AssertEquals(t, numGivenUp, address.Offset(48))
require.Equal(t, address.Offset(0), ps1.NumFreeAddresses())
require.Equal(t, address.Offset(size), numGivenUp)
}
func TestDonateHard(t *testing.T) {
@@ -222,28 +236,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()
wt.AssertTrue(t, ok, "Failed to get IP!")
ok, _ := spaceset.Allocate(address.NewRange(start, size))
require.True(t, ok, "Failed to get IP!")
}
wt.AssertEquals(t, spaceset.NumFreeAddresses(), address.Offset(0))
require.Equal(t, address.Offset(0), spaceset.NumFreeAddresses())
// 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++ {
wt.AssertSuccess(t, spaceset.Free(address.Add(start, i)))
require.NoError(t, spaceset.Free(address.Add(start, i)))
}
// Now split
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))
newRange, ok := spaceset.Donate(address.NewRange(start, size))
require.True(t, ok, "GiveUpSpace result")
require.Equal(t, ip("10.0.1.23"), newRange.Start)
require.Equal(t, address.Offset(24), newRange.Size())
require.Equal(t, address.Offset(23), spaceset.NumFreeAddresses())
//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)
require.Equal(t, expected, spaceset)
}
+82
View File
@@ -0,0 +1,82 @@
package ipam
import (
"github.com/weaveworks/weave/ipam/paxos"
"github.com/weaveworks/weave/net/address"
)
type Status struct {
Paxos *paxos.Status
Range string
DefaultSubnet string
Entries []EntryStatus
PendingClaims []ClaimStatus
PendingAllocates []string
}
type EntryStatus struct {
Token string
Peer string
Version uint32
}
type ClaimStatus struct {
Ident string
Address address.Address
}
func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status {
if allocator == nil {
return nil
}
var paxosStatus *paxos.Status
if allocator.paxosTicker != nil {
paxosStatus = paxos.NewStatus(allocator.paxos)
}
resultChan := make(chan *Status)
allocator.actionChan <- func() {
resultChan <- &Status{
paxosStatus,
allocator.universe.String(),
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 _, entry := range allocator.ring.Entries {
slice = append(slice, EntryStatus{entry.Token.String(), entry.Peer.String(), entry.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.addr})
}
return slice
}
func newAllocateIdentSlice(allocator *Allocator) []string {
var slice []string
for _, op := range allocator.pendingAllocates {
allocate := op.(*allocate)
slice = append(slice, allocate.ident)
}
return slice
}
+57 -123
View File
@@ -3,12 +3,14 @@ package ipam
import (
"fmt"
"math/rand"
"sync"
"testing"
"time"
"github.com/weaveworks/weave/common"
"github.com/stretchr/testify/require"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/router"
wt "github.com/weaveworks/weave/testing"
"github.com/weaveworks/weave/testing/gossip"
)
type mockMessage struct {
@@ -30,12 +32,15 @@ func toStringArray(messages []mockMessage) []string {
}
type mockGossipComms struct {
t *testing.T
sync.RWMutex
*testing.T
name string
messages []mockMessage
}
func (m *mockGossipComms) String() string {
m.RLock()
defer m.RUnlock()
return fmt.Sprintf("[mockGossipComms %s]", m.name)
}
@@ -44,13 +49,15 @@ func (m *mockGossipComms) String() string {
// requires they are not based off iterating through a map.
func (m *mockGossipComms) GossipBroadcast(update router.GossipData) error {
m.Lock()
defer m.Unlock()
buf := []byte{}
if len(m.messages) == 0 {
m.Fatalf("%s: Gossip broadcast message unexpected: \n%x", m.name, buf)
require.FailNow(m, fmt.Sprintf("%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)
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message to %s but got broadcast", m.name, msg.dst))
} else if msg.buf != nil && !equalByteBuffer(msg.buf, buf) {
m.Fatalf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf)
require.FailNow(m, fmt.Sprintf("%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:]
@@ -70,22 +77,19 @@ func equalByteBuffer(a, b []byte) bool {
return true
}
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 {
m.Lock()
defer m.Unlock()
if len(m.messages) == 0 {
m.Fatalf("%s: Gossip message to %s unexpected: \n%s", m.name, dstPeerName, buf)
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 == router.UnknownPeerName {
m.Fatalf("%s: Expected Gossip broadcast message but got dest %s", m.name, dstPeerName)
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip broadcast message but got dest %s", m.name, dstPeerName))
} else if msg.dst != dstPeerName {
m.Fatalf("%s: Expected Gossip message to %s but got dest %s", m.name, msg.dst, dstPeerName)
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message to %s but got dest %s", m.name, msg.dst, dstPeerName))
} else if buf[0] != msg.msgType {
m.Fatalf("%s: Expected Gossip message of type %d but got type %d", m.name, msg.msgType, buf[0])
require.FailNow(m, fmt.Sprintf("%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:]) {
m.Fatalf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, 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:]))
} else {
// Swallow this message
m.messages = m.messages[1:]
@@ -96,44 +100,52 @@ func (m *mockGossipComms) GossipUnicast(dstPeerName router.PeerName, buf []byte)
func ExpectMessage(alloc *Allocator, dst string, msgType byte, buf []byte) {
m := alloc.gossip.(*mockGossipComms)
dstPeerName, _ := router.PeerNameFromString(dst)
m.Lock()
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{router.UnknownPeerName, 0, buf})
m.Unlock()
}
func CheckAllExpectedMessagesSent(allocs ...*Allocator) {
for _, alloc := range allocs {
m := alloc.gossip.(*mockGossipComms)
m.RLock()
if len(m.messages) > 0 {
wt.Fatalf(m.t, "%s: Gossip message(s) not sent as expected: \n%x", m.name, m.messages)
require.FailNow(m, fmt.Sprintf("%s: Gossip message(s) not sent as expected: \n%x", m.name, m.messages))
}
m.RUnlock()
}
}
func makeAllocator(name string, cidr string, quorum uint) *Allocator {
func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, address.Range) {
peername, err := router.PeerNameFromString(name)
if err != nil {
panic(err)
}
alloc, err := NewAllocator(peername, router.PeerUID(rand.Int63()),
"nick-"+name, cidr, quorum)
_, cidr, err := address.ParseCIDR(cidrStr)
if err != nil {
panic(err)
}
return alloc
alloc := NewAllocator(peername, router.PeerUID(rand.Int63()),
"nick-"+name, cidr.Range(), quorum)
return alloc, cidr.HostRange()
}
func makeAllocatorWithMockGossip(t *testing.T, name string, universeCIDR string, quorum uint) *Allocator {
alloc := makeAllocator(name, universeCIDR, quorum)
gossip := &mockGossipComms{t: t, name: name}
func makeAllocatorWithMockGossip(t *testing.T, name string, universeCIDR string, quorum uint) (*Allocator, address.Range) {
alloc, subnet := makeAllocator(name, universeCIDR, quorum)
gossip := &mockGossipComms{T: t, name: name}
alloc.SetInterfaces(gossip)
alloc.Start()
return alloc
return alloc, subnet
}
func (alloc *Allocator) claimRingForTesting(allocs ...*Allocator) {
@@ -145,6 +157,14 @@ func (alloc *Allocator) claimRingForTesting(allocs ...*Allocator) {
alloc.space.AddRanges(alloc.ring.OwnedRanges())
}
func (alloc *Allocator) NumFreeAddresses(r address.Range) address.Offset {
resultChan := make(chan address.Offset)
alloc.actionChan <- func() {
resultChan <- alloc.space.NumFreeAddressesInRange(r)
}
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)
@@ -152,14 +172,14 @@ func AssertSent(t *testing.T, ch <-chan bool) {
case <-ch:
// This case is ok
case <-timeout:
wt.Fatalf(t, "Nothing sent on channel")
require.FailNow(t, "Nothing sent on channel")
}
}
func AssertNothingSent(t *testing.T, ch <-chan bool) {
select {
case val := <-ch:
wt.Fatalf(t, "Unexpected value on channel: %t", val)
require.FailNow(t, fmt.Sprintf("Unexpected value on channel: %v", val))
default:
// no message received
}
@@ -168,115 +188,29 @@ func AssertNothingSent(t *testing.T, ch <-chan bool) {
func AssertNothingSentErr(t *testing.T, ch <-chan error) {
select {
case val := <-ch:
wt.Fatalf(t, "Unexpected value on channel: %t", val)
require.FailNow(t, fmt.Sprintf("Unexpected value on channel: %v", val))
default:
// no message received
}
}
// 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}
func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, *gossip.TestRouter, address.Range) {
gossipRouter := gossip.NewTestRouter(0.0)
allocs := make([]*Allocator, size)
var subnet address.Range
for i := 0; i < size; i++ {
alloc := makeAllocator(fmt.Sprintf("%02d:00:00:02:00:00", i),
var alloc *Allocator
alloc, subnet = makeAllocator(fmt.Sprintf("%02d:00:00:02:00:00", i),
cidr, uint(size/2+1))
alloc.SetInterfaces(gossipRouter.connect(alloc.ourName, alloc))
alloc.SetInterfaces(gossipRouter.Connect(alloc.ourName, alloc))
alloc.Start()
allocs[i] = alloc
}
gossipRouter.GossipBroadcast(allocs[size-1].Gossip())
time.Sleep(1000 * time.Millisecond)
return allocs, gossipRouter
allocs[size-1].gossip.GossipBroadcast(allocs[size-1].Gossip())
gossipRouter.Flush()
return allocs, gossipRouter, subnet
}
func stopNetworkOfAllocators(allocs []*Allocator) {
-125
View File
@@ -1,125 +0,0 @@
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
}
-31
View File
@@ -1,31 +0,0 @@
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")
}
-391
View File
@@ -1,391 +0,0 @@
package nameserver
import (
"bytes"
"container/heap"
"errors"
"fmt"
"github.com/benbjohnson/clock"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"math"
"math/rand"
"sync"
"time"
)
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
}
e.Status = stResolved
e.Flags = flags
e.putTime = now
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))
}
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()
}
-216
View File
@@ -1,216 +0,0 @@
package nameserver
import (
"fmt"
"github.com/benbjohnson/clock"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
wt "github.com/weaveworks/weave/testing"
"net"
"testing"
"time"
)
// 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, nullTTL, CacheNoLocalReplies)
resp, err = l.Get(questionMsg3, minUDPSize)
wt.AssertNil(t, resp, "Get() response with CacheNoLocalReplies")
wt.AssertNotNil(t, err, "Get() error with CacheNoLocalReplies")
clk.Add(time.Second * time.Duration(negLocalTTL+1))
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")
}
+272 -60
View File
@@ -1,86 +1,298 @@
package nameserver
import (
"bytes"
"fmt"
"math/rand"
"net"
"sort"
"strings"
"time"
"github.com/miekg/dns"
"github.com/weaveworks/weave/net/address"
)
const (
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
topDomain = "."
reverseDNSdomain = "in-addr.arpa."
etcResolvConf = "/etc/resolv.conf"
udpBuffSize = uint16(4096)
minUDPSize = 512
DefaultListenAddress = "0.0.0.0:53"
DefaultTTL = 1
DefaultClientTimeout = 5 * time.Second
)
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}
type DNSServer struct {
ns *Nameserver
domain string
ttl uint32
address string
servers []*dns.Server
upstream *dns.ClientConfig
tcpClient *dns.Client
udpClient *dns.Client
}
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 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
}
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
func NewDNSServer(ns *Nameserver, domain, address, effectiveAddress string, ttl uint32, clientTimeout time.Duration) (*DNSServer, error) {
s := &DNSServer{
ns: ns,
domain: dns.Fqdn(domain),
ttl: ttl,
address: address,
tcpClient: &dns.Client{Net: "tcp", ReadTimeout: clientTimeout},
udpClient: &dns.Client{Net: "udp", ReadTimeout: clientTimeout, UDPSize: udpBuffSize},
}
var err error
if s.upstream, err = dns.ClientConfigFromFile(etcResolvConf); err != nil {
return nil, err
}
if s.upstream != nil {
s.upstream.Servers = filter(s.upstream.Servers, effectiveAddress)
}
err = s.listen(address)
return s, err
}
func makeAddressReply(r *dns.Msg, q *dns.Question, addrs []ZoneRecord) *dns.Msg {
answers := make([]dns.RR, len(addrs))
header := makeHeader(r, q)
count := 0
for _, addr := range addrs {
ip := addr.IP()
ip4 := ip.To4()
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()
}
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++
}
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 makeReply(r, answers[:count])
return nil
}
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()}
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,
}
return makeReply(r, answers)
}
func makeDNSFailResponse(r *dns.Msg) *dns.Msg {
m := new(dns.Msg)
m.SetReply(r)
m.RecursionAvailable = true
m.Rcode = dns.RcodeNameError
m.HandleFunc(d.domain, h.handleLocal)
m.HandleFunc(reverseDNSdomain, h.handleReverse)
m.HandleFunc(topDomain, h.handleRecursive)
return m
}
// 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())
func (h *handler) handleLocal(w dns.ResponseWriter, req *dns.Msg) {
h.ns.debugf("local request: %+v", *req)
if len(req.Question) != 1 || req.Question[0].Qtype != dns.TypeA {
h.nameError(w, req)
return
}
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
}
header := dns.RR_Header{
Name: req.Question[0].Name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: h.ttl,
}
answers := make([]dns.RR, len(addrs))
for i, addr := range addrs {
ip := addr.IP4()
answers[i] = &dns.A{Hdr: header, A: ip}
}
shuffleAnswers(&answers)
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(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 && req.Question[0].Qtype == dns.TypeA {
hostname := dns.Fqdn(req.Question[0].Name)
if strings.Count(hostname, ".") == 1 {
h.handleLocal(w, req)
return
}
}
for _, server := range h.upstream.Servers {
reqCopy := req.Copy()
reqCopy.Id = dns.Id()
response, _, err := h.client.Exchange(reqCopy, fmt.Sprintf("%s:%s", server, h.upstream.Port))
if err != nil || 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))
}
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
}
// 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
})
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]
}
return maxLen
}
+172
View File
@@ -0,0 +1,172 @@
package nameserver
import (
"fmt"
"math/rand"
"net"
"strconv"
"testing"
"time"
"github.com/miekg/dns"
"github.com/stretchr/testify/require"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/router"
)
func startServer(t *testing.T, upstream *dns.ClientConfig) (*DNSServer, *Nameserver, int, int) {
peername, err := router.PeerNameFromString("00:00:00:02:00:00")
require.Nil(t, err)
nameserver := New(peername, nil, nil, "")
dnsserver, err := NewDNSServer(nameserver, "weave.local.", "0.0.0.0:0", "", 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
if upstream != nil {
dnsserver.upstream = upstream
}
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.", "", router.UnknownPeerName, i)
}
doRequest := func(client *dns.Client, request *dns.Msg, port int) *dns.Msg {
request.SetQuestion("foo.weave.local.", dns.TypeA)
response, _, err := client.Exchange(request, fmt.Sprintf("127.0.0.1:%d", port))
require.Nil(t, 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)
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)
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)
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)
}
+209
View File
@@ -0,0 +1,209 @@
package nameserver
import (
"bytes"
"encoding/gob"
"fmt"
"sort"
"time"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/router"
)
var now = func() int64 { return time.Now().Unix() }
type Entry struct {
ContainerID string
Origin router.PeerName
Addr address.Address
Hostname string
Version int
Tombstone int64 // timestamp of when it was deleted
}
type Entries []Entry
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
}
}
// 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
}
return false
}
func (e1 *Entry) String() string {
return fmt.Sprintf("%s -> %s", e1.Hostname, e1.Addr.String())
}
func (es Entries) Len() int { return len(es) }
func (es Entries) Swap(i, j int) { panic("Swap") }
func (es Entries) Less(i, j int) bool { return es[i].less(&es[j]) }
func (es Entries) check() error {
if !sort.IsSorted(es) {
return fmt.Errorf("Not sorted!")
}
for i := 1; i < len(es); i++ {
if es[i].equal(&es[i-1]) {
return fmt.Errorf("Duplicate entry: %d:%v and %d:%v", i-1, es[i-1], i, es[i])
}
}
return nil
}
func (es *Entries) checkAndPanic() {
if err := es.check(); err != nil {
panic(err)
}
}
func (es *Entries) add(hostname, containerid string, origin router.PeerName, addr address.Address) Entry {
es.checkAndPanic()
defer es.checkAndPanic()
entry := Entry{Hostname: hostname, Origin: origin, ContainerID: containerid, Addr: addr}
i := sort.Search(len(*es), func(i int) bool {
return !(*es)[i].less(&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 {
es.checkAndPanic()
defer es.checkAndPanic()
newEntries := Entries{}
i := 0
for _, entry := range incoming {
for i < len(*es) && (*es)[i].less(&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 router.PeerName, f func(*Entry) bool) Entries {
es.checkAndPanic()
defer es.checkAndPanic()
tombstoned := Entries{}
for i, e := range *es {
if f(&e) && e.Origin == ourname {
e.Version++
e.Tombstone = now()
(*es)[i] = e
tombstoned = append(tombstoned, e)
}
}
return tombstoned
}
func (es *Entries) filter(f func(*Entry) bool) {
es.checkAndPanic()
defer es.checkAndPanic()
i := 0
for _, e := range *es {
if !f(&e) {
continue
}
(*es)[i] = e
i++
}
*es = (*es)[:i]
}
func (es Entries) lookup(hostname string) Entries {
es.checkAndPanic()
i := sort.Search(len(es), func(i int) bool {
return es[i].Hostname >= hostname
})
if i >= len(es) || es[i].Hostname != hostname {
return Entries{}
}
j := sort.Search(len(es)-i, func(j int) bool {
return es[i+j].Hostname > hostname
})
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")
}
type GossipData struct {
Timestamp int64
Entries
}
func (g *GossipData) Merge(o router.GossipData) {
other := o.(*GossipData)
g.Entries.merge(other.Entries)
if g.Timestamp < other.Timestamp {
g.Timestamp = other.Timestamp
}
}
func (g *GossipData) Encode() [][]byte {
buf := &bytes.Buffer{}
if err := gob.NewEncoder(buf).Encode(g); err != nil {
panic(err)
}
return [][]byte{buf.Bytes()}
}
+132
View File
@@ -0,0 +1,132 @@
package nameserver
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/router"
)
func TestAdd(t *testing.T) {
oldNow := now
defer func() { now = oldNow }()
now = func() int64 { return 1234 }
entries := Entries{}
entries.add("A", "", router.UnknownPeerName, address.Address(0))
expected := Entries{
Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0)},
}
require.Equal(t, entries, expected)
entries.tombstone(router.UnknownPeerName, func(e *Entry) bool { return e.Hostname == "A" })
expected = Entries{
Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0), Version: 1, Tombstone: 1234},
}
require.Equal(t, entries, expected)
entries.add("A", "", router.UnknownPeerName, address.Address(0))
expected = Entries{
Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0), Version: 2},
}
require.Equal(t, entries, expected)
}
func TestMerge(t *testing.T) {
e1 := Entries{
Entry{Hostname: "A"},
Entry{Hostname: "C"},
Entry{Hostname: "D"},
Entry{Hostname: "F"},
}
e2 := Entries{
Entry{Hostname: "B"},
Entry{Hostname: "E"},
Entry{Hostname: "F"},
}
diff := e1.merge(e2)
expectedDiff := Entries{
Entry{Hostname: "B"},
Entry{Hostname: "E"},
}
require.Equal(t, expectedDiff, diff)
expected := Entries{
Entry{Hostname: "A"},
Entry{Hostname: "B"},
Entry{Hostname: "C"},
Entry{Hostname: "D"},
Entry{Hostname: "E"},
Entry{Hostname: "F"},
}
require.Equal(t, expected, e1)
diff = e1.merge(e1)
require.Equal(t, Entries{}, diff)
}
func TestOldMerge(t *testing.T) {
e1 := Entries{Entry{Hostname: "A", Version: 0}}
diff := e1.merge(Entries{Entry{Hostname: "A", Version: 1}})
require.Equal(t, Entries{Entry{Hostname: "A", Version: 1}}, diff)
require.Equal(t, Entries{Entry{Hostname: "A", Version: 1}}, e1)
diff = e1.merge(Entries{Entry{Hostname: "A", Version: 0}})
require.Equal(t, Entries{}, diff)
require.Equal(t, Entries{Entry{Hostname: "A", Version: 1}}, e1)
}
func TestTombstone(t *testing.T) {
oldNow := now
defer func() { now = oldNow }()
now = func() int64 { return 1234 }
es := Entries{
Entry{Hostname: "A"},
Entry{Hostname: "B"},
}
es.tombstone(router.UnknownPeerName, func(e *Entry) bool {
return e.Hostname == "B"
})
expected := Entries{
Entry{Hostname: "A"},
Entry{Hostname: "B", Version: 1, Tombstone: 1234},
}
require.Equal(t, expected, es)
}
func TestDelete(t *testing.T) {
es := Entries{
Entry{Hostname: "A"},
Entry{Hostname: "B"},
}
es.filter(func(e *Entry) bool {
return e.Hostname != "A"
})
expected := Entries{
Entry{Hostname: "B"},
}
require.Equal(t, expected, es)
}
func TestLookup(t *testing.T) {
es := Entries{
Entry{Hostname: "A"},
Entry{Hostname: "B", ContainerID: "bar"},
Entry{Hostname: "B", ContainerID: "foo"},
Entry{Hostname: "C"},
}
have := es.lookup("B")
want := Entries{
Entry{Hostname: "B", ContainerID: "bar"},
Entry{Hostname: "B", ContainerID: "foo"},
}
require.Equal(t, have, want)
}
+65 -67
View File
@@ -1,95 +1,93 @@
package nameserver
import (
"encoding/json"
"fmt"
"net/http"
"github.com/gorilla/mux"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"log"
"net"
"net/http"
"github.com/weaveworks/weave/net/address"
)
func httpErrorAndLog(level *log.Logger, w http.ResponseWriter, msg string,
status int, logmsg string, logargs ...interface{}) {
http.Error(w, msg, status)
level.Printf("[http] "+logmsg, logargs...)
func (n *Nameserver) badRequest(w http.ResponseWriter, err error) {
http.Error(w, err.Error(), http.StatusBadRequest)
n.infof("%v", err)
}
func ListenHTTP(version string, server *DNSServer, domain string, db Zone, port int) {
muxRouter := mux.NewRouter()
muxRouter.Methods("GET").Path("/status").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "weave DNS", version)
fmt.Fprintln(w, server.Status())
func (n *Nameserver) HandleHTTP(router *mux.Router) {
router.Methods("GET").Path("/domain").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, n.domain)
})
muxRouter.Methods("PUT").Path("/name/{id:.+}/{ip:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
reqError := func(msg string, logmsg string, logargs ...interface{}) {
httpErrorAndLog(Warning, w, msg, http.StatusBadRequest, logmsg, logargs...)
}
vars := mux.Vars(r)
idStr := vars["id"]
ipStr := vars["ip"]
name := r.FormValue("fqdn")
if name == "" {
reqError("Invalid FQDN", "Invalid FQDN in request: %s, %s", r.URL, r.Form)
router.Methods("PUT").Path("/name/{container}/{ip}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
vars = mux.Vars(r)
container = vars["container"]
ipStr = vars["ip"]
hostname = dns.Fqdn(r.FormValue("fqdn"))
ip, err = address.ParseIP(ipStr)
)
if err != nil {
n.badRequest(w, err)
return
}
ip := net.ParseIP(ipStr)
if ip == nil {
reqError("Invalid IP", "Invalid IP in request: %s", ipStr)
if err := n.AddEntry(hostname, container, n.ourName, ip); err != nil {
n.badRequest(w, fmt.Errorf("Unable to add entry: %v", err))
return
}
if dns.IsSubDomain(domain, name) {
Info.Printf("[http] Adding %s -> %s", name, ipStr)
if err := db.AddRecord(idStr, name, ip); err != nil {
if _, ok := err.(DuplicateError); !ok {
httpErrorAndLog(
Error, w, "Internal error", http.StatusInternalServerError,
"Unexpected error from DB: %s", err)
return
} // oh, I already know this. whatever.
if r.FormValue("check-alive") == "true" && n.docker.IsContainerNotRunning(container) {
n.infof("container '%s' is not running: removing", container)
if err := n.Delete(hostname, container, ipStr, ip); err != nil {
n.infof("failed to remove: %v", err)
}
}
w.WriteHeader(204)
})
deleteHandler := func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
hostname := r.FormValue("fqdn")
if hostname == "" {
hostname = "*"
} else {
Info.Printf("[http] Ignoring name %s, not in %s", name, domain)
}
})
muxRouter.Methods("DELETE").Path("/name/{id:.+}/{ip:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
reqError := func(msg string, logmsg string, logargs ...interface{}) {
httpErrorAndLog(Warning, w, msg, http.StatusBadRequest, logmsg, logargs...)
hostname = dns.Fqdn(hostname)
}
vars := mux.Vars(r)
idStr := vars["id"]
ipStr := vars["ip"]
container, ok := vars["container"]
if !ok {
container = "*"
}
ip := net.ParseIP(ipStr)
if ip == nil {
reqError("Invalid IP in request", "Invalid IP in request: %s", ipStr)
ipStr, ok := vars["ip"]
ip, err := address.ParseIP(ipStr)
if ok && err != nil {
n.badRequest(w, err)
return
} else if !ok {
ipStr = "*"
}
if err := n.Delete(hostname, container, ipStr, ip); err != nil {
n.badRequest(w, fmt.Errorf("Unable to delete entries: %v", err))
return
}
Info.Printf("[http] Deleting %s (%s)", idStr, ipStr)
if err := db.DeleteRecord(idStr, ip); err != nil {
if _, ok := err.(LookupError); !ok {
httpErrorAndLog(
Error, w, "Internal error", http.StatusInternalServerError,
"Unexpected error from DB: %s", err)
return
}
w.WriteHeader(204)
}
router.Methods("DELETE").Path("/name/{container}/{ip}").HandlerFunc(deleteHandler)
router.Methods("DELETE").Path("/name/{container}").HandlerFunc(deleteHandler)
router.Methods("DELETE").Path("/name").HandlerFunc(deleteHandler)
router.Methods("GET").Path("/name").Headers("Accept", "application/json").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
n.RLock()
defer n.RUnlock()
if err := json.NewEncoder(w).Encode(n.entries); err != nil {
n.badRequest(w, fmt.Errorf("Error marshalling response: %v", err))
}
})
http.Handle("/", muxRouter)
address := fmt.Sprintf(":%d", port)
if err := http.ListenAndServe(address, nil); err != nil {
Error.Fatal("[http] Unable to create http listener: ", err)
}
}
-91
View File
@@ -1,91 +0,0 @@
package nameserver
import (
"fmt"
wt "github.com/weaveworks/weave/testing"
"math/rand"
"net"
"net/http"
"net/url"
"strings"
"testing"
"time"
)
func genForm(method string, url string, data url.Values) (resp *http.Response, err error) {
req, err := http.NewRequest(method, url, strings.NewReader(data.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
return http.DefaultClient.Do(req)
}
func TestHttp(t *testing.T) {
var (
containerID = "deadbeef"
testDomain = "weave.local."
successTestName = "test1." + testDomain
testAddr1 = "10.2.2.1/24"
dockerIP = "9.8.7.6"
)
zone := NewZoneDb(DefaultLocalDomain)
port := rand.Intn(10000) + 32768
fmt.Println("Http test on port", port)
go ListenHTTP("", nil, testDomain, zone, port)
time.Sleep(100 * time.Millisecond) // Allow for http server to get going
// Ask the http server to add our test address into the database
addrParts := strings.Split(testAddr1, "/")
addrURL := fmt.Sprintf("http://localhost:%d/name/%s/%s", port, containerID, addrParts[0])
resp, err := genForm("PUT", addrURL,
url.Values{"fqdn": {successTestName}, "local_ip": {dockerIP}, "routing_prefix": {addrParts[1]}})
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
// Check that the address is now there.
ip, _, _ := net.ParseCIDR(testAddr1)
foundIP, err := zone.LookupName(successTestName)
wt.AssertNoErr(t, err)
if !foundIP[0].IP().Equal(ip) {
t.Fatalf("Unexpected result for %s: received %s, expected %s", successTestName, foundIP, ip)
}
// Adding exactly the same address should be OK
resp, err = genForm("PUT", addrURL,
url.Values{"fqdn": {successTestName}, "local_ip": {dockerIP}, "routing_prefix": {addrParts[1]}})
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http success response for duplicate add")
// Now try adding the same address again with a different ident --
// again should be fine
otherURL := fmt.Sprintf("http://localhost:%d/name/%s/%s", port, "other", addrParts[0])
resp, err = genForm("PUT", otherURL,
url.Values{"fqdn": {successTestName}, "local_ip": {dockerIP}, "routing_prefix": {addrParts[1]}})
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
// Delete the address
resp, err = genForm("DELETE", addrURL, nil)
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
// Check that the address is still resolvable.
x, err := zone.LookupName(successTestName)
t.Logf("Got %s", x)
wt.AssertNoErr(t, err)
// Delete the address record mentioning the other container
resp, err = genForm("DELETE", otherURL, nil)
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
// Check that the address is gone
x, err = zone.LookupName(successTestName)
t.Logf("Got %s", x)
wt.AssertErrorType(t, err, (*LookupError)(nil), "fully-removed address")
// Would like to shut down the http server at the end of this test
// but it's complicated.
// See https://groups.google.com/forum/#!topic/golang-nuts/vLHWa5sHnCE
}
-296
View File
@@ -1,296 +0,0 @@
package nameserver
import (
"bytes"
"fmt"
"github.com/miekg/dns"
"math"
"net"
"time"
)
// Portions of this code taken from github.com/armon/mdns
const (
ipv4mdns = "224.0.0.251" // link-local multicast address
mdnsPort = 5353 // mDNS assigned port
// We wait this long to hear a response from other mDNS servers on
// the network.
mDNSTimeout = 500 * time.Millisecond
MaxDuration = time.Duration(math.MaxInt64)
MailboxSize = 16
)
var (
ipv4Addr = &net.UDPAddr{
IP: net.ParseIP(ipv4mdns),
Port: mdnsPort,
}
)
type Response struct {
name string
addr net.IP
ttl int
err error
}
func (r Response) Name() string { return r.name }
func (r Response) IP() net.IP { return r.addr }
func (r Response) Priority() int { return 0 }
func (r Response) Weight() int { return 0 }
func (r Response) TTL() int { return r.ttl }
func (r Response) Equal(r2 *Response) bool {
if r.name != r2.name {
return false
}
if !r.addr.Equal(r2.addr) {
return false
}
if r.err != r2.err {
return false
}
return true
}
func (r Response) String() string {
var buf bytes.Buffer
if r.err != nil {
fmt.Fprintf(&buf, "%s", r.err)
} else {
if len(r.Name()) > 0 {
fmt.Fprintf(&buf, "%s", r.Name())
}
if !r.IP().IsUnspecified() {
fmt.Fprintf(&buf, "[%s]", r.IP())
}
if r.ttl > 0 {
fmt.Fprintf(&buf, "(TTL:%d)", r.TTL())
}
}
return buf.String()
}
type responseInfo struct {
timeout time.Time // if no answer by this time, give up
insistent bool // insistent queries are not removed on the first reply
ch chan<- *Response
}
// Represents one query that we have sent for one name.
// If we, internally, get several requests for the same name while we have
// a query in flight, then we don't want to send more queries out.
// Invariant on responseInfos: they are in ascending order of timeout.
type inflightQuery struct {
name string
id uint16 // the DNS message ID
responseInfos []*responseInfo
}
type MDNSClient struct {
// note unorthodox use of 'Server' class in client logic - using it to
// listen on a multicast socket and call callback whenever a message comes in.
listener *dns.Server
conn *net.UDPConn
addr *net.UDPAddr
inflight map[string]*inflightQuery
actionChan chan<- MDNSAction
running bool
}
type mDNSQueryInfo struct {
name string
querytype uint16
responseCh chan<- *Response
}
func NewMDNSClient() (*MDNSClient, error) {
return &MDNSClient{
addr: ipv4Addr,
running: false,
inflight: make(map[string]*inflightQuery)}, nil
}
func (c *MDNSClient) Start(ifi *net.Interface) (err error) {
if c.running {
return nil
}
if c.conn, err = net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0}); err != nil {
return err
}
multicast, err := LinkLocalMulticastListener(ifi)
if err != nil {
return err
}
handleMDNS := func(w dns.ResponseWriter, r *dns.Msg) {
// Don't want to handle queries here, so filter anything out that isn't a response
if len(r.Answer) > 0 {
c.ResponseCallback(r)
}
}
c.listener = &dns.Server{Unsafe: true, PacketConn: multicast, Handler: dns.HandlerFunc(handleMDNS)}
go c.listener.ActivateAndServe()
actionChan := make(chan MDNSAction, MailboxSize)
c.actionChan = actionChan
go c.actorLoop(actionChan)
return nil
}
func (c *MDNSClient) Stop() error {
if c.running {
c.actionChan <- nil
}
return nil
}
func LinkLocalMulticastListener(ifi *net.Interface) (net.PacketConn, error) {
return net.ListenMulticastUDP("udp", ifi, ipv4Addr)
}
// ACTOR client API
type MDNSAction func()
// Async
func (c *MDNSClient) SendQuery(name string, querytype uint16, insistent bool, responseCh chan<- *Response) {
c.actionChan <- func() {
query, found := c.inflight[name]
if !found {
m := new(dns.Msg)
m.SetQuestion(name, querytype)
m.RecursionDesired = false
buf, err := m.Pack()
if err != nil {
responseCh <- &Response{err: err}
close(responseCh)
return
}
query = &inflightQuery{
name: name,
id: m.Id,
}
if _, err = c.conn.WriteTo(buf, c.addr); err != nil {
responseCh <- &Response{err: err}
close(responseCh)
return
}
c.inflight[name] = query
}
info := &responseInfo{
ch: responseCh,
timeout: time.Now().Add(mDNSTimeout),
insistent: insistent,
}
// Invariant on responseInfos: they are in ascending order of
// timeout. Since we use a fixed interval from Now(), this
// must be after all existing timeouts.
query.responseInfos = append(query.responseInfos, info)
}
}
// Async - called from dns library multiplexer
func (c *MDNSClient) ResponseCallback(r *dns.Msg) {
c.actionChan <- func() {
for _, answer := range r.Answer {
var name string
var res *Response
switch rr := answer.(type) {
case *dns.A:
name = rr.Hdr.Name
res = &Response{name: name, addr: rr.A, ttl: int(rr.Hdr.Ttl)}
case *dns.PTR:
name = rr.Hdr.Name
raddr, _ := raddrToIP(name)
res = &Response{name: rr.Ptr, addr: raddr, ttl: int(rr.Hdr.Ttl)}
default:
return
}
if query, found := c.inflight[name]; found {
var newResponseInfos []*responseInfo
for _, resp := range query.responseInfos {
resp.ch <- res
// insistent queries are not removed on the first reply, but on the timeout
if resp.insistent {
newResponseInfos = append(newResponseInfos, resp)
} else {
close(resp.ch)
}
}
if len(newResponseInfos) == 0 {
delete(c.inflight, name)
} else {
query.responseInfos = newResponseInfos
}
} else {
// We've received a response that didn't match a query
// Do we want to cache it?
}
}
}
}
// ACTOR server
// Check all in-flight queries, close all that have already timed out,
// and return the duration until the next timeout
func (c *MDNSClient) checkInFlightQueries() time.Duration {
now := time.Now()
after := MaxDuration
for name, query := range c.inflight {
// Invariant on responseInfos: they are in ascending order of timeout.
numClosed := 0
for _, item := range query.responseInfos {
duration := item.timeout.Sub(now)
if duration <= 0 { // timed out
close(item.ch)
numClosed++
} else {
if duration < after {
after = duration
}
break // don't need to look at any more for this query
}
}
// Remove timed-out items from the slice
query.responseInfos = query.responseInfos[numClosed:]
if len(query.responseInfos) == 0 {
delete(c.inflight, name)
}
}
return after
}
func (c *MDNSClient) actorLoop(actionChan <-chan MDNSAction) {
timer := time.NewTimer(MaxDuration)
run := func() { timer.Reset(c.checkInFlightQueries()) }
c.running = true
for c.running {
select {
case action := <-actionChan:
if action == nil {
c.listener.Shutdown()
c.running = false
} else {
action()
run()
}
case <-timer.C:
run()
}
}
// Close all response channels at termination
for _, query := range c.inflight {
for _, item := range query.responseInfos {
close(item.ch)
}
}
}
-197
View File
@@ -1,197 +0,0 @@
package nameserver
import (
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
wt "github.com/weaveworks/weave/testing"
"log"
"net"
"testing"
"time"
)
var (
successTestName = "test1.weave."
failTestName = "test2.weave."
testAddr = net.ParseIP("9.8.7.6")
testInAddr = "6.7.8.9.in-addr.arpa."
)
func minimalServer(w dns.ResponseWriter, req *dns.Msg) {
sendAnswer := func(m *dns.Msg) {
buf, err := m.Pack()
CheckFatal(err)
if buf == nil {
log.Fatal("Nil buffer")
}
//log.Println("minimalServer sending:", buf)
// This is a bit of a kludge - per the RFC we should send responses from 5353, but that doesn't seem to work
sendconn, err := net.DialUDP("udp", nil, ipv4Addr)
CheckFatal(err)
_, err = sendconn.Write(buf)
sendconn.Close()
CheckFatal(err)
}
//log.Println("minimalServer received:", req)
if len(req.Answer) > 0 {
return // Only interested in questions.
}
if len(req.Question) != 1 {
return // We only handle single-question messages
}
if req.Question[0].Qtype == dns.TypeA && req.Question[0].Name == successTestName {
m := new(dns.Msg)
m.SetReply(req)
hdr := dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeA,
Class: dns.ClassINET, Ttl: 3600}
a := &dns.A{Hdr: hdr, A: testAddr}
m.Answer = append(m.Answer, a)
sendAnswer(m)
} else if req.Question[0].Qtype == dns.TypePTR && req.Question[0].Name == testInAddr {
m := new(dns.Msg)
m.SetReply(req)
hdr := dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR,
Class: dns.ClassINET, Ttl: 3600}
a := &dns.PTR{Hdr: hdr, Ptr: successTestName}
m.Answer = append(m.Answer, a)
sendAnswer(m)
}
}
func RunLocalMulticastServer() (*dns.Server, error) {
multicast, err := LinkLocalMulticastListener(nil)
if err != nil {
return nil, err
}
server := &dns.Server{
PacketConn: multicast,
Handler: dns.HandlerFunc(minimalServer),
ReadTimeout: 100 * time.Millisecond}
go server.ActivateAndServe()
return server, nil
}
func setup(t *testing.T) (*MDNSClient, *dns.Server, error) {
InitDefaultLogging(testing.Verbose())
server, err := RunLocalMulticastServer()
if err != nil {
t.Fatalf("Unable to run test server: %s. No default multicast interface?", err)
}
mdnsClient, err := NewMDNSClient()
wt.AssertNoErr(t, err)
err = mdnsClient.Start(nil)
wt.AssertNoErr(t, err)
return mdnsClient, server, err
}
func (c *MDNSClient) Shutdown() {
c.actionChan <- nil
}
type testContext struct {
receivedAddr net.IP
receivedCount int
channel chan *Response
}
func newTestContext() *testContext {
return &testContext{channel: make(chan *Response, 4)}
}
func (c *testContext) checkResponse(t *testing.T, channelOk bool, resp *Response) {
if !channelOk {
c.channel = nil
return
}
wt.AssertNoErr(t, resp.err)
log.Printf("Got address response %s addr %s", resp.Name(), resp.IP())
c.receivedAddr = resp.IP()
c.receivedCount++
}
func TestSimpleQuery(t *testing.T) {
log.Println("TestSimpleQuery starting")
mdnsClient, server, _ := setup(t)
defer mdnsClient.Shutdown()
defer server.Shutdown()
context := newTestContext()
// First, a test we expect to succeed
mdnsClient.SendQuery(successTestName, dns.TypeA, false, context.channel)
for resp := range context.channel {
context.checkResponse(t, true, resp)
}
if !context.receivedAddr.Equal(testAddr) || context.receivedCount != 1 {
t.Fatal("Unexpected result for", successTestName, context.receivedAddr, context.receivedCount)
}
// Now, a test we expect to time out with no responses
context = newTestContext()
mdnsClient.SendQuery("test2.weave.", dns.TypeA, false, context.channel)
for resp := range context.channel {
context.checkResponse(t, true, resp)
}
if context.receivedCount > 0 {
t.Fatal("Unexpected result for test2.weave", context.receivedAddr)
}
}
func TestParallelQuery(t *testing.T) {
log.Println("TestParallelQuery starting")
mdnsClient, server, _ := setup(t)
defer mdnsClient.Shutdown()
defer server.Shutdown()
context1 := newTestContext()
context2 := newTestContext()
go mdnsClient.SendQuery(successTestName, dns.TypeA, false, context1.channel)
go mdnsClient.SendQuery(successTestName, dns.TypeA, false, context2.channel)
timeout := time.After(2 * time.Second)
outerloop:
for context1.channel != nil || context2.channel != nil {
select {
case resp, ok := <-context1.channel:
context1.checkResponse(t, ok, resp)
case resp, ok := <-context2.channel:
context2.checkResponse(t, ok, resp)
case <-timeout:
break outerloop
}
}
if !context1.receivedAddr.Equal(testAddr) || !context2.receivedAddr.Equal(testAddr) || context1.receivedCount != 1 || context2.receivedCount != 1 {
t.Fatal("Unexpected result for", successTestName, context1.receivedAddr, context2.receivedAddr, context1.receivedCount, context2.receivedCount)
}
}
func TestAsLookup(t *testing.T) {
mdnsClient, server, _ := setup(t)
defer mdnsClient.Shutdown()
defer server.Shutdown()
ips, err := mdnsClient.LookupName(successTestName)
wt.AssertNoErr(t, err)
if !testAddr.Equal(ips[0].IP()) {
t.Fatalf("Returned address incorrect %s", ips)
}
ips, err = mdnsClient.LookupName("foo.example.com.")
wt.AssertErrorType(t, err, (*LookupError)(nil), "unknown hostname")
names, err := mdnsClient.LookupInaddr(testInAddr)
wt.AssertNoErr(t, err)
if !(successTestName == names[0].Name()) {
t.Fatalf("Expected name %s, got %s", successTestName, names)
}
}
-71
View File
@@ -1,71 +0,0 @@
package nameserver
import (
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
)
func mdnsLookup(client *MDNSClient, name string, qtype uint16, insistent bool) ([]ZoneRecord, error) {
var responses []*Response
channel := make(chan *Response)
client.SendQuery(name, qtype, insistent, channel)
for resp := range channel {
if err := resp.err; err != nil {
Debug.Printf("[mdns] Error for query type %s name %s: %s", dns.TypeToString[qtype], name, err)
return nil, err
}
Debug.Printf("[mdns] Got response name for %s-query about name '%s': name '%s', addr '%s'",
dns.TypeToString[qtype], name, resp.name, resp.addr)
if !insistent {
return []ZoneRecord{resp}, nil // non-background queries return the first response...
}
// add the response, avoiding duplicates
// TODO: replace this linear search by something better... (not a big deal as we don't expect long responses)
duplicate := false
for _, r := range responses {
if r.Equal(resp) {
duplicate = true
break
}
}
if !duplicate {
responses = append(responses, resp)
}
}
if len(responses) == 0 {
return nil, LookupError(name)
}
// convert []Response to []ZoneRecord
res := make([]ZoneRecord, len(responses))
for i, rr := range responses {
res[i] = ZoneRecord(rr)
}
return res, nil
}
// Perform a lookup for a name, returning either an IP address or an error
func (client *MDNSClient) LookupName(name string) ([]ZoneRecord, error) {
return mdnsLookup(client, name, dns.TypeA, false)
}
// Perform a lookup for an IP address, returning either a name or an error
func (client *MDNSClient) LookupInaddr(inaddr string) ([]ZoneRecord, error) {
return mdnsLookup(client, inaddr, dns.TypePTR, false)
}
// Perform lookup for a name, returning either a list of IP addresses or an error
// Insistent queries will wait up to `mDNSTimeout` milliseconds for responses
func (client *MDNSClient) InsistentLookupName(name string) ([]ZoneRecord, error) {
return mdnsLookup(client, name, dns.TypeA, true)
}
// Perform an insistent lookup for an IP address, returning either a list of names or an error
// Insistent queries will wait up to `mDNSTimeout` milliseconds for responses
func (client *MDNSClient) InsistentLookupInaddr(inaddr string) ([]ZoneRecord, error) {
return mdnsLookup(client, inaddr, dns.TypePTR, true)
}
-143
View File
@@ -1,143 +0,0 @@
package nameserver
import (
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"net"
)
type MDNSServer struct {
localAddrs []net.Addr
sendconn *net.UDPConn
srv *dns.Server
zone Zone
running bool
}
// Create a new mDNS server
// Nothing will be done (including port bindings) until you `Start()` the server
func NewMDNSServer(zone Zone) (*MDNSServer, error) {
return &MDNSServer{zone: zone}, nil
}
// Return true if testaddr is a UDP address with IP matching my local i/f
func (s *MDNSServer) addrIsLocal(testaddr net.Addr) bool {
if udpaddr, ok := testaddr.(*net.UDPAddr); ok {
for _, localaddr := range s.localAddrs {
if ipnetlocal, ok := localaddr.(*net.IPNet); ok {
if ipnetlocal.IP.Equal(udpaddr.IP) {
return true
}
}
}
}
return false
}
// Start the mDNS server
func (s *MDNSServer) Start(ifi *net.Interface) (err error) {
// skip double initialization
if s.running {
return nil
}
// This is a bit of a kludge - per the RFC we should send responses from 5353, but that doesn't seem to work
s.sendconn, err = net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
if err != nil {
return err
}
conn, err := LinkLocalMulticastListener(ifi)
if err != nil {
return err
}
if ifi == nil {
s.localAddrs, err = net.InterfaceAddrs()
} else {
s.localAddrs, err = ifi.Addrs()
}
if err != nil {
return err
}
handleLocal := s.makeHandler(dns.TypeA,
func(zone ZoneLookup, r *dns.Msg, q *dns.Question) *dns.Msg {
if ips, err := zone.LookupName(q.Name); err == nil {
return makeAddressReply(r, q, ips)
}
return nil
})
handleReverse := s.makeHandler(dns.TypePTR,
func(zone ZoneLookup, r *dns.Msg, q *dns.Question) *dns.Msg {
if names, err := zone.LookupInaddr(q.Name); err == nil {
return makePTRReply(r, q, names)
}
return nil
})
mux := dns.NewServeMux()
mux.HandleFunc(s.zone.Domain(), handleLocal)
mux.HandleFunc(RDNSDomain, handleReverse)
s.srv = &dns.Server{
Listener: nil,
PacketConn: conn,
Handler: mux,
}
go s.srv.ActivateAndServe()
s.running = true
return err
}
// Stop the mDNS server
func (s *MDNSServer) Stop() error {
s.running = false
return s.srv.Shutdown()
}
func (s *MDNSServer) Zone() Zone {
return s.zone
}
type LookupFunc func(ZoneLookup, *dns.Msg, *dns.Question) *dns.Msg
func (s *MDNSServer) makeHandler(qtype uint16, lookup LookupFunc) dns.HandlerFunc {
return func(rw dns.ResponseWriter, r *dns.Msg) {
// Handle only questions, ignore answers. We might also ignore
// questions that arise locally (i.e., that come from an IP we
// think is local), but in the interest of avoiding
// complication, and easier testing, this is elided on the
// assumption that the client wouldn't ask if it already knew
// the answer, and if it does ask, it'll be happy to get an
// answer.
// TODO: use any Answer in the query for adding records in the Zone database...
if len(r.Answer) == 0 && len(r.Question) > 0 {
q := &r.Question[0]
if q.Qtype == qtype {
Debug.Printf("[mdns msgid %d] srv: trying to answer to mDNS query '%s'", r.MsgHdr.Id, q.Name)
if m := lookup(s.zone, r, q); m != nil {
Debug.Printf("[mdns msgid %d] srv: found local answer to mDNS query '%s'", r.MsgHdr.Id, q.Name)
if err := s.sendResponse(m); err != nil {
Warning.Printf("[mdns msgid %d] srv: error writing mDNS response to %v", r.MsgHdr.Id, s.sendconn)
} else {
Debug.Printf("[mdns msgid %d] srv: response sent: %d answers", r.MsgHdr.Id, len(m.Answer))
}
} else {
Debug.Printf("[mdns msgid %d] srv: no local answer for answering mDNS query '%s'",
r.MsgHdr.Id, q.Name)
}
}
}
}
}
func (s *MDNSServer) sendResponse(m *dns.Msg) error {
buf, err := m.Pack()
if err != nil {
return err
}
_, err = s.sendconn.WriteTo(buf, ipv4Addr)
return err
}
-120
View File
@@ -1,120 +0,0 @@
package nameserver
import (
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
wt "github.com/weaveworks/weave/testing"
"net"
"testing"
"time"
)
func TestServerSimpleQuery(t *testing.T) {
var (
testRecord1 = Record{"test.weave.local.", net.ParseIP("10.20.20.10"), 0, 0, 0}
testRecord2 = Record{"test.weave.local.", net.ParseIP("10.20.20.20"), 0, 0, 0}
testInAddr1 = "10.20.20.10.in-addr.arpa."
)
InitDefaultLogging(testing.Verbose())
Info.Println("TestServerSimpleQuery starting")
mzone := newMockedZoneWithRecords([]ZoneRecord{testRecord1, testRecord2})
mdnsServer, err := NewMDNSServer(mzone)
wt.AssertNoErr(t, err)
err = mdnsServer.Start(nil)
wt.AssertNoErr(t, err)
defer mdnsServer.Stop()
var receivedAddrs []net.IP
receivedName := ""
recvChan := make(chan interface{})
receivedCount := 0
// Implement a minimal listener for responses
multicast, err := LinkLocalMulticastListener(nil)
wt.AssertNoErr(t, err)
handleMDNS := func(w dns.ResponseWriter, r *dns.Msg) {
// Only handle responses here
if len(r.Answer) > 0 {
t.Logf("Received %d answer(s)", len(r.Answer))
for _, answer := range r.Answer {
switch rr := answer.(type) {
case *dns.A:
t.Logf("... A:\n%+v", rr)
receivedAddrs = append(receivedAddrs, rr.A)
receivedCount++
case *dns.PTR:
t.Logf("... PTR:\n%+v", rr)
receivedName = rr.Ptr
receivedCount++
}
}
recvChan <- "ok"
}
}
sendQuery := func(name string, querytype uint16) {
receivedAddrs = make([]net.IP, 0)
receivedName = ""
receivedCount = 0
recvChan = make(chan interface{})
m := new(dns.Msg)
m.SetQuestion(name, querytype)
m.RecursionDesired = false
buf, err := m.Pack()
wt.AssertNoErr(t, err)
conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
wt.AssertNoErr(t, err)
Debug.Printf("Sending UDP packet to %s", ipv4Addr)
_, err = conn.WriteTo(buf, ipv4Addr)
wt.AssertNoErr(t, err)
Debug.Printf("Waiting for response")
select {
case <-recvChan:
return
case <-time.After(100 * time.Millisecond):
Debug.Printf("Timeout while waiting for response")
return
}
}
listener := &dns.Server{
Unsafe: true,
PacketConn: multicast,
Handler: dns.HandlerFunc(handleMDNS),
ReadTimeout: 100 * time.Millisecond}
go listener.ActivateAndServe()
defer listener.Shutdown()
time.Sleep(100 * time.Millisecond) // Allow for server to get going
Debug.Printf("Checking that we get 2 IPs fo name '%s' [A]", testRecord1.Name())
sendQuery(testRecord1.Name(), dns.TypeA)
if receivedCount != 2 {
t.Fatalf("Unexpected result count %d for %s", receivedCount, testRecord1.Name())
}
if !(receivedAddrs[0].Equal(testRecord1.IP()) || receivedAddrs[0].Equal(testRecord2.IP())) {
t.Fatalf("Unexpected result %s for %s", receivedAddrs, testRecord1.Name())
}
if !(receivedAddrs[1].Equal(testRecord1.IP()) || receivedAddrs[1].Equal(testRecord2.IP())) {
t.Fatalf("Unexpected result %s for %s", receivedAddrs, testRecord1.Name())
}
Debug.Printf("Checking that 'testfail.weave.' [A] gets no answers")
sendQuery("testfail.weave.", dns.TypeA)
if receivedCount != 0 {
t.Fatalf("Unexpected result count %d for testfail.weave", receivedCount)
}
Debug.Printf("Checking that '%s' [PTR] gets one name", testInAddr1)
sendQuery(testInAddr1, dns.TypePTR)
if receivedCount != 1 {
t.Fatalf("Expected an answer to %s, got %d answers", testInAddr1, receivedCount)
} else if !(testRecord1.Name() == receivedName) {
t.Fatalf("Expected answer %s to query for %s, got %s", testRecord1.Name(), testInAddr1, receivedName)
}
}
-91
View File
@@ -1,91 +0,0 @@
package nameserver
import (
. "github.com/weaveworks/weave/common"
"net"
"sync"
)
const (
testSocketTimeout = 100 // in millisecs
)
// Warn about some methods that some day should be implemented...
func notImplWarn() { Warning.Printf("Mocked method. Not implemented.") }
// A mocked Zone that always returns the same records
// * it does not send/receive any mDNS query
type mockedZoneWithRecords struct {
sync.RWMutex
records []ZoneRecord
// Statistics
NumLookupsName int
NumLookupsInaddr int
}
func newMockedZoneWithRecords(zr []ZoneRecord) *mockedZoneWithRecords {
return &mockedZoneWithRecords{records: zr}
}
func (mz *mockedZoneWithRecords) Domain() string { return DefaultLocalDomain }
func (mz *mockedZoneWithRecords) LookupName(name string) ([]ZoneRecord, error) {
Debug.Printf("[mocked zone]: LookupName: returning records %s", mz.records)
mz.Lock()
defer mz.Unlock()
mz.NumLookupsName++
var res []ZoneRecord
for _, r := range mz.records {
if r.Name() == name {
res = append(res, r)
}
}
return res, nil
}
func (mz *mockedZoneWithRecords) LookupInaddr(inaddr string) ([]ZoneRecord, error) {
Debug.Printf("[mocked zone]: LookupInaddr: returning records %s", mz.records)
mz.Lock()
defer mz.Unlock()
mz.NumLookupsInaddr++
var res []ZoneRecord
for _, r := range mz.records {
revIP, err := raddrToIP(inaddr)
if err != nil {
return nil, newParseError("lookup address", inaddr)
}
if r.IP().Equal(revIP) {
res = append(res, r)
}
}
return res, nil
}
func (mz *mockedZoneWithRecords) DomainLookupName(name string) ([]ZoneRecord, error) {
return mz.LookupName(name)
}
func (mz *mockedZoneWithRecords) DomainLookupInaddr(inaddr string) ([]ZoneRecord, error) {
return mz.LookupInaddr(inaddr)
}
// the following methods are not currently needed...
func (mz *mockedZoneWithRecords) AddRecord(ident string, name string, ip net.IP) error {
notImplWarn()
return nil
}
func (mz *mockedZoneWithRecords) DeleteRecord(ident string, ip net.IP) error {
notImplWarn()
return nil
}
func (mz *mockedZoneWithRecords) DeleteRecordsFor(ident string) error { notImplWarn(); return nil }
func (mz *mockedZoneWithRecords) Status() string { notImplWarn(); return "nothing" }
func (mz *mockedZoneWithRecords) ObserveName(name string, observer ZoneRecordObserver) error {
notImplWarn()
return nil
}
func (mz *mockedZoneWithRecords) ObserveInaddr(inaddr string, observer ZoneRecordObserver) error {
notImplWarn()
return nil
}
+258
View File
@@ -0,0 +1,258 @@
package nameserver
import (
"bytes"
"encoding/gob"
"fmt"
"sync"
"time"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/common/docker"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/router"
)
const (
// Tombstones do not need to survive long periods of peer disconnection, as
// we delete entries for disconnected peers. Therefore they just need to hang
// around to account for propagation delay through gossip. We do need them
// to hang around longer than the allowed clock skew (defined next). 30mins
// tombstone timeout allows for 15mins max clock skew.
tombstoneTimeout = time.Minute * 30
// Maximum age of acceptable gossip messages (to account for clock skew)
gossipWindow = int64(tombstoneTimeout/time.Second) / 2
// Used by prog/weaver/main.go and proxy/create_container_interceptor.go
DefaultDomain = "weave.local."
)
// Nameserver: gossip-based, in memory nameserver.
// - Holds a sorted list of (hostname, peer, container id, ip) tuples for the whole cluster.
// - This list is gossiped & merged around the cluser.
// - Lookup-by-hostname are O(nlogn), and return a (copy of a) slice of the entries
// - Update is O(n) for now
type Nameserver struct {
sync.RWMutex
ourName router.PeerName
domain string
gossip router.Gossip
docker *docker.Client
entries Entries
peers *router.Peers
quit chan struct{}
}
func New(ourName router.PeerName, peers *router.Peers, docker *docker.Client, domain string) *Nameserver {
ns := &Nameserver{
ourName: ourName,
domain: dns.Fqdn(domain),
peers: peers,
docker: docker,
quit: make(chan struct{}),
}
if peers != nil {
peers.OnGC(ns.PeerGone)
}
return ns
}
func (n *Nameserver) SetGossip(gossip router.Gossip) {
n.gossip = gossip
}
func (n *Nameserver) Start() {
go func() {
ticker := time.Tick(tombstoneTimeout)
for {
select {
case <-n.quit:
return
case <-ticker:
n.deleteTombstones()
}
}
}()
}
func (n *Nameserver) Stop() {
n.quit <- struct{}{}
}
func (n *Nameserver) broadcastEntries(es ...Entry) error {
if n.gossip != nil {
return n.gossip.GossipBroadcast(&GossipData{
Entries: Entries(es),
Timestamp: now(),
})
}
return nil
}
func (n *Nameserver) AddEntry(hostname, containerid string, origin router.PeerName, addr address.Address) error {
n.infof("adding entry %s -> %s", hostname, addr.String())
n.Lock()
entry := n.entries.add(hostname, containerid, origin, addr)
n.Unlock()
return n.broadcastEntries(entry)
}
func (n *Nameserver) Lookup(hostname string) []address.Address {
n.RLock()
defer n.RUnlock()
entries := n.entries.lookup(hostname)
result := []address.Address{}
for _, e := range entries {
if e.Tombstone > 0 {
continue
}
result = append(result, e.Addr)
}
n.debugf("lookup %s -> %s", hostname, &result)
return result
}
func (n *Nameserver) ReverseLookup(ip address.Address) (string, error) {
n.RLock()
defer n.RUnlock()
match, err := n.entries.first(func(e *Entry) bool {
return e.Tombstone == 0 && e.Addr == ip
})
if err != nil {
return "", err
}
n.debugf("reverse lookup %s -> %s", ip, match.Hostname)
return match.Hostname, nil
}
func (n *Nameserver) ContainerDied(ident string) {
n.Lock()
entries := n.entries.tombstone(n.ourName, func(e *Entry) bool {
if e.ContainerID == ident {
n.infof("container %s died; tombstoning entry %s", ident, e.String())
return true
}
return false
})
n.Unlock()
if len(entries) > 0 {
if err := n.broadcastEntries(entries...); err != nil {
n.errorf("failed to broadcast container %s death: %v", ident, err)
}
}
}
func (n *Nameserver) PeerGone(peer *router.Peer) {
n.infof("peer %s gone", peer.String())
n.Lock()
defer n.Unlock()
n.entries.filter(func(e *Entry) bool {
return e.Origin != peer.Name
})
}
func (n *Nameserver) Delete(hostname, containerid, ipStr string, ip address.Address) error {
n.Lock()
n.infof("tombstoning hostname=%s, container=%s, ip=%s", hostname, containerid, ipStr)
entries := n.entries.tombstone(n.ourName, func(e *Entry) bool {
if hostname != "*" && e.Hostname != hostname {
return false
}
if containerid != "*" && e.ContainerID != containerid {
return false
}
if ipStr != "*" && e.Addr != ip {
return false
}
n.infof("tombstoning entry %v", e)
return true
})
n.Unlock()
return n.broadcastEntries(entries...)
}
func (n *Nameserver) deleteTombstones() {
n.Lock()
defer n.Unlock()
now := time.Now().Unix()
n.entries.filter(func(e *Entry) bool {
return e.Tombstone == 0 || now-e.Tombstone <= int64(tombstoneTimeout/time.Second)
})
}
func (n *Nameserver) Gossip() router.GossipData {
n.RLock()
defer n.RUnlock()
gossip := &GossipData{
Entries: make(Entries, len(n.entries)),
Timestamp: now(),
}
copy(gossip.Entries, n.entries)
return gossip
}
func (n *Nameserver) OnGossipUnicast(sender router.PeerName, msg []byte) error {
return nil
}
func (n *Nameserver) receiveGossip(msg []byte) (router.GossipData, router.GossipData, error) {
var gossip GossipData
if err := gob.NewDecoder(bytes.NewReader(msg)).Decode(&gossip); err != nil {
return nil, nil, err
}
if delta := gossip.Timestamp - now(); delta > gossipWindow || delta < -gossipWindow {
return nil, nil, fmt.Errorf("clock skew of %d detected", delta)
}
if err := gossip.Entries.check(); err != nil {
return nil, nil, err
}
n.Lock()
defer n.Unlock()
if n.peers != nil {
gossip.Entries.filter(func(e *Entry) bool {
return n.peers.Fetch(e.Origin) != nil
})
}
newEntries := n.entries.merge(gossip.Entries)
if len(newEntries) > 0 {
return &GossipData{Entries: newEntries, Timestamp: now()}, &gossip, nil
}
return nil, &gossip, nil
}
// merge received data into state and return "everything new I've
// just learnt", or nil if nothing in the received data was new
func (n *Nameserver) OnGossip(msg []byte) (router.GossipData, error) {
newEntries, _, err := n.receiveGossip(msg)
return newEntries, err
}
// merge received data into state and return a representation of
// the received data, for further propagation
func (n *Nameserver) OnGossipBroadcast(_ router.PeerName, msg []byte) (router.GossipData, error) {
_, entries, err := n.receiveGossip(msg)
return entries, err
}
func (n *Nameserver) infof(fmt string, args ...interface{}) {
Log.Infof("[nameserver %s] "+fmt, append([]interface{}{n.ourName}, args...)...)
}
func (n *Nameserver) debugf(fmt string, args ...interface{}) {
Log.Debugf("[nameserver %s] "+fmt, append([]interface{}{n.ourName}, args...)...)
}
func (n *Nameserver) errorf(fmt string, args ...interface{}) {
Log.Errorf("[nameserver %s] "+fmt, append([]interface{}{n.ourName}, args...)...)
}
+256
View File
@@ -0,0 +1,256 @@
package nameserver
import (
"fmt"
"math/rand"
"reflect"
"sort"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/router"
wt "github.com/weaveworks/weave/testing"
"github.com/weaveworks/weave/testing/gossip"
)
func makeNetwork(size int) ([]*Nameserver, *gossip.TestRouter) {
gossipRouter := gossip.NewTestRouter(0.0)
nameservers := make([]*Nameserver, size)
for i := 0; i < size; i++ {
name, _ := router.PeerNameFromString(fmt.Sprintf("%02d:00:00:02:00:00", i))
nameserver := New(name, nil, nil, "")
nameserver.SetGossip(gossipRouter.Connect(nameserver.ourName, nameserver))
nameserver.Start()
nameservers[i] = nameserver
}
return nameservers, gossipRouter
}
func stopNetwork(nameservers []*Nameserver, grouter *gossip.TestRouter) {
for _, nameserver := range nameservers {
nameserver.Stop()
}
grouter.Stop()
}
type pair struct {
origin router.PeerName
addr address.Address
}
type mapping struct {
hostname string
addrs []pair
}
type addrs []address.Address
func (a addrs) Len() int { return len(a) }
func (a addrs) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a addrs) Less(i, j int) bool { return a[i] < a[j] }
func (a addrs) String() string {
ss := []string{}
for _, addr := range a {
ss = append(ss, addr.String())
}
return strings.Join(ss, " ")
}
func (m mapping) Addrs() []address.Address {
want := addrs{}
for _, p := range m.addrs {
want = append(want, p.addr)
}
sort.Sort(want)
return want
}
func TestNameservers(t *testing.T) {
wt.RunWithTimeout(t, time.Minute, func() {
testNameservers(t)
})
}
func testNameservers(t *testing.T) {
//common.SetLogLevel("debug")
lookupTimeout := 10 // ms
nameservers, grouter := makeNetwork(50)
defer stopNetwork(nameservers, grouter)
nameserversByName := map[router.PeerName]*Nameserver{}
for _, n := range nameservers {
nameserversByName[n.ourName] = n
}
mappings := []mapping{}
check := func(nameserver *Nameserver, expected mapping) {
have := []address.Address{}
for i := 0; i < lookupTimeout; i++ {
have = nameserver.Lookup(expected.hostname)
sort.Sort(addrs(have))
if reflect.DeepEqual(have, expected.Addrs()) {
return
}
time.Sleep(1 * time.Millisecond)
}
want := expected.Addrs()
require.Equal(t, addrs(want).String(), addrs(have).String())
}
addMapping := func() {
nameserver := nameservers[rand.Intn(len(nameservers))]
addr := address.Address(rand.Int31())
hostname := fmt.Sprintf("hostname%d", rand.Int63())
mapping := mapping{hostname, []pair{{nameserver.ourName, addr}}}
mappings = append(mappings, mapping)
require.Nil(t, nameserver.AddEntry(hostname, "", nameserver.ourName, addr))
check(nameserver, mapping)
}
addExtraMapping := func() {
if len(mappings) <= 0 {
return
}
nameserver := nameservers[rand.Intn(len(nameservers))]
i := rand.Intn(len(mappings))
mapping := mappings[i]
addr := address.Address(rand.Int31())
mapping.addrs = append(mapping.addrs, pair{nameserver.ourName, addr})
mappings[i] = mapping
require.Nil(t, nameserver.AddEntry(mapping.hostname, "", nameserver.ourName, addr))
check(nameserver, mapping)
}
deleteMapping := func() {
if len(mappings) <= 0 {
return
}
i := rand.Intn(len(mappings))
mapping := mappings[i]
if len(mapping.addrs) <= 0 {
return
}
j := rand.Intn(len(mapping.addrs))
pair := mapping.addrs[j]
mapping.addrs = append(mapping.addrs[:j], mapping.addrs[j+1:]...)
mappings[i] = mapping
nameserver := nameserversByName[pair.origin]
require.Nil(t, nameserver.Delete(mapping.hostname, "*", pair.addr.String(), pair.addr))
check(nameserver, mapping)
}
doLookup := func() {
if len(mappings) <= 0 {
return
}
mapping := mappings[rand.Intn(len(mappings))]
nameserver := nameservers[rand.Intn(len(nameservers))]
check(nameserver, mapping)
}
doReverseLookup := func() {
if len(mappings) <= 0 {
return
}
mapping := mappings[rand.Intn(len(mappings))]
if len(mapping.addrs) <= 0 {
return
}
nameserver := nameservers[rand.Intn(len(nameservers))]
hostname := ""
var err error
for i := 0; i < lookupTimeout; i++ {
hostname, err = nameserver.ReverseLookup(mapping.addrs[0].addr)
if err != nil && mapping.hostname == hostname {
return
}
time.Sleep(1 * time.Millisecond)
}
require.Nil(t, err)
require.Equal(t, mapping.hostname, hostname)
}
for i := 0; i < 1000; i++ {
r := rand.Float32()
switch {
case r < 0.1:
addMapping()
case 0.1 <= r && r < 0.2:
addExtraMapping()
case 0.2 <= r && r < 0.3:
deleteMapping()
case 0.3 <= r && r < 0.9:
doLookup()
case 0.9 <= r:
doReverseLookup()
}
grouter.Flush()
}
}
func TestContainerAndPeerDeath(t *testing.T) {
peername, err := router.PeerNameFromString("00:00:00:02:00:00")
require.Nil(t, err)
nameserver := New(peername, nil, nil, "")
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
require.Nil(t, err)
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
nameserver.ContainerDied("containerid")
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
require.Nil(t, err)
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
nameserver.PeerGone(&router.Peer{Name: peername})
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
}
func TestTombstoneDeletion(t *testing.T) {
oldNow := now
defer func() { now = oldNow }()
now = func() int64 { return 1234 }
peername, err := router.PeerNameFromString("00:00:00:02:00:00")
require.Nil(t, err)
nameserver := New(peername, nil, nil, "")
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
require.Nil(t, err)
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
nameserver.deleteTombstones()
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
err = nameserver.Delete("hostname", "containerid", "", address.Address(0))
require.Nil(t, err)
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
require.Equal(t, Entries{{
ContainerID: "containerid",
Origin: peername,
Addr: address.Address(0),
Hostname: "hostname",
Version: 1,
Tombstone: 1234,
}}, nameserver.entries)
now = func() int64 { return 1234 + int64(tombstoneTimeout/time.Second) + 1 }
nameserver.deleteTombstones()
require.Equal(t, Entries{}, nameserver.entries)
}
-339
View File
@@ -1,339 +0,0 @@
package nameserver
import (
"bytes"
"fmt"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"net"
"sync"
)
const (
DefaultLocalDomain = "weave.local." // The default name used for the local domain
DefaultServerPort = 53 // The default server port
DefaultCLICfgFile = "/etc/resolv.conf" // default "resolv.conf" file to try to load
DefaultUDPBuflen = 4096 // bigger than the default 512
DefaultCacheLen = 8192 // default cache capacity
DefaultResolvWorkers = 8 // default number of resolution workers
DefaultTimeout = 5 // default timeout for DNS resolutions
)
type DNSServerConfig struct {
// (Optional) client config file for resolving upstream servers
UpstreamCfgFile string
// (Optional) DNS client config for the fallback server(s)
UpstreamCfg *dns.ClientConfig
// (Optional) port number (for TCP and UDP)
Port int
// (Optional) local domain (ie, "weave.local.")
LocalDomain string
// (Optional) cache size
CacheLen int
// (Optional) timeout for DNS queries
Timeout int
// (Optional) UDP buffer length
UDPBufLen int
}
type dnsProtocol uint8
const (
protUDP dnsProtocol = iota // UDP protocol
protTCP dnsProtocol = iota // TCP protocol
)
func (proto dnsProtocol) String() string {
switch proto {
case protUDP:
return "UDP"
case protTCP:
return "TCP"
}
return "unknown"
}
// get a new dns.Client for a protocol
func (proto dnsProtocol) GetNewClient(bufsize int) *dns.Client {
switch proto {
case protTCP:
return &dns.Client{Net: "tcp"}
case protUDP:
return &dns.Client{Net: "udp", UDPSize: uint16(bufsize)}
}
return nil
}
type DNSServer struct {
Zone Zone
Iface *net.Interface
Upstream *dns.ClientConfig
udpSrv *dns.Server
tcpSrv *dns.Server
mdnsCli *MDNSClient
mdnsSrv *MDNSServer
cache *Cache
timeout int
udpBuf int
listenersWg *sync.WaitGroup
Domain string // the local domain
ListenAddr string // the address the server is listening at
}
// Creates a new DNS server
func NewDNSServer(config DNSServerConfig, zone Zone, iface *net.Interface) (s *DNSServer, err error) {
s = &DNSServer{
Zone: zone,
Iface: iface,
listenersWg: new(sync.WaitGroup),
Domain: DefaultLocalDomain,
ListenAddr: fmt.Sprintf(":%d", DefaultServerPort),
}
// fill empty parameters with defaults...
if config.Port != 0 {
s.ListenAddr = fmt.Sprintf(":%d", config.Port)
}
if len(config.LocalDomain) > 0 {
s.Domain = config.LocalDomain
}
if config.UpstreamCfg != nil {
s.Upstream = config.UpstreamCfg
} else {
cfgFile := DefaultCLICfgFile
if len(config.UpstreamCfgFile) > 0 {
cfgFile = config.UpstreamCfgFile
}
if s.Upstream, err = dns.ClientConfigFromFile(cfgFile); err != nil {
return nil, err
}
}
if config.Timeout > 0 {
s.timeout = config.Timeout
}
if config.UDPBufLen > 0 {
s.udpBuf = config.UDPBufLen
}
s.mdnsCli, err = NewMDNSClient()
if err != nil {
return
}
s.mdnsSrv, err = NewMDNSServer(s.Zone)
if err != nil {
return
}
cacheLen := DefaultCacheLen
if config.CacheLen > 0 {
cacheLen = config.CacheLen
}
Debug.Printf("[dns] Initializing cache: %d entries", config.CacheLen)
s.cache, err = NewCache(cacheLen, nil)
if err != nil {
return
}
// create two DNS request multiplexerers, depending on the protocol used by clients
// (we use the same protocol for asking upstream servers)
mux := func(proto dnsProtocol) *dns.ServeMux {
m := dns.NewServeMux()
m.HandleFunc(s.Domain, s.queryHandler(proto))
m.HandleFunc(RDNSDomain, s.rdnsHandler(proto))
m.HandleFunc(".", s.notUsHandler(proto))
return m
}
s.udpSrv = &dns.Server{Addr: s.ListenAddr, Net: "udp", Handler: mux(protUDP)}
s.tcpSrv = &dns.Server{Addr: s.ListenAddr, Net: "tcp", Handler: mux(protTCP)}
return
}
// Start the DNS server
func (s *DNSServer) Start() error {
Info.Printf("Using mDNS on %v", s.Iface)
err := s.mdnsCli.Start(s.Iface)
CheckFatal(err)
err = s.mdnsSrv.Start(s.Iface)
CheckFatal(err)
s.listenersWg.Add(2)
go func() {
defer s.listenersWg.Done()
Debug.Printf("Listening for DNS on %s (UDP)", s.ListenAddr)
err = s.udpSrv.ListenAndServe()
CheckFatal(err)
Debug.Printf("DNS UDP server exiting...")
}()
go func() {
defer s.listenersWg.Done()
Debug.Printf("Listening for DNS on %s (TCP)", s.ListenAddr)
err = s.tcpSrv.ListenAndServe()
CheckFatal(err)
Debug.Printf("DNS TCP server exiting...")
}()
// Waiting for all goroutines to finish (otherwise they die as main routine dies)
s.listenersWg.Wait()
Info.Printf("WeaveDNS server exiting...")
return nil
}
// Return status string
func (s *DNSServer) Status() string {
var buf bytes.Buffer
fmt.Fprintln(&buf, "Local domain", s.Domain)
fmt.Fprintln(&buf, "Listen address", s.ListenAddr)
fmt.Fprintln(&buf, "mDNS interface", s.Iface)
fmt.Fprintln(&buf, "Fallback DNS config", s.Upstream)
fmt.Fprintf(&buf, "Zone database:\n%s", s.Zone)
return buf.String()
}
// Perform a graceful shutdown
func (s *DNSServer) Stop() error {
// Stop the listeners/handlers
if err := s.tcpSrv.Shutdown(); err != nil {
return err
}
if err := s.udpSrv.Shutdown(); err != nil {
return err
}
s.listenersWg.Wait()
// shutdown the mDNS server
s.mdnsSrv.Stop()
return nil
}
func (s *DNSServer) queryHandler(proto dnsProtocol) dns.HandlerFunc {
zoneLookup := func(lookup ZoneLookup, q *dns.Question, r *dns.Msg) (*dns.Msg, []ZoneRecord, error) {
ips, err := lookup.LookupName(q.Name)
if err != nil {
return nil, nil, err
}
return makeAddressReply(r, q, ips), ips, nil
}
fallback := func(w dns.ResponseWriter, r *dns.Msg) {
w.WriteMsg(makeDNSFailResponse(r))
}
return s.commonQueryHandler(proto, "Query", dns.TypeA, zoneLookup, fallback)
}
func (s *DNSServer) rdnsHandler(proto dnsProtocol) dns.HandlerFunc {
zoneLookup := func(lookup ZoneLookup, q *dns.Question, r *dns.Msg) (*dns.Msg, []ZoneRecord, error) {
names, err := lookup.LookupInaddr(q.Name)
if err != nil {
return nil, nil, err
}
return makePTRReply(r, q, names), names, nil
}
notUsHandler := s.notUsHandler(proto)
fallback := func(w dns.ResponseWriter, r *dns.Msg) {
Info.Printf("[dns msgid %d] -> sending to fallback server", r.MsgHdr.Id)
notUsHandler(w, r)
}
return s.commonQueryHandler(proto, "Reverse query", dns.TypePTR, zoneLookup, fallback)
}
func (s *DNSServer) commonQueryHandler(proto dnsProtocol, kind string, qtype uint16,
zoneLookup func(ZoneLookup, *dns.Question, *dns.Msg) (*dns.Msg, []ZoneRecord, error), fallback dns.HandlerFunc) dns.HandlerFunc {
return func(w dns.ResponseWriter, r *dns.Msg) {
q := r.Question[0]
Debug.Printf("[dns msgid %d] %s: %+v", r.MsgHdr.Id, kind, q)
maxLen := getMaxReplyLen(r, proto)
lookups := []ZoneLookup{s.Zone, s.mdnsCli}
reply, err := s.cache.Get(r, maxLen)
if err != nil {
if err == errNoLocalReplies {
Debug.Printf("[dns msgid %d] Cached 'no local replies' - skipping local lookup", r.MsgHdr.Id)
lookups = []ZoneLookup{s.Zone}
} else {
Debug.Printf("[dns msgid %d] Error from cache: %s", r.MsgHdr.Id, err)
w.WriteMsg(makeDNSFailResponse(r))
return
}
}
if reply != nil {
Debug.Printf("[dns msgid %d] Returning reply from cache: %s/%d answers",
r.MsgHdr.Id, dns.RcodeToString[reply.MsgHdr.Rcode], len(reply.Answer))
w.WriteMsg(reply)
return
}
// catch unsupported queries
if q.Qtype != qtype {
Debug.Printf("[dns msgid %d] Unsupported query type %s", r.MsgHdr.Id, dns.TypeToString[q.Qtype])
m := makeDNSFailResponse(r)
s.cache.Put(r, m, negLocalTTL, 0)
w.WriteMsg(m)
return
}
for _, lookup := range lookups {
if m, answers, err := zoneLookup(lookup, &q, r); err == nil {
m.Authoritative = true
Debug.Printf("[dns msgid %d] Caching response for type %s query for '%s': %s [code:%s]",
m.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name, answers, dns.RcodeToString[m.Rcode])
s.cache.Put(r, m, nullTTL, 0)
w.WriteMsg(m)
return
}
}
Info.Printf("[dns msgid %d] No results for type %s query for '%s' [caching no-local]",
r.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name)
s.cache.Put(r, nil, negLocalTTL, CacheNoLocalReplies)
fallback(w, r)
}
}
// When we receive a request for a name outside of our '.weave.local.'
// domain, ask the configured DNS server as a fallback.
func (s *DNSServer) notUsHandler(proto dnsProtocol) dns.HandlerFunc {
dnsClient := proto.GetNewClient(DefaultUDPBuflen)
return func(w dns.ResponseWriter, r *dns.Msg) {
maxLen := getMaxReplyLen(r, proto)
q := r.Question[0]
// create a request where we announce our max payload size
rcopy := r
rcopy.SetEdns0(uint16(maxLen), false)
Debug.Printf("[dns msgid %d] Fallback query: %+v [%s, max:%d bytes]", rcopy.MsgHdr.Id, q, proto, maxLen)
for _, server := range s.Upstream.Servers {
reply, _, err := dnsClient.Exchange(rcopy, fmt.Sprintf("%s:%s", server, s.Upstream.Port))
if err != nil {
Debug.Printf("[dns msgid %d] Network error trying %s (%s)",
r.MsgHdr.Id, server, err)
continue
}
if reply != nil && reply.Rcode != dns.RcodeSuccess {
Debug.Printf("[dns msgid %d] Failure reported by %s for query %s",
r.MsgHdr.Id, server, q.Name)
continue
}
Debug.Printf("[dns msgid %d] Given answer by %s for query %s",
r.MsgHdr.Id, server, q.Name)
w.WriteMsg(reply)
return
}
Warning.Printf("[dns msgid %d] Failed lookup for external name %s", r.MsgHdr.Id, q.Name)
w.WriteMsg(makeDNSFailResponse(r))
}
}
-301
View File
@@ -1,301 +0,0 @@
package nameserver
import (
"encoding/binary"
"fmt"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
wt "github.com/weaveworks/weave/testing"
"net"
"testing"
"time"
)
const (
testRDNSsuccess = "1.2.2.10.in-addr.arpa."
testRDNSfail = "4.3.2.1.in-addr.arpa."
testRDNSnonlocal = "8.8.8.8.in-addr.arpa."
testUDPBufSize = 16384
)
var (
testPort = 17625
)
func setupForTest(t *testing.T) {
// fail early if we cannot find a default multicast interface
multicast, err := LinkLocalMulticastListener(nil)
if err != nil {
t.Fatalf("Unable to create multicast listener: %s. No default multicast interface?", err)
}
multicast.Close()
testPort++
}
func TestUDPDNSServer(t *testing.T) {
setupForTest(t)
const (
containerID = "foobar"
successTestName = "test1.weave.local."
failTestName = "test2.weave.local."
nonLocalName = "weave.works."
testAddr1 = "10.2.2.1"
)
testCIDR1 := testAddr1 + "/24"
InitDefaultLogging(true)
var zone = NewZoneDb(DefaultLocalDomain)
ip, _, _ := net.ParseCIDR(testCIDR1)
zone.AddRecord(containerID, successTestName, ip)
fallbackHandler := func(w dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetReply(req)
if len(req.Question) == 1 {
q := req.Question[0]
if q.Name == nonLocalName && q.Qtype == dns.TypeMX {
m.Answer = make([]dns.RR, 1)
m.Answer[0] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: "mail." + nonLocalName}
} else if q.Name == nonLocalName && q.Qtype == dns.TypeANY {
m.Answer = make([]dns.RR, 512/len("mailn."+nonLocalName)+1)
for i := range m.Answer {
m.Answer[i] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: fmt.Sprintf("mail%d.%s", i, nonLocalName)}
}
} else if q.Name == testRDNSnonlocal && q.Qtype == dns.TypePTR {
m.Answer = make([]dns.RR, 1)
m.Answer[0] = &dns.PTR{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: 0}, Ptr: "ns1.google.com."}
} else if q.Name == testRDNSfail && q.Qtype == dns.TypePTR {
m.Rcode = dns.RcodeNameError
}
}
w.WriteMsg(m)
}
// Run another DNS server for fallback
s, fallbackAddr, err := runLocalUDPServer(t, "127.0.0.1:0", fallbackHandler)
wt.AssertNoErr(t, err)
defer s.Shutdown()
_, fallbackPort, err := net.SplitHostPort(fallbackAddr)
wt.AssertNoErr(t, err)
config := &dns.ClientConfig{Servers: []string{"127.0.0.1"}, Port: fallbackPort}
srv, err := NewDNSServer(DNSServerConfig{UpstreamCfg: config, Port: testPort}, zone, nil)
wt.AssertNoErr(t, err)
defer srv.Stop()
go srv.Start()
time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start
var r *dns.Msg
r = assertExchange(t, successTestName, dns.TypeA, 1, 1, 0)
wt.AssertType(t, r.Answer[0], (*dns.A)(nil), "DNS record")
wt.AssertEqualString(t, r.Answer[0].(*dns.A).A.String(), testAddr1, "IP address")
assertExchange(t, failTestName, dns.TypeA, 0, 0, dns.RcodeNameError)
r = assertExchange(t, testRDNSsuccess, dns.TypePTR, 1, 1, 0)
wt.AssertType(t, r.Answer[0], (*dns.PTR)(nil), "DNS record")
wt.AssertEqualString(t, r.Answer[0].(*dns.PTR).Ptr, successTestName, "IP address")
assertExchange(t, testRDNSfail, dns.TypePTR, 0, 0, dns.RcodeNameError)
// This should fail because we don't have information about MX records
assertExchange(t, successTestName, dns.TypeMX, 0, 0, dns.RcodeNameError)
// This non-local query for an MX record should succeed by being
// passed on to the fallback server
assertExchange(t, nonLocalName, dns.TypeMX, 1, -1, 0)
// Now ask a query that we expect to return a lot of data.
assertExchange(t, nonLocalName, dns.TypeANY, 5, -1, 0)
assertExchange(t, testRDNSnonlocal, dns.TypePTR, 1, -1, 0)
// Not testing MDNS functionality of server here (yet), since it
// needs two servers, each listening on its own address
}
func TestTCPDNSServer(t *testing.T) {
setupForTest(t)
const (
numAnswers = 512
nonLocalName = "weave.works."
)
dnsAddr := fmt.Sprintf("localhost:%d", testPort)
InitDefaultLogging(true)
var zone = NewZoneDb(DefaultLocalDomain)
// generate a list of `numAnswers` IP addresses
var addrs []ZoneRecord
bs := make([]byte, 4)
for i := 0; i < numAnswers; i++ {
binary.LittleEndian.PutUint32(bs, uint32(i))
addrs = append(addrs, Record{"", net.IPv4(bs[0], bs[1], bs[2], bs[3]), 0, 0, 0})
}
// handler for the fallback server: it will just return a very long response
fallbackUDPHandler := func(w dns.ResponseWriter, req *dns.Msg) {
maxLen := getMaxReplyLen(req, protUDP)
t.Logf("Fallback UDP server got asked: returning %d answers", numAnswers)
q := req.Question[0]
m := makeAddressReply(req, &q, addrs)
mLen := m.Len()
m.SetEdns0(uint16(maxLen), false)
if mLen > maxLen {
t.Logf("... truncated response (%d > %d)", mLen, maxLen)
m.Truncated = true
}
w.WriteMsg(m)
}
fallbackTCPHandler := func(w dns.ResponseWriter, req *dns.Msg) {
t.Logf("Fallback TCP server got asked: returning %d answers", numAnswers)
q := req.Question[0]
m := makeAddressReply(req, &q, addrs)
w.WriteMsg(m)
}
t.Logf("Running a DNS fallback server with UDP")
us, fallbackUDPAddr, err := runLocalUDPServer(t, "127.0.0.1:0", fallbackUDPHandler)
wt.AssertNoErr(t, err)
defer us.Shutdown()
_, fallbackPort, err := net.SplitHostPort(fallbackUDPAddr)
wt.AssertNoErr(t, err)
t.Logf("Starting another fallback server, with TCP, on the same port as the UDP server")
fallbackTCPAddr := fmt.Sprintf("127.0.0.1:%s", fallbackPort)
ts, fallbackTCPAddr, err := runLocalTCPServer(t, fallbackTCPAddr, fallbackTCPHandler)
wt.AssertNoErr(t, err)
defer ts.Shutdown()
t.Logf("Creating a WeaveDNS server instance, falling back to 127.0.0.1:%s", fallbackPort)
config := &dns.ClientConfig{Servers: []string{"127.0.0.1"}, Port: fallbackPort}
srv, err := NewDNSServer(DNSServerConfig{UpstreamCfg: config, Port: testPort}, zone, nil)
wt.AssertNoErr(t, err)
defer srv.Stop()
go srv.Start()
time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start
t.Logf("Creating a UDP and a TCP client")
uc := new(dns.Client)
uc.UDPSize = minUDPSize
tc := new(dns.Client)
tc.Net = "tcp"
t.Logf("Creating DNS query message")
m := new(dns.Msg)
m.RecursionDesired = true
m.SetQuestion(nonLocalName, dns.TypeA)
t.Logf("Checking the fallback server at %s returns a truncated response with UDP", fallbackUDPAddr)
r, _, err := uc.Exchange(m, fallbackUDPAddr)
t.Logf("Got response from fallback server (UDP) with %d answers", len(r.Answer))
t.Logf("Response:\n%+v\n", r)
wt.AssertNoErr(t, err)
wt.AssertTrue(t, r.MsgHdr.Truncated, "DNS truncated reponse flag")
wt.AssertNotEqualInt(t, len(r.Answer), numAnswers, "number of answers (UDP)")
t.Logf("Checking the WeaveDNS server at %s returns a truncated reponse with UDP", dnsAddr)
r, _, err = uc.Exchange(m, dnsAddr)
t.Logf("UDP Response:\n%+v\n", r)
wt.AssertNoErr(t, err)
wt.AssertNotNil(t, r, "response")
t.Logf("%d answers", len(r.Answer))
wt.AssertTrue(t, r.MsgHdr.Truncated, "DNS truncated reponse flag")
wt.AssertNotEqualInt(t, len(r.Answer), numAnswers, "number of answers (UDP)")
t.Logf("Checking the WeaveDNS server at %s does not return a truncated reponse with TCP", dnsAddr)
r, _, err = tc.Exchange(m, dnsAddr)
t.Logf("TCP Response:\n%+v\n", r)
wt.AssertNoErr(t, err)
wt.AssertNotNil(t, r, "response")
t.Logf("%d answers", len(r.Answer))
wt.AssertFalse(t, r.MsgHdr.Truncated, "DNS truncated response flag")
wt.AssertEqualInt(t, len(r.Answer), numAnswers, "number of answers (TCP)")
t.Logf("Checking the WeaveDNS server at %s does not return a truncated reponse with UDP with a bigger buffer", dnsAddr)
m.SetEdns0(testUDPBufSize, false)
r, _, err = uc.Exchange(m, dnsAddr)
t.Logf("UDP-large Response:\n%+v\n", r)
wt.AssertNoErr(t, err)
wt.AssertNotNil(t, r, "response")
t.Logf("%d answers", len(r.Answer))
wt.AssertNoErr(t, err)
wt.AssertFalse(t, r.MsgHdr.Truncated, "DNS truncated response flag")
wt.AssertEqualInt(t, len(r.Answer), numAnswers, "number of answers (UDP-long)")
}
//////////////////////////////////////////////////////////////////////////////
// perform a DNS query and assert the reply code, number or answers, etc
func assertExchange(t *testing.T, z string, ty uint16, minAnswers int, maxAnswers int, expErr int) *dns.Msg {
c := new(dns.Client)
c.UDPSize = testUDPBufSize
m := new(dns.Msg)
m.RecursionDesired = true
m.SetQuestion(z, ty)
m.SetEdns0(testUDPBufSize, false) // we don't want to play with truncation here...
r, _, err := c.Exchange(m, fmt.Sprintf("127.0.0.1:%d", testPort))
t.Logf("Response:\n%+v\n", r)
wt.AssertNoErr(t, err)
if minAnswers == 0 && maxAnswers == 0 {
wt.AssertStatus(t, r.Rcode, expErr, "DNS response code")
} else {
wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code")
}
answers := len(r.Answer)
if minAnswers >= 0 && answers < minAnswers {
wt.Fatalf(t, "Number of answers >= %d", minAnswers)
}
if maxAnswers >= 0 && answers > maxAnswers {
wt.Fatalf(t, "Number of answers <= %d", maxAnswers)
}
return r
}
// run a UDP fallback server
func runLocalUDPServer(t *testing.T, laddr string, handler dns.HandlerFunc) (*dns.Server, string, error) {
t.Logf("Starting fallback UDP server at %s", laddr)
pc, err := net.ListenPacket("udp", laddr)
if err != nil {
return nil, "", err
}
server := &dns.Server{PacketConn: pc, Handler: handler, ReadTimeout: 100 * time.Millisecond}
go func() {
server.ActivateAndServe()
pc.Close()
}()
t.Logf("Fallback UDP server listening at %s", pc.LocalAddr())
return server, pc.LocalAddr().String(), nil
}
// run a TCP fallback server
func runLocalTCPServer(t *testing.T, laddr string, handler dns.HandlerFunc) (*dns.Server, string, error) {
t.Logf("Starting fallback TCP server at %s", laddr)
laddrTCP, err := net.ResolveTCPAddr("tcp", laddr)
if err != nil {
return nil, "", err
}
l, err := net.ListenTCP("tcp", laddrTCP)
if err != nil {
return nil, "", err
}
server := &dns.Server{Listener: l, Handler: handler, ReadTimeout: 100 * time.Millisecond}
go func() {
server.ActivateAndServe()
l.Close()
}()
t.Logf("Fallback TCP server listening at %s", l.Addr().String())
return server, l.Addr().String(), nil
}
+43
View File
@@ -0,0 +1,43 @@
package nameserver
type Status struct {
Domain string
Address string
TTL uint32
Entries []EntryStatus
}
type EntryStatus struct {
Hostname string
Origin string
ContainerID string
Address string
Version int
Tombstone int64
}
func NewStatus(ns *Nameserver, dnsServer *DNSServer) *Status {
if dnsServer == nil {
return nil
}
ns.RLock()
defer ns.RUnlock()
var entryStatusSlice []EntryStatus
for _, entry := range ns.entries {
entryStatusSlice = append(entryStatusSlice, EntryStatus{
entry.Hostname,
entry.Origin.String(),
entry.ContainerID,
entry.Addr.String(),
entry.Version,
entry.Tombstone})
}
return &Status{
dnsServer.domain,
dnsServer.address,
dnsServer.ttl,
entryStatusSlice}
}
-153
View File
@@ -1,153 +0,0 @@
package nameserver
import (
"bytes"
"fmt"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"net"
"sync"
)
const (
RDNSDomain = "in-addr.arpa."
)
// +1 to also exclude a dot
var rdnsDomainLen = len(RDNSDomain) + 1
type Zone interface {
AddRecord(ident string, name string, ip net.IP) error
DeleteRecord(ident string, ip net.IP) error
DeleteRecordsFor(ident string) error
Domain() string
ZoneLookup
}
type dbRecord struct {
Ident string
Name string
IP net.IP
}
// Very simple data structure for now, with linear searching.
// TODO: make more sophisticated to improve performance.
type ZoneDb struct {
mx sync.RWMutex
recs []dbRecord
domain string
}
type LookupError string
func (ops LookupError) Error() string {
return "Unable to find " + string(ops)
}
type DuplicateError struct {
}
func (dup DuplicateError) Error() string {
return "Tried to add a duplicate entry"
}
func NewZoneDb(domain string) *ZoneDb {
return &ZoneDb{
domain: domain,
}
}
func (zone *ZoneDb) Domain() string {
return zone.domain
}
func (zone *ZoneDb) indexOf(match func(dbRecord) bool) int {
for i, r := range zone.recs {
if match(r) {
return i
}
}
return -1
}
func (zone *ZoneDb) String() string {
zone.mx.RLock()
defer zone.mx.RUnlock()
var buf bytes.Buffer
for _, r := range zone.recs {
fmt.Fprintf(&buf, "%.12s %s %v\n", r.Ident, r.IP, r.Name)
}
return buf.String()
}
func (zone *ZoneDb) LookupName(name string) ([]ZoneRecord, error) {
zone.mx.RLock()
defer zone.mx.RUnlock()
for _, r := range zone.recs {
if r.Name == name {
return []ZoneRecord{Record{r.Name, r.IP, 0, 0, 0}}, nil
}
}
return nil, LookupError(name)
}
func (zone *ZoneDb) LookupInaddr(inaddr string) ([]ZoneRecord, error) {
if revIP := net.ParseIP(inaddr[:len(inaddr)-rdnsDomainLen]); revIP != nil {
revIP4 := revIP.To4()
ip := []byte{revIP4[3], revIP4[2], revIP4[1], revIP4[0]}
Debug.Printf("[zonedb] Looking for address: %+v", ip)
zone.mx.RLock()
defer zone.mx.RUnlock()
for _, r := range zone.recs {
if r.IP.Equal(ip) {
return []ZoneRecord{Record{r.Name, r.IP, 0, 0, 0}}, nil
}
}
return nil, LookupError(inaddr)
}
Warning.Printf("[zonedb] Asked to reverse lookup %s", inaddr)
return nil, LookupError(inaddr)
}
func (zone *ZoneDb) AddRecord(ident string, name string, ip net.IP) error {
zone.mx.Lock()
defer zone.mx.Unlock()
fqdn := dns.Fqdn(name)
pred := func(r dbRecord) bool { return r.Name == fqdn && r.IP.Equal(ip) && r.Ident == ident }
if index := zone.indexOf(pred); index != -1 {
return DuplicateError{}
}
zone.recs = append(zone.recs, dbRecord{ident, fqdn, ip})
return nil
}
func (zone *ZoneDb) DeleteRecord(ident string, ip net.IP) error {
zone.mx.Lock()
defer zone.mx.Unlock()
pred := func(r dbRecord) bool { return r.Ident == ident && r.IP.Equal(ip) }
if index := zone.indexOf(pred); index != -1 {
zone.recs = append(zone.recs[:index], zone.recs[index+1:]...)
return nil
}
return LookupError(ident)
}
func (zone *ZoneDb) DeleteRecordsFor(ident string) error {
zone.mx.Lock()
defer zone.mx.Unlock()
w := 0 // write index
for _, r := range zone.recs {
if r.Ident != ident {
zone.recs[w] = r
w++
}
}
zone.recs = zone.recs[:w]
return nil
}
func (zone *ZoneDb) ContainerDied(ident string) error {
Info.Printf("[zonedb] Container %s down. Removing records", ident)
return zone.DeleteRecordsFor(ident)
}
-88
View File
@@ -1,88 +0,0 @@
package nameserver
import (
wt "github.com/weaveworks/weave/testing"
"net"
"testing"
)
func TestZone(t *testing.T) {
var (
containerID = "deadbeef"
otherContainerID = "cowjuice"
successTestName = "test1.weave."
testAddr1 = "10.2.2.1/24"
)
var zone = NewZoneDb(DefaultLocalDomain)
ip, _, _ := net.ParseCIDR(testAddr1)
err := zone.AddRecord(containerID, successTestName, ip)
wt.AssertNoErr(t, err)
// Add a few more records to make the job harder
err = zone.AddRecord("abcdef0123", "adummy.weave.", net.ParseIP("10.2.0.1"))
wt.AssertNoErr(t, err)
err = zone.AddRecord("0123abcdef", "zdummy.weave.", net.ParseIP("10.2.0.2"))
wt.AssertNoErr(t, err)
// Check that the address is now there.
foundIP, err := zone.LookupName(successTestName)
wt.AssertNoErr(t, err)
if !foundIP[0].IP().Equal(ip) {
t.Fatal("Unexpected result for", successTestName, foundIP)
}
// See if we can find the address by IP.
foundName, err := zone.LookupInaddr("1.2.2.10.in-addr.arpa.")
wt.AssertNoErr(t, err)
if foundName[0].Name() != successTestName {
t.Fatal("Unexpected result for", ip, foundName)
}
err = zone.AddRecord(containerID, successTestName, ip)
wt.AssertErrorType(t, err, (*DuplicateError)(nil), "duplicate add")
err = zone.AddRecord(otherContainerID, successTestName, ip)
// Delete the record for the original container
err = zone.DeleteRecord(containerID, ip)
wt.AssertNoErr(t, err)
_, err = zone.LookupName(successTestName)
wt.AssertNoErr(t, err)
err = zone.DeleteRecord(otherContainerID, ip)
wt.AssertNoErr(t, err)
// Check that the address is not there now.
_, err = zone.LookupName(successTestName)
wt.AssertErrorType(t, err, (*LookupError)(nil), "after deleting record")
// Delete a record that isn't there
err = zone.DeleteRecord(containerID, net.ParseIP("0.0.0.0"))
wt.AssertErrorType(t, err, (*LookupError)(nil), "when deleting record that doesn't exist")
}
func TestDeleteFor(t *testing.T) {
var (
id = "foobar"
name = "foo.weave."
addr1 = "10.2.2.3/24"
addr2 = "10.2.7.8/24"
)
zone := NewZoneDb(DefaultLocalDomain)
for _, addr := range []string{addr1, addr2} {
ip, _, _ := net.ParseCIDR(addr)
err := zone.AddRecord(id, name, ip)
wt.AssertNoErr(t, err)
}
_, err := zone.LookupName(name)
wt.AssertNoErr(t, err)
err = zone.DeleteRecordsFor(id)
_, err = zone.LookupName(name)
wt.AssertErrorType(t, err, (*LookupError)(nil), "after deleting records for ident")
}
+122
View File
@@ -0,0 +1,122 @@
package address
import (
"fmt"
"net"
"github.com/weaveworks/weave/common"
)
// 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 NewRange(start Address, size Offset) Range {
return Range{Start: start, End: Add(start, size)}
}
func (r Range) Size() Offset { return Subtract(r.End, r.Start) }
func (r Range) String() string { return fmt.Sprintf("[%s-%s)", r.Start, r.End) }
func (r Range) Overlaps(or Range) bool { return !(r.Start >= or.End || r.End <= or.Start) }
func (r Range) Contains(addr Address) bool { return addr >= r.Start && addr < r.End }
func (r Range) AsCIDRString() string {
prefixLen := 32
for size := r.Size(); size > 1; size = size / 2 {
if size%2 != 0 { // Size not a power of two; cannot be expressed as a CIDR.
return r.String()
}
prefixLen--
}
return CIDR{Start: r.Start, PrefixLen: prefixLen}.String()
}
type CIDR struct {
Start Address
PrefixLen int
}
func ParseIP(s string) (Address, error) {
if ip := net.ParseIP(s); ip != nil {
return FromIP4(ip), nil
}
return 0, &net.ParseError{Type: "IP Address", Text: s}
}
func ParseCIDR(s string) (Address, CIDR, error) {
if ip, ipnet, err := net.ParseCIDR(s); err != nil {
return 0, CIDR{}, err
} else if ipnet.IP.To4() == nil {
return 0, CIDR{}, &net.ParseError{Type: "Non-IPv4 address not supported", Text: s}
} else {
prefixLen, _ := ipnet.Mask.Size()
return FromIP4(ip), CIDR{Start: FromIP4(ipnet.IP), PrefixLen: prefixLen}, nil
}
}
func (cidr CIDR) Size() Offset { return 1 << uint(32-cidr.PrefixLen) }
func (cidr CIDR) Range() Range {
return NewRange(cidr.Start, cidr.Size())
}
func (cidr CIDR) HostRange() Range {
// Respect RFC1122 exclusions of first and last addresses
return NewRange(cidr.Start+1, cidr.Size()-2)
}
func (cidr CIDR) String() string {
return fmt.Sprintf("%s/%d", cidr.Start.String(), cidr.PrefixLen)
}
// 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) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("%q", addr.String())), nil
}
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
}
func (addr Address) Reverse() Address {
return ((addr >> 24) & 0xff) | // move byte 3 to byte 0
((addr << 8) & 0xff0000) | // move byte 1 to byte 2
((addr >> 8) & 0xff00) | // move byte 2 to byte 1
((addr << 24) & 0xff000000) // byte 0 to byte 3
}
+80 -8
View File
@@ -3,18 +3,53 @@ package net
import (
"fmt"
"net"
"time"
"syscall"
"github.com/vishvananda/netlink/nl"
)
func EnsureInterface(ifaceName string, wait int) (iface *net.Interface, err error) {
if iface, err = findInterface(ifaceName); err == nil || wait == 0 {
return
// Wait for an interface to come up.
func EnsureInterface(ifaceName string) (*net.Interface, error) {
s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK)
if err != nil {
return nil, err
}
for ; err != nil && wait > 0; wait-- {
time.Sleep(1 * time.Second)
iface, err = findInterface(ifaceName)
defer s.Close()
iface, err := ensureInterface(s, ifaceName)
if err != nil {
return nil, err
}
return
return iface, err
}
func ensureInterface(s *nl.NetlinkSocket, ifaceName string) (*net.Interface, error) {
if iface, err := findInterface(ifaceName); err == nil {
return iface, nil
}
netlinkReceiveUntil(s, matchIfName(ifaceName))
iface, err := findInterface(ifaceName)
return iface, err
}
// Wait for an interface to come up and have a route added to the multicast subnet.
// This matches the behaviour in 'weave attach', which is the only context in which
// we expect this to be called. If you change one, change the other to match.
func EnsureInterfaceAndMcastRoute(ifaceName string) (*net.Interface, error) {
s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK, syscall.RTNLGRP_IPV4_ROUTE)
if err != nil {
return nil, err
}
defer s.Close()
iface, err := ensureInterface(s, ifaceName)
if err != nil {
return nil, err
}
dest := net.IPv4(224, 0, 0, 0)
if CheckRouteExists(ifaceName, dest) {
return iface, err
}
netlinkReceiveUntil(s, matchRoute(ifaceName, dest))
return iface, err
}
func findInterface(ifaceName string) (iface *net.Interface, err error) {
@@ -26,3 +61,40 @@ func findInterface(ifaceName string) (iface *net.Interface, err error) {
}
return
}
func netlinkReceiveUntil(s *nl.NetlinkSocket, f func(syscall.NetlinkMessage) (bool, error)) error {
for {
msgs, err := s.Receive()
if err != nil {
return err
}
for _, m := range msgs {
if done, err := f(m); done {
return err
}
}
}
}
func matchIfName(ifaceName string) func(m syscall.NetlinkMessage) (bool, error) {
return func(m syscall.NetlinkMessage) (bool, error) {
switch m.Header.Type {
case syscall.RTM_NEWLINK: // receive this type for link 'up'
ifmsg := nl.DeserializeIfInfomsg(m.Data)
attrs, err := syscall.ParseNetlinkRouteAttr(&m)
if err != nil {
return true, err
}
name := ""
for _, attr := range attrs {
if attr.Attr.Type == syscall.IFA_LABEL {
name = string(attr.Value[:len(attr.Value)-1])
}
}
if ifaceName == name && ifmsg.Flags&syscall.IFF_UP != 0 {
return true, nil
}
}
return false, nil
}
}
+115
View File
@@ -0,0 +1,115 @@
package net
import (
"fmt"
"net"
"syscall"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl"
)
// A network is considered free if it does not overlap any existing
// routes on this host. This is the same approach taken by Docker.
func CheckNetworkFree(subnet *net.IPNet, ignoreIfaceNames map[string]struct{}) error {
return forEachRoute(ignoreIfaceNames, func(name string, route netlink.Route) error {
if route.Dst != nil && overlaps(route.Dst, subnet) {
return fmt.Errorf("Network %s overlaps with existing route %s on host.", subnet, route.Dst)
}
return nil
})
}
// Two networks overlap if the start-point of one is inside the other.
func overlaps(n1, n2 *net.IPNet) bool {
return n1.Contains(n2.IP) || n2.Contains(n1.IP)
}
// For a specific address, we only care if it is actually *inside* an
// existing route, because weave-local traffic never hits IP routing.
func CheckAddressOverlap(addr net.IP, ignoreIfaceNames map[string]struct{}) error {
return forEachRoute(ignoreIfaceNames, func(name string, route netlink.Route) error {
if route.Dst != nil && route.Dst.Contains(addr) {
return fmt.Errorf("Address %s overlaps with existing route %s on host.", addr, route.Dst)
}
return nil
})
}
func forEachRoute(ignoreIfaceNames map[string]struct{}, check func(name string, r netlink.Route) error) error {
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
if err != nil {
return err
}
for _, route := range routes {
link, err := netlink.LinkByIndex(route.LinkIndex)
if err == nil {
if _, found := ignoreIfaceNames[link.Attrs().Name]; found {
continue
}
}
if err := check(link.Attrs().Name, route); err != nil {
return err
}
}
return nil
}
func CheckRouteExists(ifaceName string, dest net.IP) bool {
found := false
forEachRoute(map[string]struct{}{}, func(name string, route netlink.Route) error {
if name == ifaceName && route.Dst.IP.Equal(dest) {
found = true
}
return nil
})
return found
}
func matchRoute(ifaceName string, dest net.IP) func(m syscall.NetlinkMessage) (bool, error) {
return func(m syscall.NetlinkMessage) (bool, error) {
switch m.Header.Type {
case syscall.RTM_NEWROUTE:
route, err := deserializeRoute(m.Data)
if err != nil {
return true, err
}
link, _ := netlink.LinkByIndex(route.LinkIndex)
if link.Attrs().Name == ifaceName && route.Dst.IP.Equal(dest) {
return true, nil
}
}
return false, nil
}
}
// This code proposed for addition to the vishvananda/netlink library
// deserializeRoute decodes a binary netlink message into a Route struct
func deserializeRoute(m []byte) (netlink.Route, error) {
route := netlink.Route{}
msg := nl.DeserializeRtMsg(m)
attrs, err := nl.ParseRouteAttr(m[msg.Len():])
if err != nil {
return route, err
}
route.Scope = netlink.Scope(msg.Scope)
native := nl.NativeEndian()
for _, attr := range attrs {
switch attr.Attr.Type {
case syscall.RTA_GATEWAY:
route.Gw = net.IP(attr.Value)
case syscall.RTA_PREFSRC:
route.Src = net.IP(attr.Value)
case syscall.RTA_DST:
route.Dst = &net.IPNet{
IP: attr.Value,
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(attr.Value)),
}
case syscall.RTA_OIF:
routeIndex := int(native.Uint32(attr.Value[0:4]))
route.LinkIndex = routeIndex
}
}
return route, nil
}
+25
View File
@@ -0,0 +1,25 @@
package net
import (
"net"
"testing"
"github.com/stretchr/testify/require"
)
func TestOverlap(t *testing.T) {
_, subnet1, _ := net.ParseCIDR("10.0.3.0/24")
_, subnet2, _ := net.ParseCIDR("10.0.4.0/24")
_, subnet3, _ := net.ParseCIDR("10.0.3.128/25")
_, subnet4, _ := net.ParseCIDR("10.0.3.192/25")
_, universe, _ := net.ParseCIDR("10.0.0.0/8")
require.False(t, overlaps(subnet1, subnet2))
require.False(t, overlaps(subnet2, subnet1))
require.True(t, overlaps(subnet1, subnet1))
require.True(t, overlaps(subnet1, subnet3))
require.True(t, overlaps(subnet1, subnet4))
require.False(t, overlaps(subnet2, subnet4))
require.False(t, overlaps(subnet4, subnet2))
require.True(t, overlaps(universe, subnet1))
require.True(t, overlaps(subnet1, universe))
}
+54
View File
@@ -0,0 +1,54 @@
/* netcheck: check whether a given network or address overlaps with any existing routes */
package main
import (
"fmt"
"net"
"os"
"github.com/docker/docker/pkg/mflag"
weavenet "github.com/weaveworks/weave/net"
)
func fatal(err error) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
type stringList map[string]struct{}
func (ss *stringList) Set(value string) error {
(*ss)[value] = struct{}{}
return nil
}
func (ss *stringList) String() string {
return fmt.Sprintf("%v", *ss)
}
func main() {
ignoreIfaceNames := make(stringList)
mflag.Var(&ignoreIfaceNames, []string{"-ignore-iface"}, "name of interface to ignore)")
mflag.Parse()
if len(mflag.Args()) < 1 {
fmt.Fprintln(os.Stderr, "usage: netcheck [--ignore-iface <iface-name>] network-cidr")
os.Exit(1)
}
cidrStr := mflag.Args()[0]
addr, ipnet, err := net.ParseCIDR(cidrStr)
if err != nil {
fatal(err)
}
if ipnet.IP.Equal(addr) {
err = weavenet.CheckNetworkFree(ipnet, ignoreIfaceNames)
} else {
err = weavenet.CheckAddressOverlap(addr, ignoreIfaceNames)
}
if err != nil {
fatal(err)
}
os.Exit(0)
}
File renamed without changes.
@@ -1,6 +1,11 @@
FROM gliderlabs/alpine
MAINTAINER Weaveworks Inc <help@weave.works>
LABEL works.weave.role=system
WORKDIR /home/weave
VOLUME /w
ENTRYPOINT ["/home/weave/sigproxy", "/home/weave/weave"]
RUN apk add --update \
curl \
@@ -9,16 +14,10 @@ RUN apk add --update \
iproute2 \
util-linux \
conntrack-tools \
bind-tools \
&& rm -rf /var/cache/apk/*
WORKDIR /home/weave
ADD ./weave /home/weave/
ADD ./sigproxy /home/weave/
ADD ./weaveproxy /home/weave/
ADD ./weavewait /home/weavewait/weavewait
ADD ./weave ./sigproxy ./weaveproxy /home/weave/
ADD ./netcheck ./weavehosts /usr/bin/
ADD ./weavewait /w/w
ADD ./docker.tgz /
VOLUME /home/weavewait
ENTRYPOINT ["/home/weave/sigproxy", "/home/weave/weave"]
+120
View File
@@ -0,0 +1,120 @@
/* weavehosts: rewrite /etc/hosts as necessary to put the weave address(es) at the top */
package main
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"net"
"os"
"sort"
"strings"
)
func main() {
args := os.Args
if len(args) < 4 {
fmt.Fprintln(os.Stderr, "usage: weavehosts hostpath hostname cidr [cidr ...]")
os.Exit(1)
}
checkErr(updateHosts(args[1], args[2], args[3:]))
os.Exit(0)
}
func checkErr(err error) {
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func updateHosts(path, hostname string, cidrs []string) error {
hosts, err := parseHosts(path)
if err != nil {
return err
}
// Remove existing ips pointing to our hostname
toRemove := []string{}
for ip, addrs := range hosts {
for _, addr := range addrs {
if addr == hostname {
toRemove = append(toRemove, ip)
break
}
}
}
for _, ip := range toRemove {
delete(hosts, ip)
}
// Add the weave ip(s)
for _, cidr := range cidrs {
ip, _, err := net.ParseCIDR(cidr)
checkErr(err)
ipStr := ip.String()
hosts[ipStr] = append(hosts[ipStr], hostname)
}
return writeHosts(path, hosts)
}
func parseHosts(path string) (map[string][]string, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
ips := map[string][]string{}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
// Remove any comments
if i := strings.IndexByte(line, '#'); i != -1 {
line = line[:i]
}
fields := strings.Fields(line)
if len(fields) > 0 {
ips[fields[0]] = append(ips[fields[0]], fields[1:]...)
}
}
if scanner.Err() != nil {
return nil, scanner.Err()
}
return ips, nil
}
func writeHosts(path string, contents map[string][]string) error {
ips := []string{}
for ip := range contents {
ips = append(ips, ip)
}
sort.Strings(ips)
buf := &bytes.Buffer{}
fmt.Fprintln(buf, "# modified by weave")
for _, ip := range ips {
if addrs := contents[ip]; len(addrs) > 0 {
fmt.Fprintf(buf, "%s\t%s\n", ip, strings.Join(uniqueStrs(addrs), " "))
}
}
return ioutil.WriteFile(path, buf.Bytes(), 644)
}
func uniqueStrs(s []string) []string {
m := map[string]struct{}{}
result := []string{}
for _, str := range s {
if _, ok := m[str]; !ok {
m[str] = struct{}{}
result = append(result, str)
}
}
sort.Strings(result)
return result
}
+66
View File
@@ -0,0 +1,66 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/docker/docker/pkg/mflag"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/common/mflagext"
"github.com/weaveworks/weave/proxy"
)
var (
version = "(unreleased version)"
)
func main() {
var (
justVersion bool
logLevel = "info"
c = proxy.Config{ListenAddrs: []string{}}
)
c.Version = version
mflag.BoolVar(&justVersion, []string{"#version", "-version"}, false, "print version and exit")
mflag.StringVar(&logLevel, []string{"-log-level"}, "info", "logging level (debug, info, warning, error)")
mflagext.ListVar(&c.ListenAddrs, []string{"H"}, nil, "addresses on which to listen")
mflag.StringVar(&c.HostnameFromLabel, []string{"-hostname-from-label"}, "", "Key of container label from which to obtain the container's hostname")
mflag.StringVar(&c.HostnameMatch, []string{"-hostname-match"}, "(.*)", "Regexp pattern to apply on container names (e.g. '^aws-[0-9]+-(.*)$')")
mflag.StringVar(&c.HostnameReplacement, []string{"-hostname-replacement"}, "$1", "Expression to generate hostnames based on matches from --hostname-match (e.g. 'my-app-$1')")
mflag.BoolVar(&c.RewriteInspect, []string{"-rewrite-inspect"}, false, "Rewrite 'inspect' calls to return the weave network settings (if attached)")
mflag.BoolVar(&c.NoDefaultIPAM, []string{"#-no-default-ipam", "-no-default-ipalloc"}, false, "do not automatically allocate addresses for containers without a WEAVE_CIDR")
mflag.BoolVar(&c.NoRewriteHosts, []string{"-no-rewrite-hosts"}, false, "do not automatically rewrite /etc/hosts. Use if you need the docker IP to remain in /etc/hosts")
mflag.StringVar(&c.TLSConfig.CACert, []string{"#tlscacert", "-tlscacert"}, "", "Trust certs signed only by this CA")
mflag.StringVar(&c.TLSConfig.Cert, []string{"#tlscert", "-tlscert"}, "", "Path to TLS certificate file")
mflag.BoolVar(&c.TLSConfig.Enabled, []string{"#tls", "-tls"}, false, "Use TLS; implied by --tls-verify")
mflag.StringVar(&c.TLSConfig.Key, []string{"#tlskey", "-tlskey"}, "", "Path to TLS key file")
mflag.BoolVar(&c.TLSConfig.Verify, []string{"#tlsverify", "-tlsverify"}, false, "Use TLS and verify the remote")
mflag.BoolVar(&c.WithDNS, []string{"-with-dns", "w"}, false, "instruct created containers to always use weaveDNS as their nameserver")
mflag.BoolVar(&c.WithoutDNS, []string{"-without-dns"}, false, "instruct created containers to never use weaveDNS as their nameserver")
mflag.Parse()
if justVersion {
fmt.Printf("weave proxy %s\n", version)
os.Exit(0)
}
if c.WithDNS && c.WithoutDNS {
Log.Fatalf("Cannot use both '--with-dns' and '--without-dns' flags")
}
SetLogLevel(logLevel)
Log.Infoln("weave proxy", version)
Log.Infoln("Command line arguments:", strings.Join(os.Args[1:], " "))
p, err := proxy.NewProxy(c)
if err != nil {
Log.Fatalf("Could not start proxy: %s", err)
}
go p.ListenAndServe()
SignalHandlerLoop()
}
+12
View File
@@ -0,0 +1,12 @@
package main
import (
"testing"
weavetest "github.com/weaveworks/weave/testing"
)
func TestMain(t *testing.T) {
weavetest.TrimTestArgs()
main()
}
+2 -1
View File
@@ -1,5 +1,6 @@
FROM scratch
MAINTAINER Weaveworks Inc <help@weave.works>
LABEL works.weave.role=system
WORKDIR /home/weave
ADD ./weaver /home/weave/
ENTRYPOINT ["/home/weave/weaver", "-wait", "20"]
ENTRYPOINT ["/home/weave/weaver"]
+198
View File
@@ -0,0 +1,198 @@
package main
import (
"encoding/json"
"fmt"
"github.com/gorilla/mux"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/ipam"
"github.com/weaveworks/weave/nameserver"
"github.com/weaveworks/weave/net/address"
weave "github.com/weaveworks/weave/router"
"net/http"
"strings"
"text/template"
)
var rootTemplate = template.New("root").Funcs(map[string]interface{}{
"countDNSEntries": func(entries []nameserver.EntryStatus) int {
count := 0
for _, entry := range entries {
if entry.Tombstone == 0 {
count++
}
}
return count
},
"printConnectionCounts": func(conns []weave.LocalConnectionStatus) string {
counts := make(map[string]int)
for _, conn := range conns {
counts[conn.State]++
}
return printCounts(counts, []string{"established", "pending", "retrying", "failed", "connecting"})
},
"printPeerConnectionCounts": func(peers []weave.PeerStatus) string {
counts := make(map[string]int)
for _, peer := range peers {
for _, conn := range peer.Connections {
if conn.Established {
counts["established"]++
} else {
counts["pending"]++
}
}
}
return printCounts(counts, []string{"established", "pending"})
},
"printState": func(enabled bool) string {
if enabled {
return "enabled"
}
return "disabled"
},
"trimSuffix": strings.TrimSuffix,
})
// Print counts in a specified order
func printCounts(counts map[string]int, keys []string) string {
var stringCounts []string
for _, key := range keys {
if count, ok := counts[key]; ok {
stringCounts = append(stringCounts, fmt.Sprintf("%d %s", count, key))
}
}
return strings.Join(stringCounts, ", ")
}
// Strip escaped newlines from template
func escape(template string) string {
return strings.Replace(template, "\\\n", "", -1)
}
// Define a named template panicking on error
func defTemplate(name string, text string) *template.Template {
return template.Must(rootTemplate.New(name).Parse(escape(text)))
}
var statusTemplate = defTemplate("status", `\
Version: {{.Version}}
Service: router
Protocol: {{.Router.Protocol}} \
{{if eq .Router.ProtocolMinVersion .Router.ProtocolMaxVersion}}\
{{.Router.ProtocolMaxVersion}}\
{{else}}\
{{.Router.ProtocolMinVersion}}..{{.Router.ProtocolMaxVersion}}\
{{end}}
Name: {{.Router.Name}}({{.Router.NickName}})
Encryption: {{printState .Router.Encryption}}
PeerDiscovery: {{printState .Router.PeerDiscovery}}
Targets: {{len .Router.Targets}}
Connections: {{len .Router.Connections}}{{with printConnectionCounts .Router.Connections}} ({{.}}){{end}}
Peers: {{len .Router.Peers}}{{with printPeerConnectionCounts .Router.Peers}} (with {{.}} connections between them){{end}}
{{if .IPAM}}\
Service: ipam
{{if .IPAM.Entries}}\
Consensus: achieved
{{else if .IPAM.Paxos}}\
Consensus: waiting (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}})
{{else}}\
Consensus: deferred
{{end}}\
Range: {{.IPAM.Range}}
DefaultSubnet: {{.IPAM.DefaultSubnet}}
{{end}}\
{{if .DNS}}\
Service: dns
Domain: {{.DNS.Domain}}
TTL: {{.DNS.TTL}}
Entries: {{countDNSEntries .DNS.Entries}}
{{end}}\
`)
var targetsTemplate = defTemplate("targetsTemplate", `\
{{range .Router.Targets}}{{.}}
{{end}}\
`)
var connectionsTemplate = defTemplate("connectionsTemplate", `\
{{range .Router.Connections}}\
{{if .Outbound}}->{{else}}<-{{end}} {{printf "%-21v" .Address}} {{printf "%-11v" .State}} {{.Info}}
{{end}}\
`)
var peersTemplate = defTemplate("peers", `\
{{range .Router.Peers}}\
{{.Name}}({{.NickName}})
{{range .Connections}}\
{{if .Outbound}}->{{else}}<-{{end}} {{printf "%-21v" .Address}} \
{{$nameNickName := printf "%v(%v)" .Name .NickName}}{{printf "%-32v" $nameNickName}} \
{{if .Established}}established{{else}}pending{{end}}
{{end}}\
{{end}}\
`)
var dnsEntriesTemplate = defTemplate("dnsEntries", `\
{{$domain := printf ".%v" .DNS.Domain}}\
{{range .DNS.Entries}}\
{{if eq .Tombstone 0}}\
{{$hostname := trimSuffix .Hostname $domain}}\
{{printf "%-12v" $hostname}} {{printf "%-15v" .Address}} {{printf "%12.12v" .ContainerID}} {{.Origin}}
{{end}}\
{{end}}\
`)
type WeaveStatus struct {
Version string
Router *weave.Status `json:"Router,omitempty"`
IPAM *ipam.Status `json:"IPAM,omitempty"`
DNS *nameserver.Status `json:"DNS,omitempty"`
}
func HandleHTTP(muxRouter *mux.Router, version string, router *weave.Router, allocator *ipam.Allocator, defaultSubnet address.CIDR, ns *nameserver.Nameserver, dnsserver *nameserver.DNSServer) {
status := func() WeaveStatus {
return WeaveStatus{
version,
weave.NewStatus(router),
ipam.NewStatus(allocator, defaultSubnet),
nameserver.NewStatus(ns, dnsserver)}
}
muxRouter.Methods("GET").Path("/report").Headers("Accept", "application/json").HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
json, _ := json.MarshalIndent(status(), "", " ")
w.Header().Set("Content-Type", "application/json")
w.Write(json)
})
muxRouter.Methods("GET").Path("/report").Queries("format", "{format}").HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
formatTemplate, err := template.New("format").Parse(mux.Vars(r)["format"])
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := formatTemplate.Execute(w, status()); err != nil {
http.Error(w, "error during template execution", http.StatusInternalServerError)
Log.Error(err)
}
})
defHandler := func(path string, template *template.Template) {
muxRouter.Methods("GET").Path(path).HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
if err := template.Execute(w, status()); err != nil {
http.Error(w, "error during template execution", http.StatusInternalServerError)
Log.Error(err)
}
})
}
defHandler("/status", statusTemplate)
defHandler("/status/targets", targetsTemplate)
defHandler("/status/connections", connectionsTemplate)
defHandler("/status/peers", peersTemplate)
defHandler("/status/dns", dnsEntriesTemplate)
}
+333
View File
@@ -0,0 +1,333 @@
package main
import (
"crypto/sha256"
"fmt"
"net"
"net/http"
_ "net/http/pprof"
"os"
"runtime"
"strings"
"time"
"github.com/davecheney/profile"
"github.com/docker/docker/pkg/mflag"
"github.com/gorilla/mux"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/common/docker"
"github.com/weaveworks/weave/ipam"
"github.com/weaveworks/weave/nameserver"
weavenet "github.com/weaveworks/weave/net"
"github.com/weaveworks/weave/net/address"
weave "github.com/weaveworks/weave/router"
)
var version = "(unreleased version)"
func main() {
procs := runtime.NumCPU()
// packet sniffing can block an OS thread, so we need one thread
// for that plus at least one more.
if procs < 2 {
procs = 2
}
runtime.GOMAXPROCS(procs)
var (
config weave.Config
justVersion bool
protocolMinVersion int
ifaceName string
routerName string
nickName string
password string
pktdebug bool
logLevel string
prof string
bufSzMB int
noDiscovery bool
httpAddr string
iprangeCIDR string
ipsubnetCIDR string
peerCount int
apiPath string
peers []string
noDNS bool
dnsDomain string
dnsListenAddress string
dnsTTL int
dnsClientTimeout time.Duration
dnsEffectiveListenAddress string
)
mflag.BoolVar(&justVersion, []string{"#version", "-version"}, false, "print version and exit")
mflag.IntVar(&config.Port, []string{"#port", "-port"}, weave.Port, "router port")
mflag.IntVar(&protocolMinVersion, []string{"-min-protocol-version"}, weave.ProtocolMinVersion, "minimum weave protocol version")
mflag.StringVar(&ifaceName, []string{"#iface", "-iface"}, "", "name of interface to capture/inject from (disabled if blank)")
mflag.StringVar(&routerName, []string{"#name", "-name"}, "", "name of router (defaults to MAC of interface)")
mflag.StringVar(&nickName, []string{"#nickname", "-nickname"}, "", "nickname of peer (defaults to hostname)")
mflag.StringVar(&password, []string{"#password", "-password"}, "", "network password")
mflag.StringVar(&logLevel, []string{"-log-level"}, "info", "logging level (debug, info, warning, error)")
mflag.BoolVar(&pktdebug, []string{"#pktdebug", "#-pktdebug", "-pkt-debug"}, false, "enable per-packet debug logging")
mflag.StringVar(&prof, []string{"#profile", "-profile"}, "", "enable profiling and write profiles to given path")
mflag.IntVar(&config.ConnLimit, []string{"#connlimit", "#-connlimit", "-conn-limit"}, 30, "connection limit (0 for unlimited)")
mflag.BoolVar(&noDiscovery, []string{"#nodiscovery", "#-nodiscovery", "-no-discovery"}, false, "disable peer discovery")
mflag.IntVar(&bufSzMB, []string{"#bufsz", "-bufsz"}, 8, "capture buffer size in MB")
mflag.StringVar(&httpAddr, []string{"#httpaddr", "#-httpaddr", "-http-addr"}, fmt.Sprintf(":%d", weave.HTTPPort), "address to bind HTTP interface to (disabled if blank, absolute path indicates unix domain socket)")
mflag.StringVar(&iprangeCIDR, []string{"#iprange", "#-iprange", "-ipalloc-range"}, "", "IP address range reserved for automatic allocation, in CIDR notation")
mflag.StringVar(&ipsubnetCIDR, []string{"#ipsubnet", "#-ipsubnet", "-ipalloc-default-subnet"}, "", "subnet to allocate within by default, in CIDR notation")
mflag.IntVar(&peerCount, []string{"#initpeercount", "#-initpeercount", "-init-peer-count"}, 0, "number of peers in network (for IP address allocation)")
mflag.StringVar(&apiPath, []string{"#api", "-api"}, "unix:///var/run/docker.sock", "Path to Docker API socket")
mflag.BoolVar(&noDNS, []string{"-no-dns"}, false, "disable DNS server")
mflag.StringVar(&dnsDomain, []string{"-dns-domain"}, nameserver.DefaultDomain, "local domain to server requests for")
mflag.StringVar(&dnsListenAddress, []string{"-dns-listen-address"}, nameserver.DefaultListenAddress, "address to listen on for DNS requests")
mflag.IntVar(&dnsTTL, []string{"-dns-ttl"}, nameserver.DefaultTTL, "TTL for DNS request from our domain")
mflag.DurationVar(&dnsClientTimeout, []string{"-dns-fallback-timeout"}, nameserver.DefaultClientTimeout, "timeout for fallback DNS requests")
mflag.StringVar(&dnsEffectiveListenAddress, []string{"-dns-effective-listen-address"}, "", "address DNS will actually be listening, after Docker port mapping")
// crude way of detecting that we probably have been started in a
// container, with `weave launch` --> suppress misleading paths in
// mflags error messages.
if os.Args[0] == "/home/weave/weaver" { // matches the Dockerfile ENTRYPOINT
os.Args[0] = "weave"
mflag.CommandLine.Init("weave", mflag.ExitOnError)
}
mflag.Parse()
peers = mflag.Args()
SetLogLevel(logLevel)
if justVersion {
fmt.Printf("weave router %s\n", version)
os.Exit(0)
}
Log.Println("Command line options:", options())
Log.Println("Command line peers:", peers)
if protocolMinVersion < weave.ProtocolMinVersion || protocolMinVersion > weave.ProtocolMaxVersion {
Log.Fatalf("--min-protocol-version must be in range [%d,%d]", weave.ProtocolMinVersion, weave.ProtocolMaxVersion)
}
config.ProtocolMinVersion = byte(protocolMinVersion)
var err error
if ifaceName != "" {
config.Iface, err = weavenet.EnsureInterface(ifaceName)
if err != nil {
Log.Fatal(err)
}
}
if routerName == "" {
if config.Iface == nil {
Log.Fatal("Either an interface must be specified with --iface or a name with -name")
}
routerName = config.Iface.HardwareAddr.String()
}
name, err := weave.PeerNameFromUserInput(routerName)
if err != nil {
Log.Fatal(err)
}
if nickName == "" {
nickName, err = os.Hostname()
if err != nil {
Log.Fatal(err)
}
}
if password == "" {
password = os.Getenv("WEAVE_PASSWORD")
}
if password == "" {
Log.Println("Communication between peers is unencrypted.")
} else {
config.Password = []byte(password)
Log.Println("Communication between peers is encrypted.")
}
if prof != "" {
p := *profile.CPUProfile
p.ProfilePath = prof
p.NoShutdownHook = true
defer profile.Start(&p).Stop()
}
config.BufSz = bufSzMB * 1024 * 1024
config.LogFrame = logFrameFunc(pktdebug)
config.PeerDiscovery = !noDiscovery
router := weave.NewRouter(config, name, nickName)
Log.Println("Our name is", router.Ourself)
dockerCli, err := docker.NewClient(apiPath)
if err != nil {
Log.Fatal("Unable to start docker client: ", err)
}
var allocator *ipam.Allocator
var defaultSubnet address.CIDR
if iprangeCIDR != "" {
allocator, defaultSubnet = createAllocator(router, iprangeCIDR, ipsubnetCIDR, determineQuorum(peerCount, peers))
if err = dockerCli.AddObserver(allocator); err != nil {
Log.Fatal("Unable to start watcher", err)
}
} else if peerCount > 0 {
Log.Fatal("--init-peer-count flag specified without --ipalloc-range")
}
var (
ns *nameserver.Nameserver
dnsserver *nameserver.DNSServer
)
if !noDNS {
ns = nameserver.New(router.Ourself.Peer.Name, router.Peers, dockerCli, dnsDomain)
ns.SetGossip(router.NewGossip("nameserver", ns))
if err = dockerCli.AddObserver(ns); err != nil {
Log.Fatal("Unable to start watcher", err)
}
ns.Start()
defer ns.Stop()
dnsserver, err = nameserver.NewDNSServer(ns, dnsDomain, dnsListenAddress,
dnsEffectiveListenAddress, uint32(dnsTTL), dnsClientTimeout)
if err != nil {
Log.Fatal("Unable to start dns server: ", err)
}
dnsserver.ActivateAndServe()
defer dnsserver.Stop()
}
router.Start()
if errors := router.ConnectionMaker.InitiateConnections(peers, false); len(errors) > 0 {
Log.Fatal(ErrorMessages(errors))
}
// The weave script always waits for a status call to succeed,
// so there is no point in doing "weave launch --http-addr ''".
// This is here to support stand-alone use of weaver.
if httpAddr != "" {
muxRouter := mux.NewRouter()
if allocator != nil {
allocator.HandleHTTP(muxRouter, defaultSubnet, dockerCli)
}
if ns != nil {
ns.HandleHTTP(muxRouter)
}
router.HandleHTTP(muxRouter)
HandleHTTP(muxRouter, version, router, allocator, defaultSubnet, ns, dnsserver)
http.Handle("/", muxRouter)
go listenAndServeHTTP(httpAddr, muxRouter)
}
SignalHandlerLoop(router)
}
func options() map[string]string {
options := make(map[string]string)
mflag.Visit(func(f *mflag.Flag) {
value := f.Value.String()
name := canonicalName(f)
if name == "password" {
value = "<elided>"
}
options[name] = value
})
return options
}
func canonicalName(f *mflag.Flag) string {
for _, n := range f.Names {
if n[0] != '#' {
return strings.TrimLeft(n, "#-")
}
}
return ""
}
func logFrameFunc(debug bool) weave.LogFrameFunc {
if !debug {
return func(prefix string, frame []byte, dec *weave.EthernetDecoder) {}
}
return func(prefix string, frame []byte, dec *weave.EthernetDecoder) {
h := fmt.Sprintf("%x", sha256.Sum256(frame))
parts := []interface{}{prefix, len(frame), "bytes (", h, ")"}
if dec != nil {
parts = append(parts, dec.Eth.SrcMAC, "->", dec.Eth.DstMAC)
if dec.DF() {
parts = append(parts, "(DF)")
}
}
Log.Println(parts...)
}
}
func parseAndCheckCIDR(cidrStr string) address.CIDR {
_, cidr, err := address.ParseCIDR(cidrStr)
if err != nil {
Log.Fatal(err)
}
if cidr.Size() < ipam.MinSubnetSize {
Log.Fatalf("Allocation range smaller than minimum size %d: %s", ipam.MinSubnetSize, cidrStr)
}
return cidr
}
func createAllocator(router *weave.Router, ipRangeStr string, defaultSubnetStr string, quorum uint) (*ipam.Allocator, address.CIDR) {
ipRange := parseAndCheckCIDR(ipRangeStr)
defaultSubnet := ipRange
if defaultSubnetStr != "" {
defaultSubnet = parseAndCheckCIDR(defaultSubnetStr)
if !ipRange.Range().Overlaps(defaultSubnet.Range()) {
Log.Fatalf("IP address allocation default subnet %s does not overlap with allocation range %s", defaultSubnet, ipRange)
}
}
allocator := ipam.NewAllocator(router.Ourself.Peer.Name, router.Ourself.Peer.UID, router.Ourself.Peer.NickName, ipRange.Range(), quorum)
allocator.SetInterfaces(router.NewGossip("IPallocation", allocator))
allocator.Start()
return allocator, defaultSubnet
}
// Pick a quorum size heuristically based on the number of peer
// addresses passed.
func determineQuorum(initPeerCountFlag int, peers []string) uint {
if initPeerCountFlag > 0 {
return uint(initPeerCountFlag/2 + 1)
}
// Guess a suitable quorum size based on the list of peer
// addresses. The peer list might or might not contain an
// address for this peer, so the conservative assumption is
// that it doesn't. The list might contain multiple addresses
// that resolve to the same peer, in which case the quorum
// might be larger than it needs to be. But the user can
// specify it explicitly if that becomes a problem.
clusterSize := uint(len(peers) + 1)
quorum := clusterSize/2 + 1
Log.Println("Assuming quorum size of", quorum)
return quorum
}
func listenAndServeHTTP(httpAddr string, muxRouter *mux.Router) {
protocol := "tcp"
if strings.HasPrefix(httpAddr, "/") {
os.Remove(httpAddr) // in case it's there from last time
protocol = "unix"
}
l, err := net.Listen(protocol, httpAddr)
if err != nil {
Log.Fatal("Unable to create http listener socket: ", err)
}
err = http.Serve(l, nil)
if err != nil {
Log.Fatal("Unable to create http server", err)
}
}
+12
View File
@@ -0,0 +1,12 @@
package main
import (
"testing"
weavetest "github.com/weaveworks/weave/testing"
)
func TestMain(t *testing.T) {
weavetest.TrimTestArgs()
main()
}
+40
View File
@@ -0,0 +1,40 @@
package main
import (
"errors"
"fmt"
"os"
"os/exec"
"syscall"
weavenet "github.com/weaveworks/weave/net"
)
var (
ErrNoCommandSpecified = errors.New("No command specified")
)
func main() {
var (
args = os.Args[1:]
)
_, err := weavenet.EnsureInterfaceAndMcastRoute("ethwe")
checkErr(err)
if len(args) == 0 {
checkErr(ErrNoCommandSpecified)
}
binary, err := exec.LookPath(args[0])
checkErr(err)
checkErr(syscall.Exec(binary, args, os.Environ()))
}
func checkErr(err error) {
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
+123
View File
@@ -0,0 +1,123 @@
// Based on net/http/internal
package proxy
import (
"bufio"
"bytes"
"errors"
"io"
"io/ioutil"
"strconv"
)
var (
ErrLineTooLong = errors.New("header line too long")
ErrInvalidChunkLength = errors.New("invalid byte in chunk length")
)
// Unlike net/http/internal.chunkedReader, this has an interface where we can
// handle individual chunks. The interface is based on database/sql.Rows.
func NewChunkedReader(r io.Reader) *ChunkedReader {
br, ok := r.(*bufio.Reader)
if !ok {
br = bufio.NewReader(r)
}
return &ChunkedReader{r: br}
}
type ChunkedReader struct {
r *bufio.Reader
chunk *io.LimitedReader
err error
buf [2]byte
}
// Next prepares the next chunk for reading. It returns true on success, or
// false if there is no next chunk or an error happened while preparing
// it. Err should be consulted to distinguish between the two cases.
//
// Every call to Chunk, even the first one, must be preceded by a call to Next.
//
// Calls to Next will discard any unread bytes in the current Chunk.
func (cr *ChunkedReader) Next() bool {
if cr.err != nil {
return false
}
// Check the termination of the previous chunk
if cr.chunk != nil {
// Make sure the remainder is drained, in case the user of this quit
// reading early.
if _, cr.err = io.Copy(ioutil.Discard, cr.chunk); cr.err != nil {
return false
}
// Check the next two bytes after the chunk are \r\n
if _, cr.err = io.ReadFull(cr.r, cr.buf[:2]); cr.err != nil {
return false
}
if cr.buf[0] != '\r' || cr.buf[1] != '\n' {
cr.err = errors.New("malformed chunked encoding")
return false
}
} else {
cr.chunk = &io.LimitedReader{R: cr.r}
}
// Setup the next chunk
if n := cr.beginChunk(); n > 0 {
cr.chunk.N = int64(n)
} else if cr.err == nil {
cr.err = io.EOF
}
return cr.err == nil
}
// Chunk returns the io.Reader of the current chunk. On each call, this returns
// the same io.Reader for a given chunk.
func (cr *ChunkedReader) Chunk() io.Reader {
return cr.chunk
}
// Err returns the error, if any, that was encountered during iteration.
func (cr *ChunkedReader) Err() error {
if cr.err == io.EOF {
return nil
}
return cr.err
}
func (cr *ChunkedReader) beginChunk() uint64 {
var (
line []byte
n uint64
)
// chunk-size CRLF
line, cr.err = readLine(cr.r)
if cr.err != nil {
return 0
}
n, cr.err = strconv.ParseUint(string(line), 16, 64)
if cr.err != nil {
cr.err = ErrInvalidChunkLength
}
return n
}
// Read a line of bytes (up to \n) from b.
// Give up if the line exceeds the buffer size.
// The returned bytes are a pointer into storage in
// the bufio, so they are only valid until the next bufio read.
func readLine(b *bufio.Reader) (p []byte, err error) {
if p, err = b.ReadSlice('\n'); err != nil {
// We always know when EOF is coming.
// If the caller asked for a line, there should be a line.
if err == io.EOF {
err = io.ErrUnexpectedEOF
} else if err == bufio.ErrBufferFull {
err = ErrLineTooLong
}
return nil, err
}
return bytes.TrimRight(p, " \t\n\r"), nil
}
+174
View File
@@ -0,0 +1,174 @@
// Based on net/http/internal
package proxy
import (
"bytes"
"io"
"io/ioutil"
"strconv"
"strings"
"testing"
)
func TestChunk(t *testing.T) {
r := NewChunkedReader(bytes.NewBufferString(
"7\r\nhello, \r\n17\r\nworld! 0123456789abcdef\r\n0\r\n",
))
assertNextChunk(t, r, "hello, ")
assertNextChunk(t, r, "world! 0123456789abcdef")
assertNoMoreChunks(t, r)
}
func TestIncompleteReadOfChunk(t *testing.T) {
r := NewChunkedReader(bytes.NewBufferString(
"7\r\nhello, \r\n17\r\nworld! 0123456789abcdef\r\n0\r\n",
))
// Incomplete read of first chunk
{
if !r.Next() {
t.Fatalf("Expected chunk, but ran out early: %v", r.Err())
}
if r.Err() != nil {
t.Fatalf("Error reading chunk: %q", r.Err())
}
// Read just 2 bytes
buf := make([]byte, 2)
if _, err := io.ReadFull(r.Chunk(), buf[:2]); err != nil {
t.Fatalf("Error reading first bytes of chunk: %q", err)
}
if buf[0] != 'h' || buf[1] != 'e' {
t.Fatalf("Unexpected first 2 bytes of chunk: %q", buf)
}
}
assertNextChunk(t, r, "world! 0123456789abcdef")
assertNoMoreChunks(t, r)
}
func TestMalformedChunks(t *testing.T) {
r := NewChunkedReader(bytes.NewBufferString(
"7\r\nhello, GARBAGEBYTES17\r\nworld! 0123456789abcdef\r\n0\r\n",
))
assertNextChunk(t, r, "hello, ")
assertError(t, r, "malformed chunked encoding")
}
type charReader byte
// Read an infinite sequence of some char
func (r *charReader) Read(p []byte) (int, error) {
b := byte(*r)
for i := range p {
p[i] = b
}
return len(p), nil
}
func TestLargeChunks(t *testing.T) {
var expected int64 = 1024 * 1024
chars := charReader('a')
r := NewChunkedReader(io.MultiReader(
strings.NewReader(strconv.FormatInt(expected, 16)+"\r\n"),
&io.LimitedReader{N: expected, R: &chars},
strings.NewReader("\r\n0\r\n"),
))
if !r.Next() {
t.Fatalf("Expected chunk, but ran out early: %v", r.Err())
}
if r.Err() != nil {
t.Fatalf("Error reading chunk: %q", r.Err())
}
n, err := io.Copy(ioutil.Discard, r.Chunk())
if n != expected {
t.Errorf("chunk reader read %q; want %q", n, expected)
}
if err != nil {
t.Fatalf("reading chunk: %v", err)
}
assertNoMoreChunks(t, r)
}
func TestInvalidChunkSize(t *testing.T) {
r := NewChunkedReader(bytes.NewBufferString(
"foobar\r\nhello, \r\n0\r\n",
))
assertError(t, r, "invalid byte in chunk length")
}
func TestChunkSizeLineTooLong(t *testing.T) {
var (
maxLineLength = 4096
chunkSize string
)
for i := 0; i < maxLineLength; i++ {
chunkSize = chunkSize + "0"
}
chunkSize = chunkSize + "7"
r := NewChunkedReader(bytes.NewBufferString(
chunkSize + "\r\nhello, \r\n0\r\n",
))
assertError(t, r, "header line too long")
}
func TestBytesAfterLastChunkAreIgnored(t *testing.T) {
r := NewChunkedReader(bytes.NewBufferString(
"7\r\nhello, \r\n0\r\nGARBAGEBYTES",
))
assertNextChunk(t, r, "hello, ")
assertNoMoreChunks(t, r)
}
func assertNextChunk(t *testing.T, r *ChunkedReader, expected string) {
if !r.Next() {
t.Fatalf("Expected chunk, but ran out early: %v", r.Err())
}
if r.Err() != nil {
t.Fatalf("Error reading chunk: %q", r.Err())
}
data, err := ioutil.ReadAll(r.Chunk())
if string(data) != expected {
t.Errorf("chunk reader read %q; want %q", data, expected)
}
if err != nil {
t.Logf(`data: %q`, data)
t.Fatalf("reading chunk: %v", err)
}
}
func assertError(t *testing.T, r *ChunkedReader, e string) {
if r.Next() {
t.Errorf("Expected failure when reading chunks, but got one")
}
if r.Err() == nil || r.Err().Error() != e {
t.Errorf("chunk reader errored %q; want %q", r.Err(), e)
}
data, err := ioutil.ReadAll(r.Chunk())
if len(data) != 0 {
t.Errorf("chunk should have been empty. got %q", data)
}
if err != nil {
t.Logf(`data: %q`, data)
t.Errorf("reading chunk: %v", err)
}
if r.Next() {
t.Errorf("Expected no more chunks, but found too many")
}
}
func assertNoMoreChunks(t *testing.T, r *ChunkedReader) {
if r.Next() {
t.Errorf("Expected no more chunks, but found too many")
}
if r.Err() != nil {
t.Errorf("Expected no error, but found: %q", r.Err())
}
}
-170
View File
@@ -1,170 +0,0 @@
package proxy
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
type client struct {
interceptor
Dial func() (net.Conn, error)
}
func newClient(dial func() (net.Conn, error), i interceptor) *client {
return &client{
interceptor: i,
Dial: dial,
}
}
func (c *client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := c.InterceptRequest(r); err != nil {
_, isNoSuchContainer := err.(*docker.NoSuchContainer)
switch {
case isNoSuchContainer:
http.Error(w, err.Error(), http.StatusNotFound)
case err == docker.ErrNoSuchImage:
http.Error(w, err.Error(), http.StatusNotFound)
default:
http.Error(w, "Unable to intercept request", http.StatusInternalServerError)
Warning.Print("Error intercepting request: ", err)
}
return
}
conn, err := c.Dial()
if err != nil {
http.Error(w, "Could not connect to target", http.StatusInternalServerError)
Warning.Print(err)
return
}
client := httputil.NewClientConn(conn, nil)
defer client.Close()
resp, err := client.Do(r)
if err != nil && err != httputil.ErrPersistEOF {
http.Error(w, fmt.Sprintf("Could not make request to target: %v", err), http.StatusInternalServerError)
Warning.Print("Error forwarding request: ", err)
return
}
err = c.InterceptResponse(resp)
if err != nil {
http.Error(w, "Unable to intercept response", http.StatusInternalServerError)
Warning.Print("Error intercepting response: ", err)
return
}
hdr := w.Header()
for k, vs := range resp.Header {
for _, v := range vs {
hdr.Add(k, v)
}
}
Debug.Printf("Response from target: %s %v", resp.Status, w.Header())
if resp.Header.Get("Content-Type") == "application/vnd.docker.raw-stream" {
doRawStream(w, resp, client)
} else if resp.TransferEncoding != nil && resp.TransferEncoding[0] == "chunked" {
doChunkedResponse(w, resp, client)
} else {
w.WriteHeader(resp.StatusCode)
if _, err := io.Copy(w, resp.Body); err != nil {
Warning.Print(err)
}
}
}
func doRawStream(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) {
down, downBuf, up, rem, err := hijack(w, client)
if err != nil {
http.Error(w, "Unable to hijack connection for raw stream mode", http.StatusInternalServerError)
return
}
defer down.Close()
defer up.Close()
if _, err := down.Write([]byte("HTTP/1.1 200 OK\n")); err != nil {
Warning.Print(err)
return
}
if err := resp.Header.Write(down); err != nil {
Warning.Print(err)
return
}
if _, err := down.Write([]byte("\n")); err != nil {
Warning.Print(err)
return
}
upDone := make(chan struct{})
downDone := make(chan struct{})
go copyStream(down, io.MultiReader(rem, up), upDone)
go copyStream(up, downBuf, downDone)
<-upDone
<-downDone
}
func copyStream(dst io.Writer, src io.Reader, done chan struct{}) {
defer close(done)
if _, err := io.Copy(dst, src); err != nil {
Warning.Print(err)
}
if c, ok := dst.(interface {
CloseWrite() error
}); ok {
if err := c.CloseWrite(); err != nil {
Warning.Printf("Error closing connection: %s", err)
}
}
}
func doChunkedResponse(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) {
// Because we can't go back to request/response after we
// hijack the connection, we need to close it and make the
// client open another.
w.Header().Add("Connection", "close")
w.WriteHeader(resp.StatusCode)
down, _, up, rem, err := hijack(w, client)
if err != nil {
http.Error(w, "Unable to hijack response stream for chunked response", http.StatusInternalServerError)
return
}
defer up.Close()
defer down.Close()
// Copy the chunked response body to downstream,
// stopping at the end of the chunked section.
rawResponseBody := io.MultiReader(rem, up)
if _, err := io.Copy(ioutil.Discard, httputil.NewChunkedReader(io.TeeReader(rawResponseBody, down))); err != nil {
http.Error(w, "Error copying chunked response body", http.StatusInternalServerError)
return
}
resp.Trailer.Write(down)
// a chunked response ends with a CRLF
down.Write([]byte("\r\n"))
}
func hijack(w http.ResponseWriter, client *httputil.ClientConn) (down net.Conn, downBuf *bufio.ReadWriter, up net.Conn, rem io.Reader, err error) {
hj, ok := w.(http.Hijacker)
if !ok {
err = errors.New("Unable to cast to Hijack")
return
}
down, downBuf, err = hj.Hijack()
if err != nil {
return
}
up, rem = client.Hijack()
return
}
+91 -16
View File
@@ -3,9 +3,12 @@ package proxy
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"os/exec"
"regexp"
"strings"
@@ -15,26 +18,41 @@ import (
)
var (
containerIDRegexp = regexp.MustCompile("^/v[0-9\\.]*/containers/([^/]*)/.*")
weaveWaitEntrypoint = []string{"/home/weavewait/weavewait"}
containerIDRegexp = regexp.MustCompile("^(/v[0-9\\.]*)?/containers/([^/]*)/.*")
weaveWaitEntrypoint = []string{"/w/w"}
)
func callWeave(args ...string) ([]byte, error) {
func callWeave(args ...string) ([]byte, []byte, error) {
args = append([]string{"--local"}, args...)
Debug.Print("Calling weave", args)
Log.Debug("Calling weave", args)
cmd := exec.Command("./weave", args...)
cmd.Env = []string{"PROCFS=/hostproc", "PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}
out, err := cmd.CombinedOutput()
return out, err
cmd.Env = []string{
"PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"PROCFS=/hostproc",
}
if bridge := os.Getenv("DOCKER_BRIDGE"); bridge != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("DOCKER_BRIDGE=%s", bridge))
}
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
return stdout.Bytes(), stderr.Bytes(), err
}
func weaveCIDRsFromConfig(config *docker.Config) ([]string, bool) {
for _, e := range config.Env {
if strings.HasPrefix(e, "WEAVE_CIDR=") {
return strings.Fields(e[11:]), true
}
func unmarshalRequestBody(r *http.Request, target interface{}) error {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
return nil, false
if err := r.Body.Close(); err != nil {
return err
}
r.Body = ioutil.NopCloser(bytes.NewReader(body))
d := json.NewDecoder(bytes.NewReader(body))
d.UseNumber() // don't want large numbers in scientific format
return d.Decode(&target)
}
func marshalRequestBody(r *http.Request, body interface{}) error {
@@ -47,18 +65,75 @@ func marshalRequestBody(r *http.Request, body interface{}) error {
return nil
}
func unmarshalResponseBody(r *http.Response, target interface{}) error {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
if err := r.Body.Close(); err != nil {
return err
}
r.Body = ioutil.NopCloser(bytes.NewReader(body))
d := json.NewDecoder(bytes.NewReader(body))
d.UseNumber() // don't want large numbers in scientific format
return d.Decode(&target)
}
func marshalResponseBody(r *http.Response, body interface{}) error {
newBody, err := json.Marshal(body)
if err != nil {
return err
}
r.Body = ioutil.NopCloser(bytes.NewReader(newBody))
r.ContentLength = int64(len(newBody))
// Stop it being chunked, because that hangs
r.TransferEncoding = nil
return nil
}
func inspectContainerInPath(client *docker.Client, path string) (*docker.Container, error) {
subs := containerIDRegexp.FindStringSubmatch(path)
if subs == nil {
err := fmt.Errorf("No container id found in request with path %s", path)
Warning.Println(err)
Log.Warningln(err)
return nil, err
}
containerID := subs[1]
containerID := subs[2]
container, err := client.InspectContainer(containerID)
if err != nil {
Warning.Printf("Error inspecting container %s: %v", containerID, err)
Log.Warningf("Error inspecting container %s: %v", containerID, err)
}
return container, err
}
func weaveContainerIPs(containerID string) (mac string, ips []net.IP, nets []*net.IPNet, err error) {
stdout, stderr, err := callWeave("ps", containerID)
if err != nil || len(stderr) > 0 {
err = errors.New(string(stderr))
return
}
if len(stdout) <= 0 {
return
}
fields := strings.Fields(string(stdout))
if len(fields) < 2 {
return
}
mac = fields[1]
var ip net.IP
var ipnet *net.IPNet
for _, cidr := range fields[2:] {
ip, ipnet, err = net.ParseCIDR(cidr)
if err != nil {
return
}
ips = append(ips, ip)
nets = append(nets, ipnet)
}
return
}
+117 -38
View File
@@ -1,21 +1,26 @@
package proxy
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/nameserver"
"github.com/weaveworks/weave/router"
)
type createContainerInterceptor struct {
client *docker.Client
withDNS bool
dockerBridgeIP string
}
const MaxDockerHostname = 64
var (
ErrNoCommandSpecified = errors.New("No command specified")
)
type createContainerInterceptor struct{ proxy *Proxy }
type createContainerRequestBody struct {
*docker.Config
@@ -23,43 +28,72 @@ type createContainerRequestBody struct {
MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
}
// ErrNoSuchImage replaces docker.NoSuchImage, which does not contain the image
// name, which in turn breaks docker clients post 1.7.0 since they expect the
// image name to be present in errors.
type ErrNoSuchImage struct {
Name string
}
func (err *ErrNoSuchImage) Error() string {
return "No such image: " + err.Name
}
func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
r.Body.Close()
container := createContainerRequestBody{}
if err := json.Unmarshal(body, &container); err != nil {
if err := unmarshalRequestBody(r, &container); err != nil {
return err
}
if cidrs, ok := weaveCIDRsFromConfig(container.Config); ok {
Info.Printf("Creating container with WEAVE_CIDR \"%s\"", strings.Join(cidrs, " "))
container.HostConfig.VolumesFrom = append(container.HostConfig.VolumesFrom, "weaveproxy")
if cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig); err != nil {
Log.Infof("Leaving container alone because %s", err)
} else {
Log.Infof("Creating container with WEAVE_CIDR \"%s\"", strings.Join(cidrs, " "))
if container.HostConfig == nil {
container.HostConfig = &docker.HostConfig{}
}
if container.Config == nil {
container.Config = &docker.Config{}
}
i.addWeaveWaitVolume(container.HostConfig)
if err := i.setWeaveWaitEntrypoint(container.Config); err != nil {
return err
}
if err := i.setWeaveDNS(&container); err != nil {
hostname := r.URL.Query().Get("name")
if i.proxy.Config.HostnameFromLabel != "" {
if labelValue, ok := container.Labels[i.proxy.Config.HostnameFromLabel]; ok {
hostname = labelValue
}
}
hostname = i.proxy.hostnameMatchRegexp.ReplaceAllString(hostname, i.proxy.HostnameReplacement)
if err := i.setWeaveDNS(&container, hostname); err != nil {
return err
}
}
newBody, err := json.Marshal(container)
if err != nil {
return err
return marshalRequestBody(r, container)
}
r.Body = ioutil.NopCloser(bytes.NewReader(newBody))
r.ContentLength = int64(len(newBody))
return nil
}
func (i *createContainerInterceptor) addWeaveWaitVolume(hostConfig *docker.HostConfig) {
var binds []string
for _, bind := range hostConfig.Binds {
s := strings.Split(bind, ":")
if len(s) >= 2 && s[1] == "/w" {
continue
}
binds = append(binds, bind)
}
hostConfig.Binds = append(binds, fmt.Sprintf("%s:/w:ro", i.proxy.weaveWaitVolume))
}
func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container *docker.Config) error {
if len(container.Entrypoint) == 0 {
image, err := i.client.InspectImage(container.Image)
if err != nil {
image, err := i.proxy.client.InspectImage(container.Image)
if err == docker.ErrNoSuchImage {
return &ErrNoSuchImage{container.Image}
} else if err != nil {
return err
}
@@ -72,30 +106,75 @@ func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container *docker.Co
}
}
container.Entrypoint = append(weaveWaitEntrypoint, container.Entrypoint...)
if len(container.Entrypoint) == 0 && len(container.Cmd) == 0 {
return ErrNoCommandSpecified
}
if len(container.Entrypoint) == 0 || container.Entrypoint[0] != weaveWaitEntrypoint[0] {
container.Entrypoint = append(weaveWaitEntrypoint, container.Entrypoint...)
}
return nil
}
func configHasEntrypoint(c *docker.Config) bool {
return c != nil && len(c.Entrypoint) > 0
}
func configHasCmd(c *docker.Config) bool {
return c != nil && len(c.Cmd) > 0
}
func (i *createContainerInterceptor) setWeaveDNS(container *createContainerRequestBody) error {
if !i.withDNS {
func (i *createContainerInterceptor) setWeaveDNS(container *createContainerRequestBody, name string) error {
if i.proxy.WithoutDNS {
return nil
}
container.HostConfig.DNS = append(container.HostConfig.DNS, i.dockerBridgeIP)
dnsDomain, dnsRunning := i.getDNSDomain()
if !(dnsRunning || i.proxy.WithDNS) {
return nil
}
container.HostConfig.DNS = append(container.HostConfig.DNS, i.proxy.dockerBridgeIP)
if container.Hostname == "" && name != "" {
// Strip trailing period because it's unusual to see it used on the end of a host name
trimmedDNSDomain := strings.TrimSuffix(dnsDomain, ".")
if len(name)+1+len(trimmedDNSDomain) > MaxDockerHostname {
Log.Warningf("Container name [%s] too long to be used as hostname", name)
} else {
container.Hostname = name
container.Domainname = trimmedDNSDomain
}
}
if len(container.HostConfig.DNSSearch) == 0 {
container.HostConfig.DNSSearch = []string{"."}
if container.Hostname == "" {
container.HostConfig.DNSSearch = []string{dnsDomain}
} else {
container.HostConfig.DNSSearch = []string{"."}
}
}
return nil
}
func (i *createContainerInterceptor) getDNSDomain() (domain string, running bool) {
domain = nameserver.DefaultDomain
weaveContainer, err := i.proxy.client.InspectContainer("weave")
if err != nil ||
weaveContainer.NetworkSettings == nil ||
weaveContainer.NetworkSettings.IPAddress == "" {
return
}
url := fmt.Sprintf("http://%s:%d/domain", weaveContainer.NetworkSettings.IPAddress, router.HTTPPort)
resp, err := http.Get(url)
if err != nil || resp.StatusCode != http.StatusOK {
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
return string(b), true
}
func (i *createContainerInterceptor) InterceptResponse(r *http.Response) error {
return nil
}
+13 -25
View File
@@ -1,8 +1,6 @@
package proxy
import (
"encoding/json"
"io/ioutil"
"net/http"
"strings"
@@ -10,43 +8,33 @@ import (
. "github.com/weaveworks/weave/common"
)
type createExecInterceptor struct {
client *docker.Client
}
type createExecRequestBody struct {
*docker.Config
HostConfig *docker.HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"`
MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
}
type createExecInterceptor struct{ proxy *Proxy }
func (i *createExecInterceptor) InterceptRequest(r *http.Request) error {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
r.Body.Close()
options := docker.CreateExecOptions{}
if err := json.Unmarshal(body, &options); err != nil {
if err := unmarshalRequestBody(r, &options); err != nil {
return err
}
container, err := inspectContainerInPath(i.client, r.URL.Path)
container, err := inspectContainerInPath(i.proxy.client, r.URL.Path)
if err != nil {
return err
}
if cidrs, ok := weaveCIDRsFromConfig(container.Config); ok {
Info.Printf("Exec in container %s with WEAVE_CIDR \"%s\"", container.ID, strings.Join(cidrs, " "))
options.Cmd = append(weaveWaitEntrypoint, options.Cmd...)
if _, hasWeaveWait := container.Volumes["/w"]; !hasWeaveWait {
return nil
}
if err := marshalRequestBody(r, options); err != nil {
return err
cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig)
if err != nil {
Log.Infof("Leaving container %s alone because %s", container.ID, err)
return nil
}
return nil
Log.Infof("Exec in container %s with WEAVE_CIDR \"%s\"", container.ID, strings.Join(cidrs, " "))
options.Cmd = append(weaveWaitEntrypoint, options.Cmd...)
return marshalRequestBody(r, options)
}
func (i *createExecInterceptor) InterceptResponse(r *http.Response) error {
+51
View File
@@ -0,0 +1,51 @@
package proxy
import (
"net/http"
. "github.com/weaveworks/weave/common"
)
type inspectContainerInterceptor struct{ proxy *Proxy }
func (i *inspectContainerInterceptor) InterceptRequest(r *http.Request) error {
return nil
}
func (i *inspectContainerInterceptor) InterceptResponse(r *http.Response) error {
if !i.proxy.RewriteInspect || r.StatusCode != 200 {
return nil
}
container := map[string]interface{}{}
if err := unmarshalResponseBody(r, &container); err != nil {
return err
}
if err := updateContainerNetworkSettings(container); err != nil {
Log.Warningf("Inspecting container %s failed: %s", container["Id"], err)
}
return marshalResponseBody(r, container)
}
func updateContainerNetworkSettings(container map[string]interface{}) error {
containerID, err := lookupString(container, "Id")
if err != nil {
return err
}
mac, ips, nets, err := weaveContainerIPs(containerID)
if err != nil || len(ips) == 0 {
return err
}
networkSettings, err := lookupObject(container, "NetworkSettings")
if err != nil {
return err
}
networkSettings["MacAddress"] = mac
networkSettings["IPAddress"] = ips[0].String()
networkSettings["IPPrefixLen"], _ = nets[0].Mask.Size()
return nil
}
+35
View File
@@ -0,0 +1,35 @@
package proxy
import (
"net/http"
. "github.com/weaveworks/weave/common"
)
type inspectExecInterceptor struct{ proxy *Proxy }
func (i *inspectExecInterceptor) InterceptRequest(r *http.Request) error {
return nil
}
func (i *inspectExecInterceptor) InterceptResponse(r *http.Response) error {
if !i.proxy.RewriteInspect {
return nil
}
exec := map[string]interface{}{}
if err := unmarshalResponseBody(r, &exec); err != nil {
return err
}
container, err := lookupObject(exec, "Container")
if err != nil {
return err
}
if err := updateContainerNetworkSettings(container); err != nil {
Log.Warningf("Inspecting exec %s failed: %s", exec["Id"], err)
}
return marshalResponseBody(r, exec)
}
+46
View File
@@ -0,0 +1,46 @@
package proxy
import (
"fmt"
)
type UnmarshalWrongTypeError struct {
Field, Expected string
Got interface{}
}
func (e *UnmarshalWrongTypeError) Error() string {
return fmt.Sprintf("Wrong type for %s field, expected %s, but got %T", e.Field, e.Expected, e.Got)
}
// For parsing json objects. Look up (or create) a child object in the given map.
func lookupObject(m map[string]interface{}, key string) (map[string]interface{}, error) {
iface, ok := m[key]
if !ok || iface == nil {
result := map[string]interface{}{}
m[key] = result
return result, nil
}
result, ok := iface.(map[string]interface{})
if !ok {
return nil, &UnmarshalWrongTypeError{key, "object", iface}
}
return result, nil
}
// For parsing json objects. Look up (or create) a string in the given map.
func lookupString(m map[string]interface{}, key string) (string, error) {
iface, ok := m[key]
if !ok || iface == nil {
return "", nil
}
result, ok := iface.(string)
if !ok {
return "", &UnmarshalWrongTypeError{key, "string", iface}
}
return result, nil
}
+219 -42
View File
@@ -1,85 +1,262 @@
package proxy
import (
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"os"
"regexp"
"strings"
"syscall"
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
var (
containerCreateRegexp = regexp.MustCompile("/v[0-9\\.]*/containers/create")
containerStartRegexp = regexp.MustCompile("^/v[0-9\\.]*/containers/[^/]*/(re)?start$")
execCreateRegexp = regexp.MustCompile("^/v[0-9\\.]*/containers/[^/]*/exec$")
const (
defaultCaFile = "ca.pem"
defaultKeyFile = "key.pem"
defaultCertFile = "cert.pem"
dockerSock = "/var/run/docker.sock"
dockerSockUnix = "unix://" + dockerSock
)
type Proxy struct {
Dial func() (net.Conn, error)
client *docker.Client
withDNS bool
dockerBridgeIP string
withIPAM bool
var (
containerCreateRegexp = dockerAPIEndpoint("containers/create")
containerStartRegexp = dockerAPIEndpoint("containers/[^/]*/(re)?start")
containerInspectRegexp = dockerAPIEndpoint("containers/[^/]*/json")
execCreateRegexp = dockerAPIEndpoint("containers/[^/]*/exec")
execInspectRegexp = dockerAPIEndpoint("exec/[^/]*/json")
ErrWeaveCIDRNone = errors.New("the container was created with the '-e WEAVE_CIDR=none' option")
ErrNoDefaultIPAM = errors.New("the container was created without specifying an IP address with '-e WEAVE_CIDR=...' and the proxy was started with the '--no-default-ipalloc' option")
)
func dockerAPIEndpoint(endpoint string) *regexp.Regexp {
return regexp.MustCompile("^(/v[0-9\\.]*)?/" + endpoint + "$")
}
func NewProxy(targetURL string, withDNS, withIPAM bool) (*Proxy, error) {
u, err := url.Parse(targetURL)
type Config struct {
HostnameFromLabel string
HostnameMatch string
HostnameReplacement string
ListenAddrs []string
RewriteInspect bool
NoDefaultIPAM bool
NoRewriteHosts bool
TLSConfig TLSConfig
Version string
WithDNS bool
WithoutDNS bool
}
type Proxy struct {
Config
client *docker.Client
dockerBridgeIP string
hostnameMatchRegexp *regexp.Regexp
weaveWaitVolume string
}
func NewProxy(c Config) (*Proxy, error) {
p := &Proxy{Config: c}
if err := p.TLSConfig.loadCerts(); err != nil {
Log.Fatalf("Could not configure tls for proxy: %s", err)
}
// We pin the protocol version to 1.15 (which corresponds to
// Docker 1.3.x; the earliest version supported by weave) in order
// to insulate ourselves from breaking changes to the API, as
// happened in 1.20 (Docker 1.8.0) when the presentation of
// volumes changed in `inspect`.
client, err := docker.NewVersionedClient(dockerSockUnix, "1.15")
if err != nil {
return nil, err
}
p.client = client
var dockerBridgeIP []byte
if withDNS {
dockerBridgeIP, err = callWeave("docker-bridge-ip")
if !p.WithoutDNS {
dockerBridgeIP, stderr, err := callWeave("docker-bridge-ip")
if err != nil {
return nil, err
return nil, fmt.Errorf(string(stderr))
}
p.dockerBridgeIP = string(dockerBridgeIP)
}
client, err := docker.NewClient(targetURL)
p.hostnameMatchRegexp, err = regexp.Compile(c.HostnameMatch)
if err != nil {
err := fmt.Errorf("Incorrect hostname match '%s': %s", c.HostnameMatch, err.Error())
return nil, err
}
targetAddr := ""
switch u.Scheme {
case "tcp":
targetAddr = u.Host
case "unix":
targetAddr = u.Path
if err = p.findWeaveWaitVolume(); err != nil {
return nil, err
}
return &Proxy{
Dial: func() (net.Conn, error) {
return net.Dial(u.Scheme, targetAddr)
},
client: client,
withDNS: withDNS,
dockerBridgeIP: string(dockerBridgeIP),
withIPAM: withIPAM,
}, nil
return p, nil
}
func (proxy *Proxy) Dial() (net.Conn, error) {
return net.Dial("unix", dockerSock)
}
func (proxy *Proxy) findWeaveWaitVolume() error {
container, err := proxy.client.InspectContainer("weaveproxy")
if err != nil {
return fmt.Errorf("Could not find the weavewait volume: %s", err)
}
if container.Volumes == nil {
return fmt.Errorf("Could not find the weavewait volume")
}
volume, ok := container.Volumes["/w"]
if !ok {
return fmt.Errorf("Could not find the weavewait volume")
}
proxy.weaveWaitVolume = volume
return nil
}
func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Info.Printf("%s %s", r.Method, r.URL)
Log.Infof("%s %s", r.Method, r.URL)
path := r.URL.Path
var i interceptor
switch {
case containerCreateRegexp.MatchString(path):
proxy.serveWithInterceptor(&createContainerInterceptor{proxy.client, proxy.withDNS, proxy.dockerBridgeIP}, w, r)
i = &createContainerInterceptor{proxy}
case containerStartRegexp.MatchString(path):
proxy.serveWithInterceptor(&startContainerInterceptor{proxy.client, proxy.withDNS, proxy.withIPAM}, w, r)
i = &startContainerInterceptor{proxy}
case containerInspectRegexp.MatchString(path):
i = &inspectContainerInterceptor{proxy}
case execCreateRegexp.MatchString(path):
proxy.serveWithInterceptor(&createExecInterceptor{proxy.client}, w, r)
case strings.HasPrefix(path, "/weave"):
w.WriteHeader(http.StatusOK)
i = &createExecInterceptor{proxy}
case execInspectRegexp.MatchString(path):
i = &inspectExecInterceptor{proxy}
default:
proxy.serveWithInterceptor(&nullInterceptor{}, w, r)
i = &nullInterceptor{}
}
proxy.Intercept(i, w, r)
}
func (proxy *Proxy) ListenAndServe() {
listeners := []net.Listener{}
addrs := []string{}
for _, addr := range proxy.ListenAddrs {
listener, normalisedAddr, err := proxy.listen(addr)
if err != nil {
Log.Fatalf("Cannot listen on %s: %s", addr, err)
}
listeners = append(listeners, listener)
addrs = append(addrs, normalisedAddr)
}
for _, addr := range addrs {
Log.Infoln("proxy listening on", addr)
}
errs := make(chan error)
for _, listener := range listeners {
go func(listener net.Listener) {
errs <- (&http.Server{Handler: proxy}).Serve(listener)
}(listener)
}
for range listeners {
err := <-errs
if err != nil {
Log.Fatalf("Serve failed: %s", err)
}
}
}
func (proxy *Proxy) serveWithInterceptor(i interceptor, w http.ResponseWriter, r *http.Request) {
newClient(proxy.Dial, i).ServeHTTP(w, r)
func copyOwnerAndPermissions(from, to string) error {
stat, err := os.Stat(from)
if err != nil {
return err
}
if err = os.Chmod(to, stat.Mode()); err != nil {
return err
}
moreStat, ok := stat.Sys().(*syscall.Stat_t)
if !ok {
return nil
}
if err = os.Chown(to, int(moreStat.Uid), int(moreStat.Gid)); err != nil {
return err
}
return nil
}
func (proxy *Proxy) listen(protoAndAddr string) (net.Listener, string, error) {
var (
listener net.Listener
err error
proto, addr string
)
if protoAddrParts := strings.SplitN(protoAndAddr, "://", 2); len(protoAddrParts) == 2 {
proto, addr = protoAddrParts[0], protoAddrParts[1]
} else if strings.HasPrefix(protoAndAddr, "/") {
proto, addr = "unix", protoAndAddr
} else {
proto, addr = "tcp", protoAndAddr
}
switch proto {
case "tcp":
listener, err = net.Listen(proto, addr)
if err != nil {
return nil, "", err
}
if proxy.TLSConfig.enabled() {
listener = tls.NewListener(listener, proxy.TLSConfig.Config)
}
case "unix":
// remove socket from last invocation
if err := os.Remove(addr); err != nil && !os.IsNotExist(err) {
return nil, "", err
}
listener, err = net.Listen(proto, addr)
if err != nil {
return nil, "", err
}
if err = copyOwnerAndPermissions(dockerSock, addr); err != nil {
return nil, "", err
}
default:
Log.Fatalf("Invalid protocol format: %q", proto)
}
return listener, fmt.Sprintf("%s://%s", proto, addr), nil
}
func (proxy *Proxy) weaveCIDRsFromConfig(config *docker.Config, hostConfig *docker.HostConfig) ([]string, error) {
netMode := ""
if hostConfig != nil {
netMode = hostConfig.NetworkMode
}
if netMode == "host" || strings.HasPrefix(netMode, "container:") {
return nil, fmt.Errorf("the container was created with the '--net=%s'", netMode)
}
for _, e := range config.Env {
if strings.HasPrefix(e, "WEAVE_CIDR=") {
if e[11:] == "none" {
return nil, ErrWeaveCIDRNone
}
return strings.Fields(e[11:]), nil
}
}
if proxy.NoDefaultIPAM {
return nil, ErrNoDefaultIPAM
}
return nil, nil
}
+167
View File
@@ -0,0 +1,167 @@
package proxy
import (
"bufio"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
"sync"
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
func (proxy *Proxy) Intercept(i interceptor, w http.ResponseWriter, r *http.Request) {
if err := i.InterceptRequest(r); err != nil {
switch err.(type) {
case *docker.NoSuchContainer:
http.Error(w, err.Error(), http.StatusNotFound)
case *ErrNoSuchImage:
http.Error(w, err.Error(), http.StatusNotFound)
default:
http.Error(w, err.Error(), http.StatusInternalServerError)
Log.Warning("Error intercepting request: ", err)
}
return
}
conn, err := proxy.Dial()
if err != nil {
http.Error(w, "Could not connect to target", http.StatusInternalServerError)
Log.Warning(err)
return
}
client := httputil.NewClientConn(conn, nil)
defer client.Close()
resp, err := client.Do(r)
if err != nil && err != httputil.ErrPersistEOF {
http.Error(w, fmt.Sprintf("Could not make request to target: %v", err), http.StatusInternalServerError)
Log.Warning("Error forwarding request: ", err)
return
}
err = i.InterceptResponse(resp)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
Log.Warning("Error intercepting response: ", err)
return
}
hdr := w.Header()
for k, vs := range resp.Header {
for _, v := range vs {
hdr.Add(k, v)
}
}
Log.Debugf("Response from target: %s %v", resp.Status, w.Header())
if resp.Header.Get("Content-Type") == "application/vnd.docker.raw-stream" {
doRawStream(w, resp, client)
} else if resp.TransferEncoding != nil && resp.TransferEncoding[0] == "chunked" {
doChunkedResponse(w, resp, client)
} else {
w.WriteHeader(resp.StatusCode)
if _, err := io.Copy(w, resp.Body); err != nil {
Log.Warning(err)
}
}
}
func doRawStream(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) {
down, downBuf, up, remaining, err := hijack(w, client)
if err != nil {
http.Error(w, "Unable to hijack connection for raw stream mode", http.StatusInternalServerError)
return
}
defer down.Close()
defer up.Close()
if _, err := down.Write([]byte("HTTP/1.1 200 OK\n")); err != nil {
Log.Warning(err)
return
}
if err := resp.Header.Write(down); err != nil {
Log.Warning(err)
return
}
if _, err := down.Write([]byte("\n")); err != nil {
Log.Warning(err)
return
}
var wg sync.WaitGroup
wg.Add(2)
go copyStream(down, io.MultiReader(remaining, up), &wg)
go copyStream(up, downBuf, &wg)
wg.Wait()
}
type closeWriter interface {
CloseWrite() error
}
func copyStream(dst io.WriteCloser, src io.Reader, wg *sync.WaitGroup) {
defer wg.Done()
if _, err := io.Copy(dst, src); err != nil {
Log.Warning(err)
}
var err error
if c, ok := dst.(closeWriter); ok {
err = c.CloseWrite()
} else {
err = dst.Close()
}
if err != nil {
Log.Warningf("Error closing connection: %s", err)
}
}
type writeFlusher interface {
io.Writer
http.Flusher
}
func doChunkedResponse(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) {
wf, ok := w.(writeFlusher)
if !ok {
http.Error(w, "Error forwarding chunked response body: flush not available", http.StatusInternalServerError)
return
}
w.WriteHeader(resp.StatusCode)
up, remaining := client.Hijack()
defer up.Close()
var err error
chunks := NewChunkedReader(io.MultiReader(remaining, up))
for chunks.Next() && err == nil {
_, err = io.Copy(wf, chunks.Chunk())
wf.Flush()
}
if err == nil {
err = chunks.Err()
}
if err != nil {
Log.Errorf("Error forwarding chunked response body: %s", err)
}
}
func hijack(w http.ResponseWriter, client *httputil.ClientConn) (down net.Conn, downBuf *bufio.ReadWriter, up net.Conn, remaining io.Reader, err error) {
hj, ok := w.(http.Hijacker)
if !ok {
err = errors.New("Unable to cast to Hijack")
return
}
down, downBuf, err = hj.Hijack()
if err != nil {
return
}
up, remaining = client.Hijack()
return
}
+21 -14
View File
@@ -1,40 +1,47 @@
package proxy
import (
"errors"
"net/http"
"strings"
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
type startContainerInterceptor struct {
client *docker.Client
withDNS bool
withIPAM bool
}
type startContainerInterceptor struct{ proxy *Proxy }
func (i *startContainerInterceptor) InterceptRequest(r *http.Request) error {
return nil
}
func (i *startContainerInterceptor) InterceptResponse(r *http.Response) error {
container, err := inspectContainerInPath(i.client, r.Request.URL.Path)
if r.StatusCode < 200 || r.StatusCode >= 300 { // Docker didn't do the start
return nil
}
container, err := inspectContainerInPath(i.proxy.client, r.Request.URL.Path)
if err != nil {
return err
}
cidrs, ok := weaveCIDRsFromConfig(container.Config)
if !ok && !i.withIPAM {
Debug.Print("No Weave CIDR, ignoring")
cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig)
if err != nil {
Log.Infof("Leaving container %s alone because %s", container.ID, err)
return nil
}
Info.Printf("Attaching container %s with WEAVE_CIDR \"%s\" to weave network", container.ID, strings.Join(cidrs, " "))
Log.Infof("Attaching container %s with WEAVE_CIDR \"%s\" to weave network", container.ID, strings.Join(cidrs, " "))
args := []string{"attach"}
args = append(args, cidrs...)
args = append(args, container.ID)
if _, err := callWeave(args...); err != nil {
Warning.Printf("Attaching container %s to weave network failed: %v", container.ID, err)
if !i.proxy.NoRewriteHosts {
args = append(args, "--rewrite-hosts")
}
args = append(args, "--or-die", container.ID)
if _, stderr, err := callWeave(args...); err != nil {
Log.Warningf("Attaching container %s to weave network failed: %s", container.ID, string(stderr))
return errors.New(string(stderr))
} else if len(stderr) > 0 {
Log.Warningf("Attaching container %s to weave network: %s", container.ID, string(stderr))
}
return nil
}
+76
View File
@@ -0,0 +1,76 @@
package proxy
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/docker/docker/pkg/homedir"
)
type TLSConfig struct {
Enabled, Verify bool
Cert, Key, CACert string
*tls.Config
}
func (c *TLSConfig) enabled() bool {
if c == nil {
return false
}
return c.Enabled || c.Verify
}
func (c *TLSConfig) loadCerts() error {
if !c.enabled() {
return nil
}
dockerCertPath := os.Getenv("DOCKER_CERT_PATH")
if dockerCertPath == "" {
dockerCertPath = filepath.Join(homedir.Get(), ".docker")
}
if c.CACert == "" {
c.CACert = filepath.Join(dockerCertPath, defaultCaFile)
}
if c.Cert == "" {
c.Cert = filepath.Join(dockerCertPath, defaultCertFile)
}
if c.Key == "" {
c.Key = filepath.Join(dockerCertPath, defaultKeyFile)
}
tlsConfig := &tls.Config{
NextProtos: []string{"http/1.1"},
// Avoid fallback on insecure SSL protocols
MinVersion: tls.VersionTLS10,
}
if c.Verify {
certPool := x509.NewCertPool()
file, err := ioutil.ReadFile(c.CACert)
if err != nil {
return fmt.Errorf("Couldn't read CA certificate: %v", err)
}
certPool.AppendCertsFromPEM(file)
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
tlsConfig.ClientCAs = certPool
}
_, errCert := os.Stat(c.Cert)
_, errKey := os.Stat(c.Key)
if errCert == nil && errKey == nil {
cert, err := tls.LoadX509KeyPair(c.Cert, c.Key)
if err != nil {
return fmt.Errorf("Couldn't load X509 key pair: %q. Make sure the key is encrypted", err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
c.Config = tlsConfig
return nil
}
+149 -78
View File
@@ -2,10 +2,10 @@ package router
import (
"encoding/binary"
"encoding/gob"
"errors"
"fmt"
"log"
"net"
"strconv"
"sync"
"time"
)
@@ -29,6 +29,8 @@ const (
TieBreakTied
)
var ErrConnectToSelf = errors.New("Cannot connect to ourself")
type RemoteConnection struct {
local *Peer
remote *Peer
@@ -41,6 +43,7 @@ type LocalConnection struct {
sync.RWMutex
RemoteConnection
TCPConn *net.TCPConn
version byte
tcpSender TCPSender
remoteUDPAddr *net.UDPAddr
receivedHeartbeat bool
@@ -49,7 +52,7 @@ type LocalConnection struct {
SessionKey *[32]byte
heartbeatTCP *time.Ticker
heartbeatTimeout *time.Timer
heartbeatFrame *ForwardedFrame
heartbeatFrame []byte
heartbeat *time.Ticker
fragTest *time.Ticker
forwarder *Forwarder
@@ -85,38 +88,28 @@ func (conn *RemoteConnection) Log(args ...interface{}) {
log.Println(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
}
func (conn *RemoteConnection) String() string {
from := "<nil>"
if conn.local != nil {
from = conn.local.String()
}
to := "<nil>"
if conn.remote != nil {
to = conn.remote.String()
}
return fmt.Sprint("Connection ", from, "->", to)
func (conn *RemoteConnection) ErrorLog(args ...interface{}) {
log.Errorln(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
}
func NewLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, udpAddr *net.UDPAddr, router *Router) *LocalConnection {
// Does not return anything. If the connection is successful, it will
// end up in the local peer's connections map.
func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, udpAddr *net.UDPAddr, router *Router, acceptNewPeer bool) {
if connRemote.local != router.Ourself.Peer {
log.Fatal("Attempt to create local connection from a peer which is not ourself")
}
// NB, we're taking a copy of connRemote here.
return &LocalConnection{
actionChan := make(chan ConnectionAction, ChannelSize)
finished := make(chan struct{})
conn := &LocalConnection{
RemoteConnection: *connRemote,
Router: router,
TCPConn: tcpConn,
remoteUDPAddr: udpAddr,
effectivePMTU: DefaultPMTU}
}
// Async. Does not return anything. If the connection is successful,
// it will end up in the local peer's connections map.
func (conn *LocalConnection) Start(acceptNewPeer bool) {
actionChan := make(chan ConnectionAction, ChannelSize)
conn.actionChan = actionChan
finished := make(chan struct{})
conn.finished = finished
effectivePMTU: DefaultPMTU,
uid: randUint64(),
actionChan: actionChan,
finished: finished}
go conn.run(actionChan, finished, acceptNewPeer)
}
@@ -158,8 +151,7 @@ func (conn *LocalConnection) setEffectivePMTU(pmtu int) {
}
// Called by the connection's actor process, and by the connection's
// TCP received process. StackFrag is read in conn.Forward (called by
// router udp listener and sniffer processes)
// TCP receiver process. StackFrag is read in conn.forward
func (conn *LocalConnection) setStackFrag(frag bool) {
conn.Lock()
defer conn.Unlock()
@@ -230,7 +222,7 @@ func (conn *LocalConnection) ReceivedHeartbeat(remoteUDPAddr *net.UDPAddr, connU
if oldRemoteUDPAddr == nil {
return conn.sendFastHeartbeats()
} else if oldRemoteUDPAddr.String() != remoteUDPAddr.String() {
log.Println("Peer", conn.remote, "moved from", old, "to", remoteUDPAddr)
log.Println("Peer", conn.remote, "moved from", oldRemoteUDPAddr, "to", remoteUDPAddr)
}
return nil
})
@@ -253,19 +245,12 @@ func (conn *LocalConnection) SetEstablished() {
}
// Send a large frame down the DF channel in order to prompt
// PMTU discovery to start.
conn.Forward(true, &ForwardedFrame{
srcPeer: conn.local,
dstPeer: conn.remote,
frame: PMTUDiscovery},
nil)
conn.Send(true, PMTUDiscovery)
conn.heartbeat = time.NewTicker(SlowHeartbeat)
conn.fragTest = time.NewTicker(FragTestInterval)
// avoid initial waits for timers to fire
conn.Forward(true, conn.heartbeatFrame, nil)
conn.setStackFrag(false)
if err := conn.sendSimpleProtocolMsg(ProtocolStartFragmentationTest); err != nil {
return err
}
conn.Send(true, conn.heartbeatFrame)
conn.performFragTest()
return nil
})
}
@@ -287,15 +272,39 @@ func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished ch
defer func() { conn.shutdown(err) }()
defer close(finished)
tcpConn := conn.TCPConn
tcpConn.SetLinger(0)
enc := gob.NewEncoder(tcpConn)
dec := gob.NewDecoder(tcpConn)
if err = conn.handshake(enc, dec, acceptNewPeer); err != nil {
conn.TCPConn.SetLinger(0)
intro, err := ProtocolIntroParams{
MinVersion: conn.Router.ProtocolMinVersion,
MaxVersion: ProtocolMaxVersion,
Features: conn.makeFeatures(),
Conn: conn.TCPConn,
Password: conn.Router.Password,
Outbound: conn.outbound,
}.DoIntro()
if err != nil {
return
}
conn.Log("completed handshake")
conn.SessionKey = intro.SessionKey
conn.tcpSender = intro.Sender
conn.version = intro.Version
remote, err := conn.parseFeatures(intro.Features)
if err != nil {
return
}
if err = conn.registerRemote(remote, acceptNewPeer); err != nil {
return
}
if conn.SessionKey == nil {
conn.Decryptor = NewNonDecryptor()
} else {
conn.Decryptor = NewNaClDecryptor(conn.SessionKey, conn.outbound)
}
conn.Log("connection ready; using protocol version", conn.version)
// The ordering of the following is very important. [1]
@@ -310,7 +319,8 @@ func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished ch
if err = conn.initHeartbeats(); err != nil {
return
}
go conn.receiveTCP(dec)
go conn.receiveTCP(intro.Receiver)
err = conn.actorLoop(actionChan)
}
@@ -359,15 +369,88 @@ func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished ch
// prevent that completely, since, for example, forwarder can only be
// created when we know the remote UDP address, but it helps to try.
func (conn *LocalConnection) makeFeatures() map[string]string {
return map[string]string{
"PeerNameFlavour": PeerNameFlavour,
"Name": conn.local.Name.String(),
"NickName": conn.local.NickName,
"UID": fmt.Sprint(conn.local.UID),
"ConnID": fmt.Sprint(conn.uid),
}
}
type features map[string]string
func (features features) MustHave(keys []string) error {
for _, key := range keys {
if _, ok := features[key]; !ok {
return fmt.Errorf("Field %s is missing", key)
}
}
return nil
}
func (features features) Get(key string) string {
return features[key]
}
func (conn *LocalConnection) parseFeatures(features features) (*Peer, error) {
if err := features.MustHave([]string{"PeerNameFlavour", "Name", "NickName", "UID", "ConnID"}); err != nil {
return nil, err
}
remotePeerNameFlavour := features.Get("PeerNameFlavour")
if remotePeerNameFlavour != PeerNameFlavour {
return nil, fmt.Errorf("Peer name flavour mismatch (ours: '%s', theirs: '%s')", PeerNameFlavour, remotePeerNameFlavour)
}
name, err := PeerNameFromString(features.Get("Name"))
if err != nil {
return nil, err
}
nickName := features.Get("NickName")
uid, err := ParsePeerUID(features.Get("UID"))
if err != nil {
return nil, err
}
remoteConnID, err := strconv.ParseUint(features.Get("ConnID"), 10, 64)
if err != nil {
return nil, err
}
conn.uid ^= remoteConnID
return NewPeer(name, nickName, uid, 0), nil
}
func (conn *LocalConnection) registerRemote(remote *Peer, acceptNewPeer bool) error {
if acceptNewPeer {
conn.remote = conn.Router.Peers.FetchWithDefault(remote)
} else {
conn.remote = conn.Router.Peers.FetchAndAddRef(remote.Name)
if conn.remote == nil {
return fmt.Errorf("Found unknown remote name: %s at %s", remote.Name, conn.remoteTCPAddr)
}
}
if conn.remote.UID != remote.UID {
return fmt.Errorf("Connection appears to be with different version of a peer we already know of")
}
if conn.remote == conn.local {
return ErrConnectToSelf
}
return nil
}
func (conn *LocalConnection) initHeartbeats() error {
conn.heartbeatTCP = time.NewTicker(TCPHeartbeat)
conn.heartbeatTimeout = time.NewTimer(HeartbeatTimeout)
heartbeatFrameBytes := make([]byte, EthernetOverhead+8)
binary.BigEndian.PutUint64(heartbeatFrameBytes[EthernetOverhead:], conn.uid)
conn.heartbeatFrame = &ForwardedFrame{
srcPeer: conn.local,
dstPeer: conn.remote,
frame: heartbeatFrameBytes}
conn.heartbeatFrame = make([]byte, EthernetOverhead+8)
binary.BigEndian.PutUint64(conn.heartbeatFrame[EthernetOverhead:], conn.uid)
if conn.remoteUDPAddr == nil {
return nil
}
@@ -384,10 +467,9 @@ func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err
case <-conn.heartbeatTimeout.C:
err = fmt.Errorf("timed out waiting for UDP heartbeat")
case <-tickerChan(conn.heartbeat):
conn.Forward(true, conn.heartbeatFrame, nil)
conn.Send(true, conn.heartbeatFrame)
case <-tickerChan(conn.fragTest):
conn.setStackFrag(false)
err = conn.sendSimpleProtocolMsg(ProtocolStartFragmentationTest)
conn.performFragTest()
}
}
return
@@ -395,9 +477,9 @@ func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err
func (conn *LocalConnection) shutdown(err error) {
if conn.remote == nil {
log.Printf("->[%s] connection shutting down due to error during handshake: %v\n", conn.remoteTCPAddr, err)
log.Errorf("->[%s] connection shutting down due to error during handshake: %v", conn.remoteTCPAddr, err)
} else {
conn.Log("connection shutting down due to error:", err)
conn.ErrorLog("connection shutting down due to error:", err)
}
if conn.TCPConn != nil {
@@ -434,23 +516,13 @@ func (conn *LocalConnection) sendProtocolMsg(m ProtocolMsg) error {
return conn.tcpSender.Send(Concat([]byte{byte(m.tag)}, m.msg))
}
func (conn *LocalConnection) receiveTCP(decoder *gob.Decoder) {
usingPassword := conn.SessionKey != nil
var receiver TCPReceiver
if usingPassword {
receiver = NewEncryptedTCPReceiver(conn.SessionKey, conn.outbound)
} else {
receiver = NewSimpleTCPReceiver()
}
func (conn *LocalConnection) receiveTCP(receiver TCPReceiver) {
var err error
for {
var msg []byte
conn.extendReadDeadline()
if err = decoder.Decode(&msg); err != nil {
break
}
msg, err = receiver.Decode(msg)
if err != nil {
var msg []byte
if msg, err = receiver.Receive(); err != nil {
break
}
if len(msg) < 1 {
@@ -473,12 +545,6 @@ func (conn *LocalConnection) handleProtocolMsg(tag ProtocolTag, payload []byte)
// We can now consider the connection as established from our
// end.
conn.SetEstablished()
case ProtocolStartFragmentationTest:
conn.Forward(false, &ForwardedFrame{
srcPeer: conn.local,
dstPeer: conn.remote,
frame: FragTest},
nil)
case ProtocolFragmentationReceived:
conn.setStackFrag(true)
case ProtocolPMTUVerified:
@@ -499,11 +565,16 @@ func (conn *LocalConnection) sendFastHeartbeats() error {
err := conn.ensureForwarders()
if err == nil {
conn.heartbeat = time.NewTicker(FastHeartbeat)
conn.Forward(true, conn.heartbeatFrame, nil) // avoid initial wait
conn.Send(true, conn.heartbeatFrame) // avoid initial wait
}
return err
}
func (conn *LocalConnection) performFragTest() {
conn.setStackFrag(false)
conn.Send(false, FragTest)
}
func tickerChan(ticker *time.Ticker) <-chan time.Time {
if ticker != nil {
return ticker.C
+83 -86
View File
@@ -1,26 +1,27 @@
package router
import (
"bytes"
"fmt"
"log"
"math/rand"
"net"
"time"
)
const (
InitialInterval = 5 * time.Second
MaxInterval = 10 * time.Minute
InitialInterval = 2 * time.Second
MaxInterval = 6 * time.Minute
)
type peerAddrs map[string]*net.TCPAddr
type ConnectionMaker struct {
ourself *LocalPeer
peers *Peers
port int
targets map[string]*Target
cmdLinePeers map[string]*net.TCPAddr
actionChan chan<- ConnectionMakerAction
ourself *LocalPeer
peers *Peers
port int
discovery bool
targets map[string]*Target
directPeers peerAddrs
actionChan chan<- ConnectionMakerAction
}
// Information about an address where we may find a peer
@@ -28,50 +29,61 @@ type Target struct {
attempting bool // are we currently attempting to connect there?
lastError error // reason for disconnection last time
tryAfter time.Time // next time to try this address
tryInterval time.Duration // backoff time on next failure
tryInterval time.Duration // retry delay on next failure
}
type ConnectionMakerAction func() bool
func NewConnectionMaker(ourself *LocalPeer, peers *Peers, port int) *ConnectionMaker {
return &ConnectionMaker{
ourself: ourself,
peers: peers,
port: port,
cmdLinePeers: make(map[string]*net.TCPAddr),
targets: make(map[string]*Target)}
}
func (cm *ConnectionMaker) Start() {
func NewConnectionMaker(ourself *LocalPeer, peers *Peers, port int, discovery bool) *ConnectionMaker {
actionChan := make(chan ConnectionMakerAction, ChannelSize)
cm.actionChan = actionChan
cm := &ConnectionMaker{
ourself: ourself,
peers: peers,
port: port,
discovery: discovery,
directPeers: peerAddrs{},
targets: make(map[string]*Target),
actionChan: actionChan}
go cm.queryLoop(actionChan)
return cm
}
func (cm *ConnectionMaker) InitiateConnection(peer string) error {
host, port, err := net.SplitHostPort(peer)
if err != nil {
host = peer
port = "0" // we use that as an indication that "no port was supplied"
}
addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%s", host, port))
if err != nil {
return err
func (cm *ConnectionMaker) InitiateConnections(peers []string, replace bool) []error {
errors := []error{}
addrs := peerAddrs{}
for _, peer := range peers {
host, port, err := net.SplitHostPort(peer)
if err != nil {
host = peer
port = "0" // we use that as an indication that "no port was supplied"
}
if addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%s", host, port)); err != nil {
errors = append(errors, err)
} else {
addrs[peer] = addr
}
}
cm.actionChan <- func() bool {
cm.cmdLinePeers[peer] = addr
// curtail any existing reconnect interval
if target, found := cm.targets[addr.String()]; found {
target.tryAfter, target.tryInterval = tryImmediately()
if replace {
cm.directPeers = peerAddrs{}
}
for peer, addr := range addrs {
cm.directPeers[peer] = addr
// curtail any existing reconnect interval
if target, found := cm.targets[addr.String()]; found {
target.tryNow()
}
}
return true
}
return nil
return errors
}
func (cm *ConnectionMaker) ForgetConnection(peer string) {
func (cm *ConnectionMaker) ForgetConnections(peers []string) {
cm.actionChan <- func() bool {
delete(cm.cmdLinePeers, peer)
for _, peer := range peers {
delete(cm.directPeers, peer)
}
return false
}
}
@@ -81,7 +93,7 @@ func (cm *ConnectionMaker) ConnectionTerminated(address string, err error) {
if target, found := cm.targets[address]; found {
target.attempting = false
target.lastError = err
target.tryAfter, target.tryInterval = tryAfter(target.tryInterval)
target.retry()
}
return true
}
@@ -91,33 +103,6 @@ func (cm *ConnectionMaker) Refresh() {
cm.actionChan <- func() bool { return true }
}
func (cm *ConnectionMaker) String() string {
// We need to Refresh first in order to clear out any 'attempting'
// connections from cm.targets that have been established since
// the last run of cm.checkStateAndAttemptConnections. These
// entries are harmless but do represent stale state that we do
// not want to report.
cm.Refresh()
resultChan := make(chan string, 0)
cm.actionChan <- func() bool {
var buf bytes.Buffer
for address, target := range cm.targets {
fmt.Fprintf(&buf, "->[%s]", address)
if target.lastError != nil {
fmt.Fprintf(&buf, " (%s)", target.lastError)
}
if target.attempting {
fmt.Fprintf(&buf, " trying since %v\n", target.tryAfter)
} else {
fmt.Fprintf(&buf, " next try at %v\n", target.tryAfter)
}
}
resultChan <- buf.String()
return false
}
return <-resultChan
}
func (cm *ConnectionMaker) queryLoop(actionChan <-chan ConnectionMakerAction) {
timer := time.NewTimer(MaxDuration)
run := func() { timer.Reset(cm.checkStateAndAttemptConnections()) }
@@ -135,8 +120,8 @@ func (cm *ConnectionMaker) queryLoop(actionChan <-chan ConnectionMakerAction) {
func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration {
var (
validTarget = make(map[string]struct{})
cmdLineTarget = make(map[string]struct{})
validTarget = make(map[string]struct{})
directTarget = make(map[string]struct{})
)
// Copy the set of things we are connected to, so we can access
// them without locking. Also clear out any entries in cm.targets
@@ -152,12 +137,12 @@ func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration {
return
}
target := &Target{}
target.tryAfter, target.tryInterval = tryImmediately()
target.tryNow()
cm.targets[address] = target
}
// Add command-line targets that are not connected
for _, addr := range cm.cmdLinePeers {
// Add direct targets that are not connected
for _, addr := range cm.directPeers {
completeAddr := *addr
attempt := true
if completeAddr.Port == 0 {
@@ -170,7 +155,7 @@ func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration {
}
}
address := completeAddr.String()
cmdLineTarget[address] = void
directTarget[address] = void
if attempt {
addTarget(address)
}
@@ -178,9 +163,11 @@ func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration {
// Add targets for peers that someone else is connected to, but we
// aren't
cm.addPeerTargets(ourConnectedPeers, addTarget)
if cm.discovery {
cm.addPeerTargets(ourConnectedPeers, addTarget)
}
return cm.connectToTargets(validTarget, cmdLineTarget)
return cm.connectToTargets(validTarget, directTarget)
}
func (cm *ConnectionMaker) ourConnections() (PeerNameSet, map[string]struct{}, map[string]struct{}) {
@@ -233,7 +220,7 @@ func (cm *ConnectionMaker) addPeerTargets(ourConnectedPeers PeerNameSet, addTarg
})
}
func (cm *ConnectionMaker) connectToTargets(validTarget map[string]struct{}, cmdLineTarget map[string]struct{}) time.Duration {
func (cm *ConnectionMaker) connectToTargets(validTarget map[string]struct{}, directTarget map[string]struct{}) time.Duration {
now := time.Now() // make sure we catch items just added
after := MaxDuration
for address, target := range cm.targets {
@@ -244,10 +231,13 @@ func (cm *ConnectionMaker) connectToTargets(validTarget map[string]struct{}, cmd
delete(cm.targets, address)
continue
}
if target.tryAfter.IsZero() {
continue
}
switch duration := target.tryAfter.Sub(now); {
case duration <= 0:
target.attempting = true
_, isCmdLineTarget := cmdLineTarget[address]
_, isCmdLineTarget := directTarget[address]
go cm.attemptConnection(address, isCmdLineTarget)
case duration < after:
after = duration
@@ -257,22 +247,29 @@ func (cm *ConnectionMaker) connectToTargets(validTarget map[string]struct{}, cmd
}
func (cm *ConnectionMaker) attemptConnection(address string, acceptNewPeer bool) {
log.Printf("->[%s] attempting connection\n", address)
log.Printf("->[%s] attempting connection", address)
if err := cm.ourself.CreateConnection(address, acceptNewPeer); err != nil {
log.Printf("->[%s] error during connection attempt: %v\n", address, err)
log.Errorf("->[%s] error during connection attempt: %v", address, err)
cm.ConnectionTerminated(address, err)
}
}
func tryImmediately() (time.Time, time.Duration) {
interval := time.Duration(rand.Int63n(int64(InitialInterval)))
return time.Now(), interval
func (t *Target) tryNow() {
t.tryAfter = time.Now()
t.tryInterval = InitialInterval
}
func tryAfter(interval time.Duration) (time.Time, time.Duration) {
interval += time.Duration(rand.Int63n(int64(interval)))
if interval > MaxInterval {
interval = MaxInterval
// The delay at the nth retry is a random value in the range
// [i-i/2,i+i/2], where i = InitialInterval * 1.5^(n-1).
func (t *Target) retry() {
if t.lastError == ErrConnectToSelf {
t.tryAfter = time.Time{}
t.tryInterval = MaxInterval
return
}
t.tryAfter = time.Now().Add(t.tryInterval/2 + time.Duration(rand.Int63n(int64(t.tryInterval))))
t.tryInterval = t.tryInterval * 3 / 2
if t.tryInterval > MaxInterval {
t.tryInterval = MaxInterval
}
return time.Now().Add(interval), interval
}
+3 -1
View File
@@ -11,7 +11,7 @@ const (
Port = 6783
HTTPPort = Port + 1
DefaultPMTU = 65535
MaxUDPPacketSize = 65536
MaxUDPPacketSize = 65535
ChannelSize = 16
FragTestSize = 60001
PMTUDiscoverySize = 60000
@@ -19,11 +19,13 @@ const (
FastHeartbeat = 500 * time.Millisecond
SlowHeartbeat = 10 * time.Second
FragTestInterval = 5 * time.Minute
GossipInterval = 30 * time.Second
PMTUVerifyAttempts = 8
PMTUVerifyTimeout = 10 * time.Millisecond // gets doubled with every attempt
MaxDuration = time.Duration(math.MaxInt64)
MaxMissedHeartbeats = 6
HeartbeatTimeout = MaxMissedHeartbeats * SlowHeartbeat
MaxTCPMsgSize = 10 * 1024 * 1024
)
var (
+86 -36
View File
@@ -1,15 +1,17 @@
package router
import (
"code.google.com/p/go-bit/bit"
"code.google.com/p/go.crypto/nacl/box"
"code.google.com/p/go.crypto/nacl/secretbox"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"encoding/gob"
"fmt"
"io"
"sync"
"github.com/andybalholm/go-bit"
"golang.org/x/crypto/nacl/box"
"golang.org/x/crypto/nacl/secretbox"
)
func GenerateKeyPair() (publicKey, privateKey *[32]byte, err error) {
@@ -30,7 +32,6 @@ func FormSessionKey(remotePublicKey, localPrivateKey *[32]byte, secretKey []byte
type Encryptor interface {
FrameOverhead() int
PacketOverhead() int
IsEmpty() bool
Bytes() ([]byte, error)
AppendFrame(src []byte, dst []byte, frame []byte)
TotalLen() int
@@ -71,10 +72,6 @@ func (ne *NonEncryptor) FrameOverhead() int {
return NameSize + NameSize + 2
}
func (ne *NonEncryptor) IsEmpty() bool {
return ne.buffered == ne.prefixLen
}
func (ne *NonEncryptor) Bytes() ([]byte, error) {
buf := ne.buf[:ne.buffered]
ne.buffered = ne.prefixLen
@@ -305,10 +302,12 @@ func (di *NaClDecryptorInstance) advanceState(seqNo uint64) (int, *bit.Set) {
// The lowest 64 bits of the nonce contain the message sequence
// number. The top most bit indicates the connection polarity at the
// sender - '1' for outbound. The remaining 127 bits are zero. The
// polarity is needed so that the two ends of a connection do not use
// the same nonces. This is a requirement of the NaCl Security Model;
// see http://nacl.cr.yp.to/box.html.
// sender - '1' for outbound; the next indicates protocol type - '1'
// for TCP. The remaining 126 bits are zero. The polarity is needed so
// that the two ends of a connection do not use the same nonces; the
// protocol type so that the TCP and UDP sender nonces are disjoint.
// This is a requirement of the NaCl Security Model; see
// http://nacl.cr.yp.to/box.html.
type TCPCryptoState struct {
sessionKey *[32]byte
nonce [24]byte
@@ -320,6 +319,8 @@ func NewTCPCryptoState(sessionKey *[32]byte, outbound bool) *TCPCryptoState {
if outbound {
s.nonce[0] |= (1 << 7)
}
// Ensure that TCP and UDP sequences are disjoint
s.nonce[0] |= (1 << 6)
return s
}
@@ -332,28 +333,48 @@ type TCPSender interface {
Send([]byte) error
}
type SimpleTCPSender struct {
type GobTCPSender struct {
encoder *gob.Encoder
}
type LengthPrefixTCPSender struct {
writer io.Writer
}
type EncryptedTCPSender struct {
SimpleTCPSender
sync.RWMutex
state *TCPCryptoState
sender TCPSender
state *TCPCryptoState
}
func NewSimpleTCPSender(encoder *gob.Encoder) *SimpleTCPSender {
return &SimpleTCPSender{encoder: encoder}
func NewGobTCPSender(encoder *gob.Encoder) *GobTCPSender {
return &GobTCPSender{encoder: encoder}
}
func (sender *SimpleTCPSender) Send(msg []byte) error {
func (sender *GobTCPSender) Send(msg []byte) error {
return sender.encoder.Encode(msg)
}
func NewEncryptedTCPSender(encoder *gob.Encoder, sessionKey *[32]byte, outbound bool) *EncryptedTCPSender {
return &EncryptedTCPSender{
SimpleTCPSender: *NewSimpleTCPSender(encoder),
state: NewTCPCryptoState(sessionKey, outbound)}
func NewLengthPrefixTCPSender(writer io.Writer) *LengthPrefixTCPSender {
return &LengthPrefixTCPSender{writer: writer}
}
func (sender *LengthPrefixTCPSender) Send(msg []byte) error {
l := len(msg)
if l > MaxTCPMsgSize {
return fmt.Errorf("outgoing message exceeds maximum size: %d > %d", l, MaxTCPMsgSize)
}
// We copy the message so we can send it in a single Write
// operation, thus making this thread-safe without locking.
prefixedMsg := make([]byte, 4+l)
binary.BigEndian.PutUint32(prefixedMsg, uint32(l))
copy(prefixedMsg[4:], msg)
_, err := sender.writer.Write(prefixedMsg)
return err
}
func NewEncryptedTCPSender(sender TCPSender, sessionKey *[32]byte, outbound bool) *EncryptedTCPSender {
return &EncryptedTCPSender{sender: sender, state: NewTCPCryptoState(sessionKey, outbound)}
}
func (sender *EncryptedTCPSender) Send(msg []byte) error {
@@ -361,40 +382,69 @@ func (sender *EncryptedTCPSender) Send(msg []byte) error {
defer sender.Unlock()
encodedMsg := secretbox.Seal(nil, msg, &sender.state.nonce, sender.state.sessionKey)
sender.state.advance()
return sender.SimpleTCPSender.Send(encodedMsg)
return sender.sender.Send(encodedMsg)
}
type TCPReceiver interface {
Decode([]byte) ([]byte, error)
Receive() ([]byte, error)
}
type SimpleTCPReceiver struct {
type GobTCPReceiver struct {
decoder *gob.Decoder
}
type LengthPrefixTCPReceiver struct {
reader io.Reader
}
type EncryptedTCPReceiver struct {
SimpleTCPReceiver
state *TCPCryptoState
receiver TCPReceiver
state *TCPCryptoState
}
func NewSimpleTCPReceiver() *SimpleTCPReceiver {
return &SimpleTCPReceiver{}
func NewGobTCPReceiver(decoder *gob.Decoder) *GobTCPReceiver {
return &GobTCPReceiver{decoder: decoder}
}
func (receiver *SimpleTCPReceiver) Decode(msg []byte) ([]byte, error) {
return msg, nil
func (receiver *GobTCPReceiver) Receive() ([]byte, error) {
var msg []byte
err := receiver.decoder.Decode(&msg)
return msg, err
}
func NewEncryptedTCPReceiver(sessionKey *[32]byte, outbound bool) *EncryptedTCPReceiver {
return &EncryptedTCPReceiver{
SimpleTCPReceiver: *NewSimpleTCPReceiver(),
state: NewTCPCryptoState(sessionKey, !outbound)}
func NewLengthPrefixTCPReceiver(reader io.Reader) *LengthPrefixTCPReceiver {
return &LengthPrefixTCPReceiver{reader: reader}
}
func (receiver *EncryptedTCPReceiver) Decode(msg []byte) ([]byte, error) {
func (receiver *LengthPrefixTCPReceiver) Receive() ([]byte, error) {
lenPrefix := make([]byte, 4)
if _, err := io.ReadFull(receiver.reader, lenPrefix); err != nil {
return nil, err
}
l := binary.BigEndian.Uint32(lenPrefix)
if l > MaxTCPMsgSize {
return nil, fmt.Errorf("incoming message exceeds maximum size: %d > %d", l, MaxTCPMsgSize)
}
msg := make([]byte, l)
_, err := io.ReadFull(receiver.reader, msg)
return msg, err
}
func NewEncryptedTCPReceiver(receiver TCPReceiver, sessionKey *[32]byte, outbound bool) *EncryptedTCPReceiver {
return &EncryptedTCPReceiver{receiver: receiver, state: NewTCPCryptoState(sessionKey, !outbound)}
}
func (receiver *EncryptedTCPReceiver) Receive() ([]byte, error) {
msg, err := receiver.receiver.Receive()
if err != nil {
return nil, err
}
decodedMsg, success := secretbox.Open(nil, msg, &receiver.state.nonce, receiver.state.sessionKey)
if !success {
return nil, fmt.Errorf("Unable to decrypt TCP msg")
}
receiver.state.advance()
return decodedMsg, nil
}
+28 -20
View File
@@ -2,27 +2,31 @@ package router
import (
"bytes"
"code.google.com/p/gopacket"
"code.google.com/p/gopacket/layers"
"log"
"net"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
type EthernetDecoder struct {
eth layers.Ethernet
ip layers.IPv4
Eth layers.Ethernet
IP layers.IPv4
decoded []gopacket.LayerType
parser *gopacket.DecodingLayerParser
}
func NewEthernetDecoder() *EthernetDecoder {
dec := &EthernetDecoder{}
dec.parser = gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, &dec.eth, &dec.ip)
dec.parser = gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, &dec.Eth, &dec.IP)
return dec
}
func (dec *EthernetDecoder) DecodeLayers(data []byte) error {
return dec.parser.DecodeLayers(data, &dec.decoded)
func (dec *EthernetDecoder) DecodeLayers(data []byte) {
// We intentionally discard the error return here, because it
// is normal for gopacket to return an error saying that it
// cannot decode a layer beyond the ones we specified when
// setting up the parser.
dec.parser.DecodeLayers(data, &dec.decoded)
}
func (dec *EthernetDecoder) sendICMPFragNeeded(mtu int, sendFrame func([]byte) error) error {
@@ -30,23 +34,23 @@ func (dec *EthernetDecoder) sendICMPFragNeeded(mtu int, sendFrame func([]byte) e
opts := gopacket.SerializeOptions{
FixLengths: true,
ComputeChecksums: true}
ipHeaderSize := int(dec.ip.IHL) * 4 // IHL is the number of 32-byte words in the header
payload := gopacket.Payload(dec.ip.BaseLayer.Contents[:ipHeaderSize+8])
ipHeaderSize := int(dec.IP.IHL) * 4 // IHL is the number of 32-byte words in the header
payload := gopacket.Payload(dec.IP.BaseLayer.Contents[:ipHeaderSize+8])
err := gopacket.SerializeLayers(buf, opts,
&layers.Ethernet{
SrcMAC: dec.eth.DstMAC,
DstMAC: dec.eth.SrcMAC,
EthernetType: dec.eth.EthernetType},
SrcMAC: dec.Eth.DstMAC,
DstMAC: dec.Eth.SrcMAC,
EthernetType: dec.Eth.EthernetType},
&layers.IPv4{
Version: 4,
TOS: dec.ip.TOS,
TOS: dec.IP.TOS,
Id: 0,
Flags: 0,
FragOffset: 0,
TTL: 64,
Protocol: layers.IPProtocolICMPv4,
DstIP: dec.ip.SrcIP,
SrcIP: dec.ip.DstIP},
DstIP: dec.IP.SrcIP,
SrcIP: dec.IP.DstIP},
&layers.ICMPv4{
TypeCode: 0x304,
Id: 0,
@@ -56,7 +60,7 @@ func (dec *EthernetDecoder) sendICMPFragNeeded(mtu int, sendFrame func([]byte) e
return err
}
log.Printf("Sending ICMP 3,4 (%v -> %v): PMTU= %v\n", dec.ip.DstIP, dec.ip.SrcIP, mtu)
log.Printf("Sending ICMP 3,4 (%v -> %v): PMTU=%v", dec.IP.DstIP, dec.IP.SrcIP, mtu)
return sendFrame(buf.Bytes())
}
@@ -67,10 +71,14 @@ var (
)
func (dec *EthernetDecoder) DropFrame() bool {
return bytes.Equal(stpMACPrefix, dec.eth.DstMAC[:len(stpMACPrefix)])
return bytes.Equal(stpMACPrefix, dec.Eth.DstMAC[:len(stpMACPrefix)])
}
func (dec *EthernetDecoder) IsSpecial() bool {
return dec.eth.Length == 0 && dec.eth.EthernetType == layers.EthernetTypeLLC &&
bytes.Equal(zeroMAC, dec.eth.SrcMAC) && bytes.Equal(zeroMAC, dec.eth.DstMAC)
return dec.Eth.Length == 0 && dec.Eth.EthernetType == layers.EthernetTypeLLC &&
bytes.Equal(zeroMAC, dec.Eth.SrcMAC) && bytes.Equal(zeroMAC, dec.Eth.DstMAC)
}
func (dec *EthernetDecoder) DF() bool {
return len(dec.decoded) == 2 && (dec.IP.Flags&layers.IPv4DontFragment != 0)
}
+38 -33
View File
@@ -1,10 +1,11 @@
package router
import (
"code.google.com/p/gopacket"
"code.google.com/p/gopacket/layers"
"syscall"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
type ForwardedFrame struct {
@@ -39,16 +40,12 @@ func (conn *LocalConnection) ensureForwarders() error {
forwarder := NewForwarder(conn, encryptor, udpSender, DefaultPMTU)
forwarderDF := NewForwarderDF(conn, encryptorDF, udpSenderDF, DefaultPMTU)
effectivePMTU := forwarderDF.unverifiedPMTU
forwarder.Start()
forwarderDF.Start()
// Various fields in the conn struct are read by other processes,
// so we have to use locks.
conn.Lock()
conn.forwarder = forwarder
conn.forwarderDF = forwarderDF
conn.effectivePMTU = effectivePMTU
conn.Unlock()
return nil
@@ -62,11 +59,23 @@ func (conn *LocalConnection) stopForwarders() {
conn.forwarderDF.Shutdown()
}
// Called from peer.Relay[Broadcast] which is itself invoked from
// router (both UDP listener process and sniffer process). Also called
// from connection's heartbeat process, and from the connection's TCP
// receiver process.
func (conn *LocalConnection) Forward(df bool, frame *ForwardedFrame, dec *EthernetDecoder) error {
// Called from connection's actor process, and from the connection's
// TCP receiver process.
func (conn *LocalConnection) Send(df bool, frameBytes []byte) error {
frame := &ForwardedFrame{
srcPeer: conn.local,
dstPeer: conn.remote,
frame: frameBytes}
return conn.forward(frame, nil, df)
}
// Called from LocalPeer.Relay[Broadcast] which is itself invoked from
// router (both UDP listener process and sniffer process).
func (conn *LocalConnection) Forward(frame *ForwardedFrame, dec *EthernetDecoder) error {
return conn.forward(frame, dec, dec != nil && dec.DF())
}
func (conn *LocalConnection) forward(frame *ForwardedFrame, dec *EthernetDecoder, df bool) error {
conn.RLock()
var (
forwarder = conn.forwarder
@@ -80,6 +89,7 @@ func (conn *LocalConnection) Forward(df bool, frame *ForwardedFrame, dec *Ethern
conn.Log("Cannot forward frame yet - awaiting contact")
return nil
}
// We could use non-blocking channel sends here, i.e. drop frames
// on the floor when the forwarder is busy. This would allow our
// caller - the capturing loop in the router - to read frames more
@@ -108,11 +118,10 @@ func (conn *LocalConnection) Forward(df bool, frame *ForwardedFrame, dec *Ethern
forwarderDF.Forward(frame)
return nil
}
conn.Router.LogFrame("Fragmenting", frame.frame, &dec.eth)
// We can't trust the stack to fragment, we have IP, and we
// have a frame that's too big for the MTU, so we have to
// fragment it ourself.
return fragment(dec.eth, dec.ip, effectivePMTU, frame, func(segFrame *ForwardedFrame) {
return fragment(dec.Eth, dec.IP, effectivePMTU, frame, func(segFrame *ForwardedFrame) {
forwarderDF.Forward(segFrame)
})
}
@@ -194,20 +203,18 @@ type Forwarder struct {
}
func NewForwarder(conn *LocalConnection, enc Encryptor, udpSender UDPSender, pmtu int) *Forwarder {
return &Forwarder{
ch := make(chan *ForwardedFrame, ChannelSize)
finished := make(chan struct{})
fwd := &Forwarder{
conn: conn,
ch: ch,
finished: finished,
enc: enc,
udpSender: udpSender,
maxPayload: pmtu - UDPOverhead,
processSendError: func(err error) error { return err }}
}
func (fwd *Forwarder) Start() {
ch := make(chan *ForwardedFrame, ChannelSize)
fwd.ch = ch
finished := make(chan struct{})
fwd.finished = finished
go fwd.run(ch, finished)
return fwd
}
func (fwd *Forwarder) Shutdown() {
@@ -274,7 +281,7 @@ func (fwd *Forwarder) accumulateAndSendFrames(ch <-chan *ForwardedFrame, frame *
}
func (fwd *Forwarder) logDrop(frame *ForwardedFrame) {
fwd.conn.Log("Dropping too big frame during forwarding: frame len:", len(frame.frame), "; effective PMTU:", fwd.maxPayload+UDPOverhead-fwd.effectiveOverhead())
fwd.conn.ErrorLog("Dropping too big frame during forwarding: frame len:", len(frame.frame), "; effective PMTU:", fwd.maxPayload+UDPOverhead-fwd.effectiveOverhead())
}
func (fwd *Forwarder) appendFrame(frame *ForwardedFrame) bool {
@@ -309,25 +316,23 @@ type ForwarderDF struct {
}
func NewForwarderDF(conn *LocalConnection, enc Encryptor, udpSender UDPSender, pmtu int) *ForwarderDF {
ch := make(chan *ForwardedFrame, ChannelSize)
finished := make(chan struct{})
verifyPMTU := make(chan int, ChannelSize)
fwd := &ForwarderDF{
Forwarder: Forwarder{
conn: conn,
ch: ch,
finished: finished,
enc: enc,
udpSender: udpSender,
maxPayload: pmtu - UDPOverhead}}
maxPayload: pmtu - UDPOverhead},
verifyPMTU: verifyPMTU}
fwd.Forwarder.processSendError = fwd.processSendError
fwd.unverifiedPMTU = pmtu - fwd.effectiveOverhead()
return fwd
}
func (fwd *ForwarderDF) Start() {
ch := make(chan *ForwardedFrame, ChannelSize)
fwd.ch = ch
finished := make(chan struct{})
fwd.finished = finished
verifyPMTU := make(chan int, ChannelSize)
fwd.verifyPMTU = verifyPMTU
conn.setEffectivePMTU(fwd.unverifiedPMTU)
go fwd.run(ch, finished, verifyPMTU)
return fwd
}
func (fwd *ForwarderDF) PMTUVerified(pmtu int) {
+80 -250
View File
@@ -1,18 +1,11 @@
package router
import (
"bytes"
"encoding/gob"
"fmt"
"log"
"sync"
"time"
)
const GossipInterval = 30 * time.Second
type GossipData interface {
Encode() []byte
Encode() [][]byte
Merge(GossipData)
}
@@ -28,7 +21,7 @@ type Gossiper interface {
OnGossipUnicast(sender PeerName, msg []byte) error
// merge received data into state and return a representation of
// the received data, for further propagation
OnGossipBroadcast(update []byte) (GossipData, error)
OnGossipBroadcast(sender PeerName, update []byte) (GossipData, error)
// return state of everything we know; gets called periodically
Gossip() GossipData
// merge received data into state and return "everything new I've
@@ -41,23 +34,43 @@ type Gossiper interface {
type GossipSender struct {
send func(GossipData)
cell chan GossipData
// for testing
sent bool
flushch chan chan bool
}
func NewGossipSender(send func(GossipData)) *GossipSender {
return &GossipSender{send: send}
}
func (sender *GossipSender) Start() {
sender.cell = make(chan GossipData, 1)
cell := make(chan GossipData, 1)
flushch := make(chan chan bool)
sender := &GossipSender{
send: send,
cell: cell,
flushch: flushch,
}
go sender.run()
return sender
}
func (sender *GossipSender) run() {
for {
if pending := <-sender.cell; pending == nil { // receive zero value when chan is closed
break
} else {
select {
case pending := <-sender.cell:
if pending == nil { // receive zero value when chan is closed
return
}
sender.send(pending)
sender.sent = true
case ch := <-sender.flushch:
// send anything pending, then reply back whether we sent
// anything since previous flush
select {
case pending := <-sender.cell:
sender.send(pending)
sender.sent = true
default:
}
ch <- sender.sent
sender.sent = false
}
}
}
@@ -77,36 +90,49 @@ func (sender *GossipSender) Stop() {
close(sender.cell)
}
type connectionSenders map[Connection]*GossipSender
type peerSenders map[PeerName]*GossipSender
type GossipChannel struct {
sync.Mutex
ourself *LocalPeer
routes *Routes
name string
hash uint32
gossiper Gossiper
senders connectionSenders
broadcasters peerSenders
}
type GossipChannels map[string]*GossipChannel
func (router *Router) NewGossip(channelName string, g Gossiper) Gossip {
channelHash := hash(channelName)
channel := &GossipChannel{
ourself: router.Ourself,
routes: router.Routes,
name: channelName,
hash: channelHash,
gossiper: g,
senders: make(connectionSenders),
broadcasters: make(peerSenders)}
router.GossipChannels[channelHash] = channel
channel := NewGossipChannel(channelName, router.Ourself, router.Routes, g)
router.gossipLock.Lock()
defer router.gossipLock.Unlock()
if _, found := router.gossipChannels[channelName]; found {
checkFatal(fmt.Errorf("[gossip] duplicate channel %s", channelName))
}
router.gossipChannels[channelName] = channel
return channel
}
func (router *Router) gossipChannel(channelName string) *GossipChannel {
router.gossipLock.RLock()
channel, found := router.gossipChannels[channelName]
router.gossipLock.RUnlock()
if found {
return channel
}
router.gossipLock.Lock()
defer router.gossipLock.Unlock()
if channel, found = router.gossipChannels[channelName]; found {
return channel
}
channel = NewGossipChannel(channelName, router.Ourself, router.Routes, &surrogateGossiper)
channel.log("created surrogate channel")
router.gossipChannels[channelName] = channel
return channel
}
func (router *Router) gossipChannelSet() map[*GossipChannel]struct{} {
channels := make(map[*GossipChannel]struct{})
router.gossipLock.RLock()
defer router.gossipLock.RUnlock()
for _, channel := range router.gossipChannels {
channels[channel] = void
}
return channels
}
func (router *Router) SendAllGossip() {
for _, channel := range router.GossipChannels {
for channel := range router.gossipChannelSet() {
if gossip := channel.gossiper.Gossip(); gossip != nil {
channel.Send(router.Ourself.Name, gossip)
}
@@ -114,228 +140,32 @@ func (router *Router) SendAllGossip() {
}
func (router *Router) SendAllGossipDown(conn Connection) {
for _, channel := range router.GossipChannels {
for channel := range router.gossipChannelSet() {
if gossip := channel.gossiper.Gossip(); gossip != nil {
channel.SendDown(conn, channel.gossiper.Gossip())
}
}
}
func (router *Router) handleGossip(tag ProtocolTag, payload []byte) error {
decoder := gob.NewDecoder(bytes.NewReader(payload))
var channelHash uint32
if err := decoder.Decode(&channelHash); err != nil {
return err
}
channel, found := router.GossipChannels[channelHash]
if !found {
return fmt.Errorf("[gossip] received unknown channel with hash %v", channelHash)
}
var srcName PeerName
if err := decoder.Decode(&srcName); err != nil {
return err
}
switch tag {
case ProtocolGossipUnicast:
return channel.deliverUnicast(srcName, payload, decoder)
case ProtocolGossipBroadcast:
return channel.deliverBroadcast(srcName, payload, decoder)
case ProtocolGossip:
return channel.deliver(srcName, payload, decoder)
}
return nil
}
func (c *GossipChannel) deliverUnicast(srcName PeerName, origPayload []byte, dec *gob.Decoder) error {
var destName PeerName
if err := dec.Decode(&destName); err != nil {
return err
}
if c.ourself.Name != destName {
return c.relayUnicast(destName, origPayload)
}
var payload []byte
if err := dec.Decode(&payload); err != nil {
return err
}
return c.gossiper.OnGossipUnicast(srcName, payload)
}
func (c *GossipChannel) deliverBroadcast(srcName PeerName, _ []byte, dec *gob.Decoder) error {
var payload []byte
if err := dec.Decode(&payload); err != nil {
return err
}
data, err := c.gossiper.OnGossipBroadcast(payload)
if err != nil || data == nil {
return err
}
return c.relayBroadcast(srcName, data)
}
func (c *GossipChannel) deliver(srcName PeerName, _ []byte, dec *gob.Decoder) error {
var payload []byte
if err := dec.Decode(&payload); err != nil {
return err
}
if data, err := c.gossiper.OnGossip(payload); err != nil {
return err
} else if data != nil {
c.Send(srcName, data)
}
return nil
}
func (c *GossipChannel) Send(srcName PeerName, data GossipData) {
// do this outside the lock below so we avoid lock nesting
c.routes.EnsureRecalculated()
selectedConnections := make(ConnectionSet)
for name := range c.routes.RandomNeighbours(srcName) {
if conn, found := c.ourself.ConnectionTo(name); found {
selectedConnections[conn] = void
}
}
if len(selectedConnections) == 0 {
return
}
connections := c.ourself.Connections()
c.Lock()
defer c.Unlock()
// GC - randomly (courtesy of go's map iterator) pick some
// existing entries and stop&remove them if the associated
// connection is no longer active. We stop as soon as we
// encounter a valid entry; the idea being that when there is
// little or no garbage then this executes close to O(1)[1],
// whereas when there is lots of garbage we remove it quickly.
//
// [1] TODO Unfortunately, due to the desire to avoid nested
// locks, instead of simply invoking Peer.ConnectionTo(name)
// below, we have that Peer.Connections() invocation above. That
// is O(n_our_connections) at best.
for conn, sender := range c.senders {
if _, found := connections[conn]; !found {
delete(c.senders, conn)
sender.Stop()
} else {
break
}
}
for conn := range selectedConnections {
c.sendDown(conn, data)
}
}
func (c *GossipChannel) SendDown(conn Connection, data GossipData) {
c.Lock()
c.sendDown(conn, data)
c.Unlock()
}
func (c *GossipChannel) sendDown(conn Connection, data GossipData) {
sender, found := c.senders[conn]
if !found {
sender = NewGossipSender(func(pending GossipData) {
protocolMsg := ProtocolMsg{ProtocolGossip, GobEncode(c.hash, c.ourself.Name, pending.Encode())}
conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
})
c.senders[conn] = sender
sender.Start()
}
sender.Send(data)
}
func (c *GossipChannel) GossipUnicast(dstPeerName PeerName, msg []byte) error {
return c.relayUnicast(dstPeerName, GobEncode(c.hash, c.ourself.Name, dstPeerName, msg))
}
func (c *GossipChannel) GossipBroadcast(update GossipData) error {
return c.relayBroadcast(c.ourself.Name, update)
}
func (c *GossipChannel) relayUnicast(dstPeerName PeerName, buf []byte) error {
if relayPeerName, found := c.routes.UnicastAll(dstPeerName); !found {
c.log("unknown relay destination:", dstPeerName)
} else if conn, found := c.ourself.ConnectionTo(relayPeerName); !found {
c.log("unable to find connection to relay peer", relayPeerName)
} else {
conn.(ProtocolSender).SendProtocolMsg(ProtocolMsg{ProtocolGossipUnicast, buf})
}
return nil
}
func (c *GossipChannel) relayBroadcast(srcName PeerName, update GossipData) error {
names := c.routes.PeerNames() // do this outside the lock so they don't nest
c.Lock()
defer c.Unlock()
// GC - randomly (courtesy of go's map iterator) pick some
// existing broadcasters and stop&remove them if their source peer
// is unknown. We stop as soon as we encounter a valid entry; the
// idea being that when there is little or no garbage then this
// executes close to O(1)[1], whereas when there is lots of
// garbage we remove it quickly.
//
// [1] TODO Unfortunately, due to the desire to avoid nested
// locks, instead of simply invoking Peers.Fetch(name) below, we
// have that Peers.Names() invocation above. That is O(n_peers) at
// best.
for name, broadcaster := range c.broadcasters {
if _, found := names[name]; !found {
delete(c.broadcasters, name)
broadcaster.Stop()
} else {
break
}
}
broadcaster, found := c.broadcasters[srcName]
if !found {
broadcaster = NewGossipSender(func(pending GossipData) { c.sendBroadcast(srcName, pending) })
c.broadcasters[srcName] = broadcaster
broadcaster.Start()
}
broadcaster.Send(update)
return nil
}
func (c *GossipChannel) sendBroadcast(srcName PeerName, update GossipData) {
c.routes.EnsureRecalculated()
nextHops := c.routes.BroadcastAll(srcName)
if len(nextHops) == 0 {
return
}
protocolMsg := ProtocolMsg{ProtocolGossipBroadcast, GobEncode(c.hash, srcName, update.Encode())}
// FIXME a single blocked connection can stall us
for _, conn := range c.ourself.ConnectionsTo(nextHops) {
conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
}
}
func (c *GossipChannel) log(args ...interface{}) {
log.Println(append(append([]interface{}{}, "[gossip "+c.name+"]:"), args...)...)
}
// for testing
// FIXME this doesn't actually guarantee everything has been sent
// since a GossipSender may be in the process of sending and there is
// no easy way for us to know when that has completed.
func (router *Router) sendPendingGossip() {
for _, channel := range router.GossipChannels {
func (router *Router) sendPendingGossip() bool {
sentSomething := false
for channel := range router.gossipChannelSet() {
channel.Lock()
for _, sender := range channel.senders {
sender.flush()
sentSomething = sender.flush() || sentSomething
}
for _, sender := range channel.broadcasters {
sender.flush()
sentSomething = sender.flush() || sentSomething
}
channel.Unlock()
}
return sentSomething
}
func (sender *GossipSender) flush() {
for {
select {
case pending := <-sender.cell:
sender.send(pending)
default:
return
}
}
func (sender *GossipSender) flush() bool {
ch := make(chan bool)
sender.flushch <- ch
return <-ch
}
Loaded 100 of 223 files, more files were not shown because too many files have changed in this diff. Show more