Compare commits

...
207 Commits
Author SHA1 Message Date
Bryan Boreham 1fcd521f67 Add release v2.4.1 2018-09-13 15:25:47 +00:00
Martynas Pumputis fa74e9f290 Merge pull request #3400 from weaveworks/issues/3384-peerlist-race
Fix two race conditions in Kubernetes IPAM reclaim

Fix #3384
2018-09-12 18:13:56 +03:00
Bryan Boreham 008eeb03ad Improve comments and error checking 2018-09-12 14:11:09 +00:00
Bryan Boreham 41c48dc678 Update test to call reclaimPeer() 2018-09-12 14:11:09 +00:00
Bryan Boreham 09be35dee0 Fetch stored peer list every time round the retry loop
This ensures we take account of other peers' updates and closes a race
condition whereby another peer's changes can be overwritten.
2018-09-12 14:11:09 +00:00
Bryan Boreham c3ad641d94 Refactor: extract reclaimPeer() function 2018-09-12 14:11:09 +00:00
Bryan Boreham bacb3952a3 Fetch Kubernetes node list every time round reclaim loop
If we retain the Kubernetes list for a long time we might see a peer
appear in the Weave list and conclude it has been deleted. We must
always fetch the Kubernetes list after fetching the Weave list, during
deletion.
2018-09-12 14:10:59 +00:00
Bryan Boreham 70257a6c19 Add test for kube-utils peer list
- In parallel on several goroutines, randomly add and remove peers.
 - Maintain the test's own idea of current state.
 - Check that the test's state and kube-utils' state are consistent.
2018-09-12 12:37:16 +00:00
Bryan Boreham bfe9f00e55 Fake kubernetes API which implements optimistic locking
Mostly copied from kubernetes/client-go files
2018-09-12 12:37:16 +00:00
Bryan Boreham 591be2d08f Refactor: Introduce an interface so weave client can be mocked 2018-09-12 12:37:16 +00:00
Bryan Boreham c3ce052cc5 Refactor: use Kubernetes.Interface instead of *Clientset
So we can call with a mock for testing
2018-09-12 12:35:34 +00:00
Bryan Boreham 7dcd4bdf47 Refactor: extract peerlist fns to new file 2018-09-12 12:35:34 +00:00
Bryan Boreham 2d8373249a Vendor mock Kubernetes APIs for testing 2018-09-12 12:35:33 +00:00
Bryan Boreham 4979ce1f5f Update to dep v5
This adds more metadata to Gopkg.lock, and seems to have deleted a few
more files.
2018-09-12 12:35:33 +00:00
Martynas Pumputis 78e00f9a3a Merge pull request #3397 from weaveworks/issues/3394-delete-old-me
Reclaim IPs when node name is re-used
2018-09-12 13:57:12 +03:00
Bryan Boreham 2ff0bbe150 Reclaim IPs when node name is re-used
Change the map used to reclaim peers so it is indexed by PeerName
which should be unique over time, rather than NodeName which can be
re-used.  Create an auxilliary set of NodeNames to help.

This way, if we can see that a different peer was previously running
with the same node name, it can be included for reclaim.
2018-09-10 15:10:51 +00:00
Bryan Boreham a6e1165e71 Merge pull request #3369 from dholbach/fix-3338
docs: update ECS references
2018-09-06 14:24:15 +00:00
Bryan Boreham fc211b15e9 Merge pull request #3393 from weaveworks/issues/3392-get-peers-with-no-ip
Do not exclude k8s node without any IP addr in reclaim
2018-09-06 15:20:33 +01:00
Martynas Pumputis acc24f1237 Do not exclude k8s node without any IP addr in reclaim
Otherwise, such node will be rmpeer'd which can lead to the IPAM
inconsistencies "Received update for IP range I own at <..>".
2018-09-06 14:49:20 +03:00
Bryan Boreham 612e265123 Merge pull request #3391 from weaveworks/debug-log
add documentation to enable debug logging
2018-09-06 12:45:58 +01:00
Murali Reddy 9cf50588b5 remove weave-npc logging details 2018-09-06 11:45:02 +00:00
Murali Reddy 6c2de466c0 add documnetatin to enable debug logging 2018-09-06 11:44:53 +00:00
Martynas Pumputis dd5994c7cf Merge pull request #3379 from weaveworks/issue-3226
add missing --token argument in help for `weave launch`

Fix #3226
2018-08-17 11:15:53 +02:00
Murali Reddy e6ba59f029 add missing --token argument in help for weave launch
Fixes #3226
2018-08-17 14:40:05 +05:30
Bryan Boreham 8fb3da6fc8 Merge pull request #3371 from weaveworks/parse-labels
Parse labels
2018-08-10 13:33:22 +01:00
Bryan Boreham 1814bbd2f2 Test container args-parsing code
Extract function just to parse args, so we can call it separately and
test it.
2018-08-08 13:24:53 +00:00
Bryan Boreham 374fed4699 Support --label flag on run-container command 2018-08-08 13:24:53 +00:00
Martynas Pumputis e669ac6ef8 Merge remote-tracking branch 'origin/2.3' into 2.4 2018-07-30 10:03:56 +02:00
Martynas Pumputis 4263ac1ac5 Merge pull request #3362 from weaveworks/issues/3361-defunct-procs
Print defunct processes if such exist
2018-07-30 08:33:41 +02:00
Martynas Pumputis 8176048268 Print defunct processes if such exist
Instead of printing their count. Helps to determine which
process causes the zombies.
2018-07-28 15:27:10 +02:00
Martynas Pumputis e2d6ee4223 Add release v2.4.0 2018-07-25 10:32:04 +02:00
Bryan Boreham 90a81ce736 Merge pull request #3319 from weaveworks/weave-reset-err
Make error "Weave is not running" less confusing when running on Kubernetes
2018-07-24 10:18:24 +01:00
Bryan Boreham 9cf0dd1b87 Merge pull request #3313 from weaveworks/issues/2624-egress-npc
Implement Egress NPC
2018-07-24 10:17:45 +01:00
Martynas Pumputis e65cf32372 npc: Flush chain if rules mismatch in AddChainWithRules
Also, move the helper function to net/iptables.go.
2018-07-24 10:06:55 +02:00
Martynas Pumputis 0ad2001ab4 npc: ACCEPT already established connections in egress chain
It's safe to accept such connections as they will be accepted
in the ingress chain anyway.
2018-07-24 10:06:55 +02:00
Martynas Pumputis 70ff510e49 npc: Document egress rules 2018-07-24 10:06:55 +02:00
Martynas Pumputis 6377aeabdd npc: Do not ignore errors when creating chains
Also, move ensureRulesAtTop to net/iptables.go
2018-07-24 10:06:55 +02:00
Martynas Pumputis 1649080a35 npc: Add ns name into ipBlock ipset name
Otherwise, the same ipBlock ipset can be used among different namespaces which
results in panic as refcounting of ipsets happens only per namespace, so
npc will try to create the same ipset twice.
2018-07-24 10:06:55 +02:00
Martynas Pumputis c1ee358f66 npc: Fix ipset package import path after rebase 2018-07-24 10:06:55 +02:00
Martynas Pumputis 2c708b57a1 npc: Improve analyser comments 2018-07-24 10:06:55 +02:00
Martynas Pumputis c5772c814c npc: Add comments for traffic steering 2018-07-24 10:06:55 +02:00
Martynas Pumputis 10a5b6fb51 npc: Use constants for chains 2018-07-24 10:06:55 +02:00
Martynas Pumputis 07d31a8558 npc: Get rid of the legacy netpol 2018-07-24 10:06:55 +02:00
Martynas Pumputis cf3a3be8f2 npc: Replace isNil by reflect.IsNil 2018-07-24 10:06:23 +02:00
Martynas Pumputis 49224ec9f7 npc: Rename policy{Ingress,Egress} to policyType{Ingress,Egress} 2018-07-24 10:06:23 +02:00
Martynas Pumputis dee24aec65 npc: Add integration test for egress
Also, install dig (bind-tools) in the network-tester image.
2018-07-24 10:06:23 +02:00
Martynas Pumputis 9a63ade1b5 npc: Do not create WEAVE-NPC-EGRESS* chains in legacy netpol mode 2018-07-24 10:06:23 +02:00
Martynas Pumputis 18a6237ec7 npc: Check whether weave-local-pods exists before creating
We do not destroy it anymore, thus the check.
2018-07-24 10:06:23 +02:00
Martynas Pumputis be8298926a npc: Pass --use-legacy-netpol to weaver in 840 2018-07-24 10:06:23 +02:00
Martynas Pumputis 86958be6cf npc: Add --use-legacy-netpol flag to weaver
Egress does not work in the legacy netpol mode, so in the legacy case
we don't have to create the egress traffic steering rules. Otherwise,
some traffic will be blocked.
2018-07-24 10:06:23 +02:00
Martynas Pumputis a2d07c85da npc: Remove WEAVE-NPC-EGRESS* chains upon "weave reset"
Also, fix removal of rule which steers traffic for ingress
processing.
2018-07-24 10:06:23 +02:00
Martynas Pumputis 295ddc5248 npc: Try not to clear WEAVE-NPC-EGRESS chain
First check whether the chain exists with the required rules, and
if not - create it.

If we flush each time we start weave-npc then we create a (short)
time window during which all egress traffic is allowed.
2018-07-24 10:06:23 +02:00
Martynas Pumputis a85a19e40a npc: Add unit test for egress policy with ipblock 2018-07-24 10:06:23 +02:00
Martynas Pumputis cec3c8a846 npc: Add iptables mocked interface 2018-07-24 10:06:23 +02:00
Martynas Pumputis 7da7ce85df npc: Extend TestDefaultAllow unit test to include egress 2018-07-24 10:06:23 +02:00
Martynas Pumputis 6055431b6f npc: Refactor bypassRules 2018-07-24 10:06:23 +02:00
Martynas Pumputis c0e2deb5a0 npc: Add WEAVE-NPC-EGRESS-ACCEPT chain
For improving readability of NPC rules. The chain marks to-be-allowed egress
traffic.
2018-07-24 10:06:23 +02:00
Martynas Pumputis 1f86ed55ba npc: Ensure default-allow rules for egress 2018-07-24 10:06:23 +02:00
Martynas Pumputis 35f1ba17e1 npc: Provision egress rules 2018-07-24 10:06:23 +02:00
Martynas Pumputis b84fbf86ec npc: Add a dummy isNil method to ruleHost interface 2018-07-24 10:06:23 +02:00
Martynas Pumputis a5e0b3452d npc: Create rules with IPBlock (egress only) 2018-07-24 10:06:23 +02:00
Martynas Pumputis 0a1ca30aae npc: Add getRuleSpec for ipBlockSpec 2018-07-24 10:06:23 +02:00
Martynas Pumputis 2e85018c26 npc: Add ruleHost interface
The idea for the interface is taken from the kitt1987/ipblock work.

The interface is going to be implemented by selectorSpec and
ipBlockSpec.
2018-07-24 10:06:23 +02:00
Martynas Pumputis affdc6f5af npc: Provision networkingv1.IPBlock (only for Egress) 2018-07-24 10:06:23 +02:00
Martynas Pumputis 9198bb5a68 npc: Refactor npc/ipblock.go
- No need to validate networkingv1.IPBlock fields as k8s does it.
- Fix provisioning.
2018-07-24 10:06:23 +02:00
Kitt Hsu 9769c8d5b9 support ipblock in npc
NOTE(brb): This commit is cherry-picked from the kitt1987/ipblock
branch.
2018-07-24 10:06:23 +02:00
Martynas Pumputis 688328dc57 npc: Steer traffic for egress processing
We need two rules:

* `-A INPUT -i weave -j WEAVE-NPC-EGRESS`: applies to packets destined
  to a local node.
* `-A FORWARD -i weave -j WEAVE-NPC-EGRESS`: the rest. It can match
  an ingress packet.
2018-07-24 10:06:23 +02:00
Martynas Pumputis 8904a4b144 npc: Get rid of selectorSpec.target
We treat selectorSpec as a target selector if len(selectorSpec.policyTypes) > 0.
2018-07-24 10:06:23 +02:00
Martynas Pumputis 8d1ee752d4 npc: Rename dstSelector to targetSelector
dstSelector becomes ambiguous once we introduce the egress.

So, from now on, we call selector a target selector if it selects
pods to which a netpol applies (aka NetworkPolicySpec.podSelector).
2018-07-24 10:06:23 +02:00
Martynas Pumputis f02a11b27b npc: Analyse egress policy
This is a copy-paste from the ingress part with small modifications.
2018-07-24 10:06:23 +02:00
Martynas Pumputis 5e675f26f7 npc: Embed policyType into rule
This ensures two rules with the same rulespec but of different
policyType will end up having different keys.

Sharing the same key would prevent from provisioning the rules.
2018-07-24 10:06:23 +02:00
Martynas Pumputis 497a9c7fd0 npc: Replace rulesType by policyType 2018-07-24 10:06:23 +02:00
Martynas Pumputis 5afcd0595d npc: Parse PolicyTypes field when analysing a netpol 2018-07-24 10:06:23 +02:00
Martynas Pumputis f27d4d2117 npc: Add defaultAllowIPSetName helper 2018-07-24 10:06:23 +02:00
Martynas Pumputis 79727a86fa npc: Manage entries in egressDefaultAllowIPSet 2018-07-24 10:06:23 +02:00
Martynas Pumputis d474c1c26d npc: Add policyType to selectorSpec
This allows us to distinguish between ingress and egress
dst selectors (required when managing entries in default-allow
ipsets).
2018-07-24 10:06:23 +02:00
Martynas Pumputis 29ec43bdc8 npc: Rename default-allow ipset for ingress
As we're going to introduce one for egress, we cannot use
"default-allow" name for ingress.
2018-07-24 10:06:23 +02:00
Martynas Pumputis cabc05b06c npc: Add rule type (ingress, egress) 2018-07-24 10:06:23 +02:00
Martynas Pumputis 89e820d62e npc: Create WEAVE-NPC-EGRESS chain with the base rules
The following rules are provisioned by weave-npc:

-A WEAVE-NPC-EGRESS -m state --state RELATED,ESTABLISHED -j RETURN
-A WEAVE-NPC-EGRESS -m set ! --match-set weave-local-pods src -j RETURN
-A WEAVE-NPC-EGRESS -d 224.0.0.0/4 -j RETURN
-A WEAVE-NPC-EGRESS -m state --state NEW -j WEAVE-NPC-EGRESS-DEFAULT
-A WEAVE-NPC-EGRESS -m state --state NEW -m mark ! --mark 0x40000/0x40000 -j WEAVE-NPC-EGRESS-CUSTOM
-A WEAVE-NPC-EGRESS -m state --state NEW -m mark ! --mark 0x40000/0x40000 -j NFLOG --nflog-group 86
-A WEAVE-NPC-EGRESS -m mark ! --mark 0x40000/0x40000 -j DROP

-A WEAVE-NPC-EGRESS-CUSTOM <rulespec> -j MARK --set-xmark 0x40000/0x40000
-A WEAVE-NPC-EGRESS-CUSTOM <rulespec> -j RETURN

-A WEAVE-NPC-EGRESS-DEFAULT <rulespec> -j MARK --set-xmark 0x40000/0x40000
-A WEAVE-NPC-EGRESS-DEFAULT <rulespec> -j RETURN

For each rule we create two (mark and return). We cannot just accept
a packet if it matches any rule, as a packet might need to traverse
the ingress npc as well which happens later in the chain (in some cases
we cannot detect whether packet is ingress or egress, so we need to
check both chains).
2018-07-24 10:06:23 +02:00
Bryan Boreham 88616007b0 Merge pull request #3353 from louismunro/fix_xtables_lock
Fix xtables.lock race condition between weave and kube-proxy.
2018-07-23 13:35:26 +01:00
Munro, Louis b262a1eedc Fix xtables.lock race condition between weave and kube-proxy.
Fixes #3351
2018-07-22 13:12:00 -04:00
Martynas Pumputis 973e1075b9 Merge pull request #3349 from weaveworks/remove-old-k8s
Remove mention of Kubernetes 1.6 from docs: it's very old now
2018-07-21 17:23:05 +02:00
Bryan Boreham b8ce722a2c Merge pull request #3346 from murali-reddy/issue-3206
3206: Protect against CNI plugin being called with the host namespace
2018-07-19 12:02:49 +01:00
Bryan Boreham b8fa4af06a Merge branch '2.3' 2018-07-17 12:25:49 +01:00
Bryan Boreham 7f1945f06e Remove mention of Kubernetes 1.6 from docs: it's very old now 2018-07-17 12:24:20 +01:00
Bryan Boreham ea8f041a20 Fix indentation of TOC 2018-07-17 11:38:09 +01:00
Bryan Boreham 52e8cdb011 Merge pull request #3348 from weaveworks/check-running
Add instructions to check weave-kube logs
2018-07-17 11:35:29 +01:00
Bryan Boreham ac9f47783c Review feedback 2018-07-17 10:24:26 +01:00
Murali Reddy 27f9aa16d6 3206: Protect against CNI plugin being called with the host namespace
adding a check to verify if network namespace is not host network namespace

Fixes #3206
2018-07-16 12:43:09 +05:30
Bryan Boreham 08dc649b9a Merge branch '2.3' 2018-07-12 08:58:54 +00:00
Bryan Boreham bb1a373272 Add instructions to check weave-kube logs
"Check it is running" and "check the logs" are important steps when
troubleshooting.
2018-07-12 08:53:02 +00:00
Martynas Pumputis e192f68891 Merge pull request #3345 from stevenjohnstone/issues/3337-symlink-upgrades-broken
Issue #3337: fix upgrade_cni_plugin

Fix #3337
2018-07-09 12:17:32 +02:00
Stevie Johnstone 73654791c1 Issue #3337: fix upgrade_cni_plugin 2018-07-09 10:33:04 +01:00
Martynas Pumputis 8908458942 Merge pull request #3340 from weaveworks/k8s-1-11
Update smoke tests to use Kubernetes 1.11
2018-07-09 09:49:11 +02:00
Bryan Boreham 26792283ab Merge pull request #3328 from weaveworks/minikube-docs
Add minikube caveat to docs
2018-07-05 13:39:06 +01:00
Bryan Boreham 32a92a4695 Add --force flag to kubeadm reset 2018-06-28 10:26:09 +00:00
Bryan Boreham 3bea2ae419 Bump to Kubernetes 1.11 2018-06-28 09:25:56 +00:00
Martynas Pumputis 58eaf19026 Merge pull request #3334 from weaveworks/issues/3332-add-nodestatus-perms
Add permissions to set node status
2018-06-21 17:37:11 +02:00
Bryan Boreham d513e68fab Add permissions to set node status
This is needed to update the node NetworkUnavailable conditon
see https://github.com/weaveworks/weave/issues/3249
2018-06-21 11:08:27 +00:00
Martynas Pumputis ca8bbb27a0 Add minikube caveat to docs 2018-06-16 18:04:03 +02:00
Martynas Pumputis 287dab27ae Merge pull request #3317 from weaveworks/break-infinite-loop
Break out of kube rm-peers loop if nothing changes
2018-06-12 14:52:39 +02:00
Martynas Pumputis 25c26519ed Merge remote-tracking branch 'origin/2.3' 2018-06-12 14:47:42 +02:00
Bryan Boreham cf66c8d507 Don't remove myself when reclaiming
Some logs from users show an entry of X with annotation X, which
suggests we have gone down the path of removing ourselves. This change
avoids that possibility.
2018-06-12 11:52:03 +00:00
Bryan Boreham 318bb767a5 Merge pull request #3320 from weaveworks/manifest-tool-make
Fix manifest-tool cmd in Makefile
2018-06-12 10:48:30 +01:00
Bryan Boreham ad6148f0f6 Break out of kube rm peers loop if nothing changes
Avoid getting into an infinite loop if some of the logic or underlying
data is bad
2018-06-12 08:31:35 +00:00
Martynas Pumputis 8042d3f22a Fix manifest-tool cmd in Makefile 2018-06-12 10:19:47 +02:00
Martynas Pumputis c7259fc424 Make error "Weave is not running" less confusing when running on Kubernetes 2018-06-12 09:58:08 +02:00
Bryan Boreham 62c949df6e Merge pull request #3316 from weaveworks/fix-makefile
Fix $HOME usage in Makefile
2018-06-11 16:39:36 +01:00
Martynas Pumputis 8c7dc342e0 Fix $HOME var in Makefile 2018-06-11 17:23:22 +02:00
Martynas Pumputis d15439aeac Merge remote-tracking branch 'origin/2.3' 2018-06-11 16:39:03 +02:00
Martynas Pumputis c19c64a990 Merge pull request #3305 from weaveworks/issues/3289-many-namespaces
Make the ipset list size bigger
2018-06-11 16:36:26 +02:00
Bryan Boreham 8e374d9416 D'oh 2018-06-11 13:52:15 +00:00
Martynas Pumputis 49216ce365 Merge pull request #3307 from weaveworks/set-kube-netstatus
Set Kubernetes NodeNetworkUnavailable to false when starting
2018-06-11 15:20:14 +02:00
Bryan Boreham 812c58297f Merge pull request #3298 from weaveworks/issues/2924-preserve-src-ip-on-local
2924: Preserve the client source IP
2018-06-11 13:21:12 +01:00
Bryan Boreham 2a04df1049 Rename "kube-peers" to "kube-utils"
It's now doing things like network status that are outside of the
scope of "peers".
2018-06-11 11:22:07 +00:00
Martynas Pumputis 2eda624653 Merge pull request #3291 from weaveworks/withnetns-inproc
Remove subprocess for changing netns
2018-06-05 16:49:37 +02:00
Martynas Pumputis 2f8aa7a351 Add random nonce to test ipset name to prevent race
As ipset.New(...) can be called by two processes (weaver and weave-npc)
at the same time, there is a possibility for a race when they both
check the comment support. To prevent the race, we append a random
nonce to the test ipset name.
2018-06-05 08:52:21 +02:00
Martynas Pumputis b6115f0147 Address the PR comments
- Track only local changes.
- Add comment to the iptables rule which prevents SNAT'ing.
- Rename the ipset to weaver-no-masq-local to prevent NPC from destroying it.
- Fix comment about create/flush WEAVE-EXPOSE.
- Add comment about reexpose being no-op if bridge hasn't been exposed.
- Add comment that the feature is not only for Kubernetes.
- Reuse the ipset instance.
2018-06-04 19:22:48 +02:00
Bryan Boreham 5ff22a1a77 Review feedback: tidied up function names 2018-06-04 13:28:28 +00:00
Martynas Pumputis 7fdf8fee13 Mention NO_MASQ_LOCAL opt in the docs 2018-06-04 14:08:24 +02:00
Martynas Pumputis 45a9ea666e Add k8s integration test case for preserving client IP addr
Also, increase timeout for smoke tests to 360 sec.
2018-06-04 14:08:24 +02:00
Martynas Pumputis efd5d0e410 Add /client_ip to network-tester
The endpoint is going to be used by --no-masq-local integration tests.
2018-06-04 14:08:24 +02:00
Martynas Pumputis 6dbe95a0b5 Add NO_MASQ_LOCAL to weave-kube/launch.sh
One can enable the feature by setting NO_MASQ_LOCAL=1 among
env vars of the weave-kube DaemonSet.
2018-06-04 14:08:24 +02:00
Martynas Pumputis a0ec06a487 Destroy weave-no-masq-local ipset upon "weave reset" 2018-06-04 14:08:24 +02:00
Martynas Pumputis ebf4b4b48a Implement and start using NoMasqLocalTracker
The tracker updates the "no-masq-local" ipset with CIDR ranges allocated
by IPAM.
2018-06-04 14:08:24 +02:00
Martynas Pumputis 4f99efaaf4 Export tracker.RemoveCommon and tracker.Merge
It's going to be used by NoMasqLocalTracker.
2018-06-04 14:08:24 +02:00
Martynas Pumputis 62c30d9130 Extern LocalRangeTracker interface with String method
Cleaner approach than passing a tracker name to ipam/http.
2018-06-04 14:08:24 +02:00
Martynas Pumputis 21fb953d0f Create weave-no-masq-local ipset
The ipset is used to track locally allocated CIDRs. If requested,
external traffic sent to pods within these CIDR ranges will
avoid SNAT'ing (NYI).
2018-06-04 14:08:24 +02:00
Martynas Pumputis 27c98070e8 Move npc/ipset package to net/ipset
It's going to be used by net/bridge.go.
2018-06-04 14:08:24 +02:00
Martynas Pumputis 039750a7fa Re-expose weave bridge in EnsureBridge
This ensures that all exposing-related iptables rules are created.

As we do the re-expose, we need to clear the WEAVE-EXPOSE chain
if such exists. Thus, we use ClearChain instead of NewChain to
create the chain.
2018-06-04 14:06:09 +02:00
Bryan Boreham 9b0d1bd054 Set Kubernetes NodeNetworkUnavailable to false when starting 2018-05-26 14:06:57 +00:00
Bryan Boreham 4c1c2a5661 Add --max-list-size option for ipset lists
and default it to 1024 for cases where the size is fixed by the kernel
2018-05-25 17:24:22 +00:00
Bryan Boreham 791d9aecfc Extend k8s test to cover namespaceSelector and >10 Namespaces 2018-05-25 17:24:22 +00:00
Bryan Boreham 48aaac90fb Merge pull request #3277 from weaveworks/ml-release-docs
Add instruction for validating whether imgs were pushed for all platforms
2018-05-23 13:51:38 +01:00
Martynas Pumputis bfaa3a3a92 Merge pull request #3278 from weaveworks/seeded-docs
Add instructions for 'seeded by different peers' message
2018-05-22 17:23:06 +02:00
Martynas Pumputis 6dce685105 Merge remote-tracking branch 'origin/2.3' 2018-05-22 17:21:03 +02:00
Martynas Pumputis 8c4e33faef Merge pull request #3301 from Nodraak/nodraak-doc
[Docs] Improve the installation page concerning firewall
2018-05-22 17:14:19 +02:00
Adrien Chardon 81582b04f9 Add firewall warning on installation page 2018-05-16 14:23:35 +02:00
Marc Carré dbcc72a6f6 Merge pull request #3299 from weaveworks/add-opencontainers-labels-to-dockerfiles
Add org.opencontainers.image.* labels to Dockerfiles
2018-05-15 09:44:39 +01:00
Marc Carré 9f207f161a Add org.opencontainers.image.* labels to Dockerfiles
- This should ultimately help for image-to-code back references.
- `org.label-schema.*` labels are now deprecated, in favour of `org.opencontainers.image.*` labels.
  See also: https://github.com/opencontainers/image-spec/blob/master/annotations.md#back-compatibility-with-label-schema
- Git revision is now injected at `docker build` time.
2018-05-14 11:51:40 +01:00
Bryan Boreham 1ee4039681 Add build constraint for go1.10 onwards 2018-05-03 07:15:36 +01:00
Bryan Boreham 3203814940 Merge pull request #3283 from weaveworks/note-ip-forward
Add note about ip forward setting
2018-05-02 13:58:32 +02:00
Bryan Boreham acc8c33260 Merge pull request #3284 from weaveworks/docs-k8s-src-ip
Add docs/k8s-src-ip.md
2018-05-02 13:58:15 +02:00
Bryan Boreham 22ecf505e3 Added note about security implications of enabling IP forwarding 2018-05-02 06:06:58 +01:00
Bryan Boreham c2c14410d2 Remove subprocess for changing netns
Now that Go 1.10 has removed the issue which made us do this.
2018-04-30 23:40:59 +01:00
Martynas Pumputis ba578c91a7 Merge pull request #3268 from weaveworks/try-dep
Convert vendoring from git submodules to copy, using 'dep'
2018-04-27 16:04:00 +02:00
Bryan Boreham 7b46391e30 Add vendor instructions 2018-04-27 14:34:49 +01:00
Bryan Boreham ae551cafea Run manifest-tool from a container image
The source code has incompatible dependencies so we can't build it.
2018-04-27 14:34:49 +01:00
Bryan Boreham 672c770dbe Rework /report handler to pull format from URL
Otherwise test 150 would fail - Gorilla mux was not handling a newline
in the format parameter.  This way you also get a better error if you
have an error in the parameter - instead of a 404.
2018-04-27 14:34:49 +01:00
Bryan Boreham 847046a225 Update to match upstream changes 2018-04-27 14:34:48 +01:00
Bryan Boreham 4da998ab45 Move to lower-case logrus import 2018-04-27 14:34:48 +01:00
Bryan Boreham b534ea60f5 Copy dependencies into vendor dir, using dep 2018-04-27 14:34:48 +01:00
Bryan Boreham 549ccff5eb Don't need to update submodules to build exes 2018-04-27 14:34:47 +01:00
Bryan Boreham 9cf6749b2e Remove vendor submodules 2018-04-27 14:34:47 +01:00
Martynas Pumputis d2999cf1d7 Add docs/k8s-src-ip.md 2018-04-24 14:02:29 +02:00
Bryan Boreham 391b55ceaf Add note about ip forward setting 2018-04-23 14:24:44 +01:00
Bryan Boreham e05345d3bb Add instructions for 'seeded by different peers' message 2018-04-17 10:24:00 +01:00
Martynas Pumputis 67bd54e55d Add instruction for validating whether imgs were pushed for all platforms 2018-04-10 12:41:24 +02:00
Martynas Pumputis 20919fd6db Add release v2.3.0 2018-04-09 14:56:31 +00:00
Martynas Pumputis ada2af04ff Merge pull request #3271 from weaveworks/metrics-addr
Only expose metrics by default in weave-kube
2018-04-09 15:05:22 +02:00
Bryan Boreham fcb6f762a9 Review feedback 2018-04-09 13:42:09 +01:00
Bryan Boreham 5645f31934 Update docs for metrics endpoints 2018-04-09 13:11:29 +01:00
Bryan Boreham a7295e2d03 Mention metrics and status addresses in the docs 2018-04-09 11:55:50 +01:00
Bryan Boreham c3e6ac2cac Merge pull request #3273 from weaveworks/go1.10.1
Use go1.10.1 for building
2018-04-09 11:40:11 +01:00
Martynas Pumputis f1f3c8c8fe Use go1.10.1 for building 2018-04-09 09:46:45 +02:00
Bryan Boreham 81b2f2e15a Merge pull request #3266 from weaveworks/test-k8s-1-10
Update tests to run against Kubernetes 1.10
2018-04-04 15:17:42 +01:00
Bryan Boreham f6fdaf6492 Only expose metrics by default in weave-kube 2018-04-04 15:00:53 +01:00
Bryan Boreham 519c5d686a Remove test for k8s 1.6 which breaks on 1.10 2018-04-04 12:26:18 +01:00
Bryan Boreham f7e62ccbcc Bump Kubernetes to 1.10 2018-04-04 12:26:09 +01:00
Bryan Boreham 19161c0c92 Merge branch '2.2' 2018-03-30 19:17:18 +00:00
Bryan Boreham c528303841 Merge pull request #3265 from weaveworks/raise-kube-conn-limit
Raise weave-kube default connection limit from 30 to 100
2018-03-28 10:20:30 +01:00
Bryan Boreham a439288765 Raise weave-kube default connection limit from 30 to 100
This was missed from 265bfcf
2018-03-27 21:06:41 +00:00
Bryan Boreham 416410912d Merge pull request #3263 from weaveworks/faq-unique-peers
Add 'peer names collision' to FAQ
2018-03-23 15:14:21 +00:00
Bryan Boreham b3f26f00e2 Add some hints how to set the unique ID 2018-03-23 14:55:54 +00:00
Bryan Boreham 90e6ecdd11 Add 'peer names collision' to FAQ 2018-03-23 14:10:05 +00:00
Marc Carré 842354bc5a Add links from email received from Docker Store. 2018-03-20 10:30:52 +00:00
Marc Carré 4d3875cec2 Add details on email for Docker Store release. 2018-03-20 10:29:50 +00:00
Marc Carré 74dfe49046 Fix format in Docker Store instructions. 2018-03-19 14:19:47 +00:00
Marc Carré d0f23ed9e0 Document Docker Store release process. 2018-03-19 11:49:40 +00:00
Martynas Pumputis 8a0992315e Merge branch '2.2' 2018-03-12 18:27:04 +01:00
Martynas Pumputis 18b5cb10dc Add release v2.2.1 2018-03-12 15:02:36 +01:00
Bryan Boreham 8afc6f208d Merge pull request #3253 from NathanHaim/issues/3245_change_golang_version
Update golang version to 1.10
2018-03-12 12:11:30 +00:00
Martynas Pumputis d35d931179 Fix invalid format in ipam/testutils_test.go
Caught after upgrading Go to 1.10 in build tools.
2018-03-12 11:04:41 +00:00
NathanHaim 093174e757 add golang:1.10.0 2018-03-11 23:08:41 +01:00
Martynas Pumputis ac78762910 Merge branch '2.2' 2018-03-08 14:38:46 +01:00
Martynas Pumputis fff656f7e4 Merge pull request #3235 from weaveworks/wait-5-sec
Wait 5 seconds to let pod connect before rolling next pod
2018-03-08 14:36:11 +01:00
Martynas Pumputis 83b36e4c76 Merge branch '2.2' 2018-03-08 14:26:32 +01:00
Martynas Pumputis b42895b5c0 Merge pull request #3250 from alok87/del_default_ipset
Delete defaultAllowIPSet on namespace delete

Fix #3247

Duplicate merge commit as the PR got merged to "master" instead of "2.2"
2018-03-08 14:22:33 +01:00
Martynas Pumputis 42e4b9addc Merge pull request #3250 from alok87/del_default_ipset
Delete defaultAllowIPSet on namespace delete

Fix #3247
2018-03-08 14:01:10 +01:00
alok87 91ab0e4a76 Clean defaultAllowIPSet on namespace delete 2018-03-05 22:52:56 +05:30
NathanHaim 51feec3785 change golang version 2018-03-05 02:46:35 +01:00
Bryan Boreham 20eee3818e Merge pull request #3236 from weaveworks/apk-ca-certs
Install ca-certificates instead of copying them
2018-02-28 17:00:31 +00:00
Bryan Boreham 94cd6226ea Merge branch '2.2' 2018-02-28 16:58:22 +00:00
Bryan Boreham 5534ecf1b1 Merge pull request #3237 from weaveworks/issues/3222-dst-selector
Fix bug when dstSelector is overwritten by srcSelector
2018-02-28 16:56:07 +00:00
Bryan Boreham d498df3933 Merge branch '2.2' 2018-02-28 10:32:45 +00:00
Bryan Boreham 75bf62ca80 Merge pull request #3234 from weaveworks/raise-conn-limit
Raise default connection limit from 30 to 100
2018-02-26 17:15:56 +00:00
Marc Carré ad42357dd6 Improve formatting. 2018-02-21 10:31:04 +00:00
Marc Carré 2de899342a Improve naming consistency. 2018-02-21 10:30:35 +00:00
Marc Carré 8df48dc69d Document how to pull submodules on first checkout: git submodule update --init 2018-02-21 10:25:33 +00:00
Marc Carré 0f2809d2e9 Document how to pull submodules on first checkout: git submodule update --init 2018-02-21 10:23:08 +00:00
Marc Carré aa44153af7 Minor typos and improvements. 2018-02-21 10:19:38 +00:00
Bryan Boreham ce9c5162bd Merge pull request #3238 from weaveworks/fix-k8s-weave-passwd-howto
Fix HOWTO for setting weave-kube passwd
2018-02-04 16:29:14 +00:00
Martynas Pumputis 5c0da56ad5 Fix HOWTO for setting weave-kube passwd
A quick fix.

LG generates secretKeyRef.key w/o the ".txt" extension which makes it
inaccessible to weave-kube when passwd is stored in "weave-passwd.txt".
2018-02-04 14:43:41 +01:00
Martynas Pumputis 08858c2a10 npc: Fix bug when dstSelector is overwritten by srcSelector
There was a bug in the npc analyser when adding a netpol with
dstSelector == srcSelector. The analyser was overwriting
dstSelector with srcSelector which resulted in Pod selected
by dstSelector not being removed from a corresponding
"default-allow" ipset.

This applies only to non-legacy netpols.
2018-02-04 13:23:38 +01:00
Martynas Pumputis 0268aeb25f Install ca-certificates instead of copying them
There is a package for alpine:
https://pkgs.alpinelinux.org/packages?name=ca-certificates&branch=v3.6
2018-02-01 14:44:00 +01:00
Bryan Boreham ea3aba8971 Wait 5 seconds to let pod connect before rolling next pod 2018-01-31 10:54:07 +00:00
Bryan Boreham 265bfcf9d7 Raise default connection limit from 30 to 100 2018-01-31 10:48:05 +00:00
3184 changed files with 960596 additions and 5996 deletions

No files matched your search

+3 -2
View File
@@ -46,8 +46,8 @@ prog/weavewait/weavewait_noop
prog/weavewait/weavewait_nomcast
prog/weaveutil/weaveutil
prog/*/Dockerfile.weaveworks
prog/kube-peers/kube-peers
prog/weave-kube/kube-peers
prog/kube-utils/kube-utils
prog/weave-kube/kube-utils
prog/weave-npc/weave-npc
prog/plugin/plugin
testing/cover/cover
@@ -68,6 +68,7 @@ test/tls/tls
test/tls/*.pem
test/coverage
test/coverage.*
test/images/network-tester/webserver
*qemu-*-static
terraform.tfstate
terraform.tfstate.backup
-177
View File
@@ -1,180 +1,3 @@
[submodule "tools"]
path = tools
url = https://github.com/weaveworks/build-tools
[submodule "vendor/github.com/vishvananda/netlink"]
path = vendor/github.com/vishvananda/netlink
url = https://github.com/vishvananda/netlink.git
[submodule "vendor/github.com/gorilla/mux"]
path = vendor/github.com/gorilla/mux
url = https://github.com/gorilla/mux.git
[submodule "vendor/github.com/gorilla/context"]
path = vendor/github.com/gorilla/context
url = https://github.com/gorilla/context.git
[submodule "vendor/github.com/google/gopacket"]
path = vendor/github.com/google/gopacket
url = https://github.com/google/gopacket.git
[submodule "vendor/github.com/weaveworks/go-odp"]
path = vendor/github.com/weaveworks/go-odp
url = https://github.com/weaveworks/go-odp.git
[submodule "vendor/github.com/opencontainers/runc"]
path = vendor/github.com/opencontainers/runc
url = https://github.com/opencontainers/runc.git
[submodule "vendor/github.com/fsouza/go-dockerclient"]
path = vendor/github.com/fsouza/go-dockerclient
url = https://github.com/fsouza/go-dockerclient.git
[submodule "vendor/github.com/docker/libnetwork"]
path = vendor/github.com/docker/libnetwork
url = https://github.com/docker/libnetwork.git
[submodule "vendor/github.com/docker/docker"]
path = vendor/github.com/docker/docker
url = https://github.com/docker/docker.git
[submodule "vendor/github.com/Sirupsen/logrus"]
path = vendor/github.com/Sirupsen/logrus
url = https://github.com/Sirupsen/logrus.git
[submodule "vendor/github.com/andybalholm/go-bit"]
path = vendor/github.com/andybalholm/go-bit
url = https://github.com/andybalholm/go-bit.git
[submodule "vendor/github.com/miekg/dns"]
path = vendor/github.com/miekg/dns
url = https://github.com/miekg/dns.git
[submodule "vendor/github.com/stretchr/testify"]
path = vendor/github.com/stretchr/testify
url = https://github.com/stretchr/testify.git
[submodule "vendor/golang.org/x/crypto"]
path = vendor/golang.org/x/crypto
url = https://github.com/golang/crypto.git
[submodule "vendor/github.com/docker/machine"]
path = vendor/github.com/docker/machine
url = https://github.com/docker/machine.git
[submodule "vendor/github.com/weaveworks/mesh"]
path = vendor/github.com/weaveworks/mesh
url = https://github.com/weaveworks/mesh
[submodule "vendor/github.com/vishvananda/netns"]
path = vendor/github.com/vishvananda/netns
url = https://github.com/vishvananda/netns.git
[submodule "vendor/github.com/boltdb/bolt"]
path = vendor/github.com/boltdb/bolt
url = https://github.com/boltdb/bolt
[submodule "vendor/github.com/weaveworks/go-checkpoint"]
path = vendor/github.com/weaveworks/go-checkpoint
url = https://github.com/weaveworks/go-checkpoint
[submodule "vendor/github.com/hashicorp/go-cleanhttp"]
path = vendor/github.com/hashicorp/go-cleanhttp
url = http://github.com/hashicorp/go-cleanhttp
[submodule "vendor/github.com/coreos/go-iptables"]
path = vendor/github.com/coreos/go-iptables
url = https://github.com/coreos/go-iptables
[submodule "vendor/github.com/pkg/profile"]
path = vendor/github.com/pkg/profile
url = https://github.com/pkg/profile
[submodule "vendor/github.com/j-keck/arping"]
path = vendor/github.com/j-keck/arping
url = https://github.com/j-keck/arping
[submodule "vendor/github.com/aws/aws-sdk-go"]
path = vendor/github.com/aws/aws-sdk-go
url = https://github.com/aws/aws-sdk-go
[submodule "vendor/golang.org/x/sys"]
path = vendor/golang.org/x/sys
url = https://go.googlesource.com/sys
[submodule "vendor/github.com/beorn7/perks"]
path = vendor/github.com/beorn7/perks
url = https://github.com/beorn7/perks
[submodule "vendor/github.com/prometheus/procfs"]
path = vendor/github.com/prometheus/procfs
url = https://github.com/prometheus/procfs
[submodule "vendor/github.com/prometheus/client_golang"]
path = vendor/github.com/prometheus/client_golang
url = https://github.com/prometheus/client_golang
[submodule "vendor/github.com/prometheus/client_model"]
path = vendor/github.com/prometheus/client_model
url = https://github.com/prometheus/client_model
[submodule "vendor/github.com/prometheus/common"]
path = vendor/github.com/prometheus/common
url = https://github.com/prometheus/common
[submodule "vendor/github.com/golang/protobuf"]
path = vendor/github.com/golang/protobuf
url = https://github.com/golang/protobuf
[submodule "vendor/github.com/matttproud/golang_protobuf_extensions"]
path = vendor/github.com/matttproud/golang_protobuf_extensions
url = https://github.com/matttproud/golang_protobuf_extensions
[submodule "vendor/k8s.io/client-go"]
path = vendor/k8s.io/client-go
url = https://github.com/kubernetes/client-go
[submodule "vendor/github.com/pkg/errors"]
path = vendor/github.com/pkg/errors
url = https://github.com/pkg/errors
[submodule "vendor/github.com/spf13/cobra"]
path = vendor/github.com/spf13/cobra
url = https://github.com/spf13/cobra
[submodule "vendor/github.com/spf13/plag"]
path = vendor/github.com/spf13/plag
url = https://github.com/spf13/pflag
[submodule "vendor/github.com/spf13/pflag"]
path = vendor/github.com/spf13/pflag
url = https://github.com/spf13/pflag
[submodule "vendor/github.com/mgutz/ansi"]
path = vendor/github.com/mgutz/ansi
url = https://github.com/mgutz/ansi
[submodule "vendor/github.com/estesp/manifest-tool"]
path = vendor/github.com/estesp/manifest-tool
url = https://github.com/estesp/manifest-tool
[submodule "vendor/github.com/weaveworks/common"]
path = vendor/github.com/weaveworks/common
url = https://github.com/weaveworks/common
[submodule "vendor/github.com/containernetworking/cni"]
path = vendor/github.com/containernetworking/cni
url = https://github.com/containernetworking/cni
[submodule "vendor/google.golang.org/grpc"]
path = vendor/google.golang.org/grpc
url = https://github.com/grpc/grpc-go
[submodule "vendor/github.com/docker/go-units"]
path = vendor/github.com/docker/go-units
url = https://github.com/docker/go-units
[submodule "vendor/golang.org/x/net"]
path = vendor/golang.org/x/net
url = https://github.com/golang/net
[submodule "vendor/k8s.io/api"]
path = vendor/k8s.io/api
url = https://github.com/kubernetes/api
[submodule "vendor/k8s.io/apimachinery"]
path = vendor/k8s.io/apimachinery
url = https://github.com/kubernetes/apimachinery
[submodule "vendor/github.com/PuerkitoBio/purell"]
path = vendor/github.com/PuerkitoBio/purell
url = https://github.com/PuerkitoBio/purell
[submodule "vendor/github.com/PuerkitoBio/urlesc"]
path = vendor/github.com/PuerkitoBio/urlesc
url = https://github.com/PuerkitoBio/urlesc
[submodule "vendor/github.com/emicklei/go-restful"]
path = vendor/github.com/emicklei/go-restful
url = https://github.com/emicklei/go-restful
[submodule "vendor/github.com/go-openapi/jsonpointer"]
path = vendor/github.com/go-openapi/jsonpointer
url = https://github.com/go-openapi/jsonpointer
[submodule "vendor/github.com/go-openapi/jsonreference"]
path = vendor/github.com/go-openapi/jsonreference
url = https://github.com/go-openapi/jsonreference
[submodule "vendor/github.com/go-openapi/spec"]
path = vendor/github.com/go-openapi/spec
url = https://github.com/go-openapi/spec
[submodule "vendor/github.com/go-openapi/swag"]
path = vendor/github.com/go-openapi/swag
url = https://github.com/go-openapi/swag
[submodule "vendor/github.com/golang/glog"]
path = vendor/github.com/golang/glog
url = https://github.com/golang/glog
[submodule "vendor/github.com/google/gofuzz"]
path = vendor/github.com/google/gofuzz
url = https://github.com/google/gofuzz
[submodule "vendor/github.com/mailru/easyjson"]
path = vendor/github.com/mailru/easyjson
url = https://github.com/mailru/easyjson
[submodule "vendor/k8s.io/kube-openapi"]
path = vendor/k8s.io/kube-openapi
url = https://github.com/kubernetes/kube-openapi
[submodule "vendor/golang.org/x/text"]
path = vendor/golang.org/x/text
url = https://go.googlesource.com/text
[submodule "vendor/gopkg.in/yaml.v2"]
path = vendor/gopkg.in/yaml.v2
url = https://gopkg.in/yaml.v2
+114
View File
@@ -1,3 +1,117 @@
## Release 2.4.1
This release fixes several bugs causing inconsistencies in IPAM for
Kubernetes users whose clusters scale up and down over time.
### Bug fixes
* Nodes unable to connect after Kubernetes addon erroneously reclaimed
node without any IP addresses #3392, #3393
* Kubernetes addon could have run out of free IP addresses after nodes
are deleted #3384, #3400
* Kubernetes addon had reduced free IP addresses due to not reclaiming
IP addresses when node name is re-used #3397
### Other improvements
* Support `--label` in `WEAVE_DOCKER_ARGS` when starting Weave #3370,#3371
* Add missing `--token` argument in help for `weave launch` #3226, #3379
* Print defunct processes after smoke-tests #3362
[Full list of changes](https://github.com/weaveworks/weave/milestone/75?closed=1)
## Release 2.4.0
This release introduces a support for Kubernetes Egress Network Policy (#2624, #3313)
and adds a mechanism for preserving the client source IP address to enable
`externalTrafficPolicy: Local` on Kubernetes (#2924, #3298).
### Bug fixes
* Increase the ipset list size which prevents weave-npc from crashing on older
kernels when more than eight Kubernetes Namespaces are used (#3289, #3305).
* Avoid a possible livelock when reclaiming IP address space in weave-kube (#3317).
* Ensure `xtables.lock` is mounted as a file so that kube-proxy can take the lock
if it has started after Weave Net (#3351, #3353).
* Upgrade the CNI plugin symlinks only if the plugin has changed (#3337, #3345).
### Other improvements
* Manipulate the Kubernetes node status `NetworkUnavailable` so that Pods can be
scheduled on nodes when the GCE cloud provider is in use (#3249, #3307, #3332, #3334).
* Refrain from creating a subprocess for configuring a network interface in
a container network namespace (#3291).
* Protect against handling the CNI plugin request with the host namespace which
prevents Weave Net from misconfiguring the host network (#3206, #3346).
* Weave Net can be run on minikube VM (#3124).
* Add `org.opencontainers.image.*` labels to Dockerfiles to improve association
of the container images with git revisions (#3299).
* Improve the error message when running `weave reset` on Kubernetes (#3319).
### Build and Testing
* Use `dep` instead of `git submodules` for managing external packages (#3268).
* Fix usage of `manifest-tool` in Makefile (#3320).
* Update Kubernetes to 1.11 for the integration tests (#3340).
### External Contributors
Thanks to the following contributors:
* @kitt1987
* @louismunro
* @Nodraak
* @stevenjohnstone
[Full list of changes](https://github.com/weaveworks/weave/milestone/73?closed=1)
## Release 2.3.0
### Security fixes
* By default, do not expose Weave "/status" and "/report" to all (0.0.0.0) when
running on Kubernetes #3271
### Other improvements
* Increase the default connection limit for Weave peers (from 30 to 100) when
running on Kubernetes, so that more peers could directly connect #3265
## Build and test
* Build Weave Net with Go 1.10.1 #3273
* Run integration tests against Kubernetes 1.10.0 #3266
[Full list of changes](https://github.com/weaveworks/weave/milestone/70?closed=1)
## Release 2.2.1
### Bug fixes
* Fix a bug in weave-npc which would allow ingress traffic to Kubernetes Pods selected
by a NetworkPolicy in which source and destination selectors were the same #3222,#3237
* Fix a bug in weave-npc which would crash if a previously deleted Kubernetes Namespace
has been created again #3247,#3250
### Other improvements
* Increase the default connection limit for Weave peers (from 30 to 100), so that
more peers could directly connect #3234
* When doing a rolling update of Weave Net on Kubernetes, allow each node five seconds
to initialize before rolling next Weave Net Pod, so that issues at startup will halt
the rollout and not spread across the whole cluster #3235
* Install common CA certificates from Alpine Linux package instead of copying
them manually #3236
### External contributors
Thanks to the following contributors:
* @alok87
[Full list of changes](https://github.com/weaveworks/weave/milestone/71?closed=1)
## Release 2.2.0
This release improves the way Weave Net configures Linux network
+19
View File
@@ -24,6 +24,25 @@ taking the time to read this guide.
* Keep pull requests small so core developers can review them quickly.
* Keep each pull request focused on a specific topic. If you have two things to change, create two pull requests. This helps reviewers to understand the meat of your contribution.
## Managing Vendored Dependencies
We copy all Go dependencies into the `vendor` subdirectory and use
[`dep`](https://github.com/golang/dep) to manage them.
If you add a dependency in the code, run `dep ensure` to include it in
the config and vendor dir. Same if you remove a dependency from the code.
To update a single dependency run `dep ensure -update github.com/foo/bar`
To override `dep`'s choice of library version, you can add
[rules](https://golang.github.io/dep/docs/daily-dep.html#rule-changes-in-gopkgtoml).
Please add a comment saying why the override is necessary and when it can be
removed.
These operations result in staged but uncommitted changes to your
branch; you will need to commit them as normal. Execute them in the
root of your checkout.
## Workflow
See [WorkingOnWeave](https://github.com/weaveworks/weave/wiki/WorkingOnWeave)
+1 -1
View File
@@ -1,3 +1,3 @@
DOCKER_VERSION=17.06
KUBERNETES_VERSION=1.9.2
KUBERNETES_VERSION=1.11.0
KUBERNETES_CNI_VERSION=0.6.0
Generated
+1045
View File
File diff suppressed because it is too large. Load diff
+29
View File
@@ -0,0 +1,29 @@
# Pin to version before sctp was added, because latest libnetwork is
# incompatible with latest sctp
[[constraint]]
name = "github.com/docker/libnetwork"
revision = "119badbd"
# Encourage dep not to use the docker-1.9 version
[[constraint]]
name = "github.com/fsouza/go-dockerclient"
version = "1"
# Use recent version: last tagged version is years old
[[constraint]]
branch = "master"
name = "github.com/prometheus/client_golang"
# For compatibility with fsouza/go-dockerclient
[[constraint]]
branch = "master"
name = "github.com/docker/docker"
# Most recent release tag is incompatible with docker/docker; see https://github.com/docker/distribution/issues/2503
[[override]]
branch = "master"
name = "github.com/docker/distribution"
[prune]
go-tests = true
unused-packages = true
+20 -23
View File
@@ -101,6 +101,7 @@ endif
# The name of the user that this Makefile should produce image artifacts for. Can/should be overridden
DOCKERHUB_USER?=weaveworks
# The default version that's chosen when pushing the images. Can/should be overridden
GIT_REVISION=$(shell git rev-parse HEAD)
WEAVE_VERSION?=git-$(shell git rev-parse --short=12 HEAD)
# Docker Store does not allow the "latest" tag.
NET_PLUGIN_LATEST=latest_release
@@ -108,20 +109,20 @@ NET_PLUGIN_LATEST=latest_release
# Paths to all relevant binaries that should be compiled
WEAVER_EXE=prog/weaver/weaver
SIGPROXY_EXE=prog/sigproxy/sigproxy
KUBEPEERS_EXE=prog/kube-peers/kube-peers
KUBEUTILS_EXE=prog/kube-utils/kube-utils
WEAVENPC_EXE=prog/weave-npc/weave-npc
WEAVEWAIT_EXE=prog/weavewait/weavewait
WEAVEWAIT_NOOP_EXE=prog/weavewait/weavewait_noop
WEAVEWAIT_NOMCAST_EXE=prog/weavewait/weavewait_nomcast
WEAVEUTIL_EXE=prog/weaveutil/weaveutil
RUNNER_EXE=tools/runner/runner
MANIFEST_TOOL_DIR=vendor/github.com/estesp/manifest-tool
MANIFEST_TOOL_EXE=$(MANIFEST_TOOL_DIR)/manifest-tool
# manifest-tool needs registry credentials; we assume here that the active user has logged in
MANIFEST_TOOL_CMD=docker run --rm -v $(HOME)/.docker:/.docker weshigbee/manifest-tool --docker-cfg=/.docker
TEST_TLS_EXE=test/tls/tls
NETWORKTESTER_EXE=test/images/network-tester/webserver
# All binaries together in a list
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(KUBEPEERS_EXE) $(WEAVENPC_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE) $(RUNNER_EXE) $(TEST_TLS_EXE) $(MANIFEST_TOOL_EXE) $(NETWORKTESTER_EXE)
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(KUBEUTILS_EXE) $(WEAVENPC_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE) $(RUNNER_EXE) $(TEST_TLS_EXE) $(NETWORKTESTER_EXE)
# These stamp files are used to mark the current state of the build; whether an image has been built or not
BUILD_UPTODATE=.build.uptodate
@@ -179,11 +180,10 @@ $(WEAVER_EXE): api/*.go plugin/*.go plugin/*/*
$(WEAVER_EXE): proxy/*.go
$(WEAVEUTIL_EXE): prog/weaveutil/*.go net/*.go plugin/net/*.go plugin/ipam/*.go db/*.go
$(SIGPROXY_EXE): prog/sigproxy/*.go
$(KUBEPEERS_EXE): prog/kube-peers/*.go
$(KUBEUTILS_EXE): prog/kube-utils/*.go
$(WEAVENPC_EXE): prog/weave-npc/*.go npc/*.go npc/*/*.go
$(TEST_TLS_EXE): test/tls/*.go
$(RUNNER_EXE): tools/runner/*.go
$(MANIFEST_TOOL_EXE): $(MANIFEST_TOOL_DIR)/*.go
$(WEAVEWAIT_NOOP_EXE): prog/weavewait/*.go
$(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go
$(WEAVEWAIT_NOMCAST_EXE): prog/weavewait/*.go net/*.go
@@ -196,9 +196,6 @@ ifeq ($(BUILD_IN_CONTAINER),true)
# This make target compiles all binaries inside of the weaveworks/build container
# It bind-mounts the source into the container and passes all important variables
exes $(EXES) tests lint: $(BUILD_UPTODATE)
git submodule update --init
# Containernetworking has another copy of vishvananda/netlink which leads to duplicate definitions
-@rm -r vendor/github.com/containernetworking/cni/vendor
@mkdir -p $(shell pwd)/.pkg
$(SUDO) docker run $(RM) $(RUN_FLAGS) \
-v $(shell pwd):/go/src/github.com/weaveworks/weave \
@@ -220,7 +217,7 @@ else
endif
$(NETGO_CHECK)
$(WEAVEUTIL_EXE) $(KUBEPEERS_EXE) $(WEAVENPC_EXE) $(NETWORKTESTER_EXE):
$(WEAVEUTIL_EXE) $(KUBEUTILS_EXE) $(WEAVENPC_EXE) $(NETWORKTESTER_EXE):
go build $(BUILD_FLAGS) -o $@ ./$(@D)
$(NETGO_CHECK)
@@ -234,7 +231,7 @@ $(WEAVEWAIT_NOMCAST_EXE):
# These programs need a separate rule as they fail the netgo check in
# the main build stanza due to not importing net package
$(SIGPROXY_EXE) $(TEST_TLS_EXE) $(WEAVEWAIT_NOOP_EXE) $(RUNNER_EXE) $(MANIFEST_TOOL_EXE):
$(SIGPROXY_EXE) $(TEST_TLS_EXE) $(WEAVEWAIT_NOOP_EXE) $(RUNNER_EXE):
go build $(BUILD_FLAGS) -o $@ ./$(@D)
tests:
@@ -274,7 +271,7 @@ endif
$(WEAVER_UPTODATE): prog/weaver/Dockerfile.$(DOCKERHUB_USER) $(WEAVER_EXE) weave $(WEAVEUTIL_EXE)
cp $(WEAVEUTIL_EXE) prog/weaver/weaveutil
cp weave prog/weaver/weave
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -f prog/weaver/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVER_IMAGE) prog/weaver
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weaver/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVER_IMAGE) prog/weaver
touch $@
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) prog/weaveexec/symlink $(SIGPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVER_UPTODATE)
@@ -282,7 +279,7 @@ $(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) prog/weaveexe
cp $(WEAVEWAIT_EXE) prog/weaveexec/weavewait
cp $(WEAVEWAIT_NOOP_EXE) prog/weaveexec/weavewait_noop
cp $(WEAVEWAIT_NOMCAST_EXE) prog/weaveexec/weavewait_nomcast
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -f prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEEXEC_IMAGE) prog/weaveexec
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEEXEC_IMAGE) prog/weaveexec
touch $@
# Builds Docker plugin.
@@ -302,17 +299,17 @@ $(PLUGIN_UPTODATE): prog/net-plugin/launch.sh prog/net-plugin/config.json $(WEAV
$(SUDO) docker plugin create $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) prog/net-plugin
touch $@
$(WEAVEKUBE_UPTODATE): prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) prog/weave-kube/launch.sh $(KUBEPEERS_EXE) $(WEAVER_UPTODATE)
cp $(KUBEPEERS_EXE) prog/weave-kube/
$(SUDO) docker build -f prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEKUBE_IMAGE) prog/weave-kube
$(WEAVEKUBE_UPTODATE): prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) prog/weave-kube/launch.sh $(KUBEUTILS_EXE) $(WEAVER_UPTODATE)
cp $(KUBEUTILS_EXE) prog/weave-kube/
$(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEKUBE_IMAGE) prog/weave-kube
touch $@
$(WEAVENPC_UPTODATE): prog/weave-npc/Dockerfile.$(DOCKERHUB_USER) $(WEAVENPC_EXE) prog/weave-npc/ulogd.conf
$(SUDO) docker build -f prog/weave-npc/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVENPC_IMAGE) prog/weave-npc
$(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weave-npc/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVENPC_IMAGE) prog/weave-npc
touch $@
$(WEAVEDB_UPTODATE): prog/weavedb/Dockerfile
$(SUDO) docker build -t $(WEAVEDB_IMAGE) prog/weavedb
$(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -t $(WEAVEDB_IMAGE) prog/weavedb
touch $@
$(NETWORKTESTER_UPTODATE): test/images/network-tester/Dockerfile $(NETWORKTESTER_EXE)
@@ -322,7 +319,7 @@ $(NETWORKTESTER_UPTODATE): test/images/network-tester/Dockerfile $(NETWORKTESTER
$(WEAVE_EXPORT): $(IMAGES_UPTODATE) $(WEAVEDB_UPTODATE)
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@
tools/.git $(MANIFEST_TOOL_DIR)/.git:
tools/.git:
git submodule update --init
# CODE FOR PUBLISHING THE IMAGES
@@ -378,13 +375,13 @@ endif
# This target pushes a manifest list; it takes one parameter, the image name.
# The variable UPDATE_LATEST controls whether it updates the versioned tag and/or the latest tag
$(PUSH_ML): push_ml_%: $(MANIFEST_TOOL_EXE)
$(PUSH_ML): push_ml_%:
ifneq ($(UPDATE_LATEST),latest-only)
$(MANIFEST_TOOL_EXE) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:$(WEAVE_VERSION) --target $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
$(MANIFEST_TOOL_CMD) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:$(WEAVE_VERSION) --target $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
endif
ifneq ($(UPDATE_LATEST),false)
# Push the manifest list to :latest as well
$(MANIFEST_TOOL_EXE) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:latest --target $(DOCKERHUB_USER)/$*:latest
$(MANIFEST_TOOL_CMD) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:latest --target $(DOCKERHUB_USER)/$*:latest
endif
clean-work-dir:
@@ -398,7 +395,7 @@ clean-bin:
clean: clean-bin clean-work-dir
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(BUILD_IMAGE)
rm -rf test/tls/*.pem test/coverage.* test/coverage $(BUILD_UPTODATE) $(MANIFEST_TOOL_EXE)
rm -rf test/tls/*.pem test/coverage.* test/coverage $(BUILD_UPTODATE)
build:
$(SUDO) go clean -i net
+15
View File
@@ -92,6 +92,7 @@ build() {
sed -i.bak -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset.yaml
sed -i.bak -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset-k8s-1.6.yaml
sed -i.bak -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset-k8s-1.7.yaml
sed -i.bak -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset-k8s-1.8.yaml
make SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER
if make tests; then
@@ -181,6 +182,13 @@ draft() {
--name "weave-daemonset-k8s-1.7.yaml" \
--file "./prog/weave-kube/weave-daemonset-k8s-1.7.yaml"
github-release upload \
--user $GITHUB_USER \
--repo weave \
--tag $LATEST_TAG \
--name "weave-daemonset-k8s-1.8.yaml" \
--file "./prog/weave-kube/weave-daemonset-k8s-1.8.yaml"
echo "** Draft $TYPE $RELEASE_NAME $VERSION created at"
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/$LATEST_TAG"
}
@@ -289,6 +297,13 @@ publish() {
--name "weave-daemonset-k8s-1.7.yaml" \
--file "./prog/weave-kube/weave-daemonset-k8s-1.7.yaml"
github-release upload \
--user $GITHUB_USER \
--repo weave \
--tag latest_release \
--name "weave-daemonset-k8s-1.8.yaml" \
--file "./prog/weave-kube/weave-daemonset-k8s-1.8.yaml"
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"
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.8.1
FROM golang:1.10.1-stretch
# Support Raspberry Pi 2 and newer
ENV GOARM 7
+1 -1
View File
@@ -44,7 +44,7 @@ test:
parallel: true
- "$SRCDIR/bin/circle-test-smoke":
parallel: true
timeout: 300
timeout: 360
post:
- "$SRCDIR/bin/circle-test-teardown":
parallel: true
+1 -1
View File
@@ -231,7 +231,7 @@ func (c *Client) EnsureNetwork(networkName, driver, subnet string, options map[s
Name: networkName,
CheckDuplicate: true,
Driver: driver,
IPAM: docker.IPAMOptions{
IPAM: &docker.IPAMOptions{
Driver: driver,
Config: []docker.IPAMConfig{{Subnet: subnet}},
},
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"log"
"strings"
"github.com/Sirupsen/logrus"
"github.com/sirupsen/logrus"
)
type textFormatter struct {
+33
View File
@@ -0,0 +1,33 @@
# Source IP addr of packet sent to k8s Service
## Setup
* Weave Net: v2.2.1.
* Kubernetes: v1.10.0.
* kube-proxy: iptables mode (default kubeadm options).
* Hosts: `h1` and `h2`.
## Source IP
\# | Src | Target | Dst | Src IP | `-j WEAVE-NPC`
---|-------------|-------------------|--------|------------------|--------------
1 | Pod_h1 | ip(Pod_h2) | Pod_h2 | ip(Pod_h1) | OK
2 | h1 | ClusterIP | Pod_h1 | ip(weave_h1) | **NOK**
3 | h1 | ClusterIP | Pod_h2 | ip(weave_h1) | OK
4 | Pod_h1 | ClusterIP | Pod_h1 | ip(weave_h1) | **NOK**
5 | Pod_h1 | ClusterIP | Pod_h2 | ip(weave_h1) | OK
6 | h1 | ip(h1):NodePort | Pod_h1 | ip(weave_h1) | **NOK**
7 | h1 | ip(h1):NodePort | Pod_h2 | ip(weave_h1) | OK
8 | h1 | ip(h2):NodePort | Pod_h1 | ip(weave_h2) | OK
9 | h1 | ip(h2):NodePort | Pod_h2 | ip(weave_h2) | ??? Can't reproduce
10 | Pod_h1 | ip(h1):NodePort | Pod_h1 | ip(weave_h1) | **NOK**
11 | Pod_h1 | ip(h1):NodePort | Pod_h2 | ip(weave_h1) | OK
12 | Pod_h1 | ip(h2):NodePort | Pod_h1 | ip(weave_h2) | OK
13 | Pod_h1 | ip(h2):NodePort | Pod_h2 | ip(weave_h2) | OK
Remarks:
* **Src IP** is of a packet which is captured on the weave bridge.
* **-j WEAVE-NPC** - whether a packet enters the `filter/WEAVE-NPC` iptables chain (OK = NetworkPolicy is enforced as required).
* **Pod_h1** - a Pod running on the `h1` host.
* **ip(weave_h1)** - IP addr of the weave bridge on the `h1` host.
+38
View File
@@ -127,6 +127,44 @@ Finally, for **Mainline** releases only:
* Images tagged `latest` are updated on DockerHub
Now, you can validate whether images were published for all platforms:
# Required platforms for each image:
grep '^ML_PLATFORMS=' Makefile
# Published platforms per image:
for img in $(grep '^PUBLISH=' Makefile); do
img="weaveworks/$(echo $img | cut -d_ -f2):${TAG#v}"
platforms=$(vendor/github.com/estesp/manifest-tool/manifest-tool \
inspect --raw "$img" | \
jq '.[].Platform | .os + "/" +.architecture' | \
tr '\n' ' ')
echo "$img: $platforms"
done
### Docker Store
Weave Net is [available in Docker Store](https://store.docker.com/plugins/weave-net-plugin) and should also be released there.
* Go to https://store.docker.com/ and log in.
* Go to "[_Publish_](https://store.docker.com/publisher/center)".
* Under "_My Products_", select "_owners (weaveworks)_". Weave Net should be listed among our products.
* Click "_Actions_" > "_Edit Product_".
* Go to "_Plans & Pricing_" > "_Free Tier_".
* Under "_Source Repositories & Tags_", click on "_Add Source_", select "_net-plugin_" under "_Repository_" and the version to release under "_Tag_".
* Under "_Resources_" > "_Installation Instructions_", update the lines containing `export NET_VERSION=<version>`.
* Click "_Submit For Review_". You should see a message like: "_Your product has been submitted for approval. [...] We'll be in touch with next steps soon!_".
* You should receive an email saying:
> This email confirms that we received your submission on \<date\> of Weave Net to the Docker Store.
>
> We're reviewing your submission to ensure that it meets our [security guidelines](https://success.docker.com/Store#Security_and_Audit_Policies) and complies with our [best practices](https://success.docker.com/Store#Create_Great_Content). Don't worry! We'll let you know if there's anything you need to change before we publish your submission. You should hear back from us within the next 14 days.
>
> Thanks for submitting your content to the Docker Store!
* Hope Docker eventually performs the release or contacts us. If nothing happens within 14 days, contact their support team: publisher-support@docker.com.
### Finish up
* If not on master, merge branch into master and push to GitHub.
+2
View File
@@ -74,6 +74,7 @@ type Allocator struct {
isKnownPeer func(mesh.PeerName) bool
quorum func() uint
now func() time.Time
tracker tracker.LocalRangeTracker
}
// PreClaims are IP addresses discovered before we could initialize IPAM
@@ -130,6 +131,7 @@ func NewAllocator(config Config) *Allocator {
quorum: config.Quorum,
dead: make(map[string]time.Time),
now: time.Now,
tracker: config.Tracker,
}
alloc.pendingClaims = make([]operation, len(config.PreClaims))
+2 -2
View File
@@ -88,7 +88,7 @@ func (alloc *Allocator) handleHTTPClaim(dockerCli *docker.Client, w http.Respons
}
// HandleHTTP wires up ipams HTTP endpoints to the provided mux.
func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, tracker string, dockerCli *docker.Client) {
func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, dockerCli *docker.Client) {
router.Methods("GET").Path("/ipinfo/defaultsubnet").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s", defaultSubnet)
})
@@ -199,6 +199,6 @@ func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CID
})
router.Methods("GET").Path("/ipinfo/tracker").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, tracker)
fmt.Fprintf(w, alloc.tracker.String())
})
}
+2 -2
View File
@@ -43,7 +43,7 @@ func listenHTTP(alloc *Allocator, subnet address.CIDR) int {
router.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, fmt.Sprintln(alloc))
})
alloc.HandleHTTP(router, subnet, "", nil)
alloc.HandleHTTP(router, subnet, nil)
httpListener, err := net.Listen("tcp", ":0")
if err != nil {
@@ -132,7 +132,7 @@ func TestBadHttp(t *testing.T) {
// Verb that's not handled
resp, err := doHTTP("HEAD", fmt.Sprintf("http://localhost:%d/ip/%s/%s", port, containerID, testAddr1))
require.NoError(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode, "http response")
require.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode, "http response")
// Mis-spelled URL
resp, err = doHTTP("POST", fmt.Sprintf("http://localhost:%d/xip/%s/", port, containerID))
require.NoError(t, err)
+1 -1
View File
@@ -117,7 +117,7 @@ func CheckAllExpectedMessagesSent(allocs ...*Allocator) {
m := alloc.gossip.(*mockGossipComms)
m.RLock()
if len(m.messages) > 0 {
require.FailNow(m, fmt.Sprintf("%s: Gossip message(s) not sent as expected: \n%x", m.name, m.messages))
require.FailNow(m, fmt.Sprintf("%s: Gossip message(s) not sent as expected: \n%v", m.name, m.messages))
}
m.RUnlock()
}
+5 -42
View File
@@ -84,7 +84,7 @@ func NewAWSVPCTracker(bridgeName string) (*AWSVPCTracker, error) {
func (t *AWSVPCTracker) HandleUpdate(prevRanges, currRanges []address.Range, local bool) error {
t.debugf("replacing %q by %q; local(%t)", prevRanges, currRanges, local)
prev, curr := removeCommon(address.NewCIDRs(merge(prevRanges)), address.NewCIDRs(merge(currRanges)))
prev, curr := RemoveCommon(address.NewCIDRs(Merge(prevRanges)), address.NewCIDRs(Merge(currRanges)))
// It might make sense to do the removal first and then add entries
// because of the 50 routes limit. However, in such case a container might
@@ -123,6 +123,10 @@ func (t *AWSVPCTracker) HandleUpdate(prevRanges, currRanges []address.Range, loc
return nil
}
func (t *AWSVPCTracker) String() string {
return "awsvpc"
}
func (t *AWSVPCTracker) createVPCRoute(cidr string) (*ec2.CreateRouteOutput, error) {
route := &ec2.CreateRouteInput{
RouteTableId: &t.routeTableID,
@@ -232,47 +236,6 @@ func (t *AWSVPCTracker) infof(fmt string, args ...interface{}) {
// Helpers
// merge merges adjacent range entries.
// The given slice has to be sorted in increasing order.
func merge(r []address.Range) []address.Range {
var merged []address.Range
for i := range r {
if prev := len(merged) - 1; prev >= 0 && merged[prev].End == r[i].Start {
merged[prev].End = r[i].End
} else {
merged = append(merged, r[i])
}
}
return merged
}
// removeCommon filters out CIDR ranges which are contained in both a and b slices.
// Both slices have to be sorted in increasing order.
func removeCommon(a, b []address.CIDR) (newA, newB []address.CIDR) {
i, j := 0, 0
for i < len(a) && j < len(b) {
switch {
case a[i].Start() < b[j].Start() || a[i].End() < b[j].End():
newA = append(newA, a[i])
i++
case a[i].Start() > b[j].Start() || a[i].End() > b[j].End():
newB = append(newB, b[j])
j++
default:
i++
j++
}
}
newA = append(newA, a[i:]...)
newB = append(newB, b[j:]...)
return
}
func parseCIDR(cidr string) (*net.IPNet, error) {
ip, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
+46
View File
@@ -0,0 +1,46 @@
package tracker
import (
"github.com/weaveworks/weave/net/address"
)
// Merge merges adjacent range entries.
// The given slice has to be sorted in increasing order.
func Merge(r []address.Range) []address.Range {
var merged []address.Range
for i := range r {
if prev := len(merged) - 1; prev >= 0 && merged[prev].End == r[i].Start {
merged[prev].End = r[i].End
} else {
merged = append(merged, r[i])
}
}
return merged
}
// RemoveCommon filters out CIDR ranges which are contained in both a and b slices.
// Both slices have to be sorted in increasing order.
func RemoveCommon(a, b []address.CIDR) (newA, newB []address.CIDR) {
i, j := 0, 0
for i < len(a) && j < len(b) {
switch {
case a[i].Start() < b[j].Start() || a[i].End() < b[j].End():
newA = append(newA, a[i])
i++
case a[i].Start() > b[j].Start() || a[i].End() > b[j].End():
newB = append(newB, b[j])
j++
default:
i++
j++
}
}
newA = append(newA, a[i:]...)
newB = append(newB, b[j:]...)
return
}
@@ -19,7 +19,7 @@ var (
func TestRemoveCommon(t *testing.T) {
a := []address.CIDR{r0to127, r1dot0to255}
b := []address.CIDR{r1dot0to255, r2dot0to255}
newA, newB := removeCommon(a, b)
newA, newB := RemoveCommon(a, b)
require.Equal(t, []address.CIDR{r0to127}, newA)
require.Equal(t, []address.CIDR{r2dot0to255}, newB)
}
@@ -30,7 +30,7 @@ func TestMerge(t *testing.T) {
r128to255.Range(),
r2dot0to255.Range(),
}
require.Equal(t, []address.Range{r0to255.Range(), r2dot0to255.Range()}, merge(ranges))
require.Equal(t, []address.Range{r0to255.Range(), r2dot0to255.Range()}, Merge(ranges))
}
// Helper
+3
View File
@@ -17,4 +17,7 @@ type LocalRangeTracker interface {
// The local parameter indicates whether the ranges belong to the peer
// by which the method is called.
HandleUpdate(prevRanges, currRanges []address.Range, local bool) error
// String returns the tracker name
String() string
}
+1 -1
View File
@@ -10,7 +10,7 @@ import (
// Configure the ARP cache parameters for the given interface. This
// makes containers react more quickly to a change in the MAC address
// associated with an IP address.
func ConfigureARPCache(procPath, name string) error {
func configureARPCache(procPath, name string) error {
if err := sysctl(procPath, fmt.Sprintf("net/ipv4/neigh/%s/base_reachable_time", name), "5"); err != nil {
return err
}
+131 -51
View File
@@ -6,12 +6,18 @@ import (
"net"
"path/filepath"
"github.com/Sirupsen/logrus"
"github.com/coreos/go-iptables/iptables"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
"k8s.io/apimachinery/pkg/types"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/common/odp"
"github.com/weaveworks/weave/ipam/tracker"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/net/ipset"
"github.com/weaveworks/weave/npc"
)
/* This code implements three possible configurations to connect
@@ -47,11 +53,12 @@ datapath of old kernel versions (https://github.com/weaveworks/weave/issues/1577
*/
const (
WeaveBridgeName = "weave"
DatapathName = "datapath"
DatapathIfName = "vethwe-datapath"
BridgeIfName = "vethwe-bridge"
PcapIfName = "vethwe-pcap"
WeaveBridgeName = "weave"
DatapathName = "datapath"
DatapathIfName = "vethwe-datapath"
BridgeIfName = "vethwe-bridge"
PcapIfName = "vethwe-pcap"
NoMasqLocalIpset = ipset.Name("weaver-no-masq-local")
)
type Bridge interface {
@@ -213,6 +220,7 @@ type BridgeConfig struct {
MTU int
Mac string
Port int
NoMasqLocal bool
}
func (config *BridgeConfig) configuredBridgeType() Bridge {
@@ -226,7 +234,7 @@ func (config *BridgeConfig) configuredBridgeType() Bridge {
}
}
func EnsureBridge(procPath string, config *BridgeConfig, log *logrus.Logger) (Bridge, error) {
func EnsureBridge(procPath string, config *BridgeConfig, log *logrus.Logger, ips ipset.Interface) (Bridge, error) {
existingBridgeType, err := ExistingBridgeType(config.WeaveBridgeName, config.DatapathName)
if err != nil {
return nil, err
@@ -252,7 +260,7 @@ func EnsureBridge(procPath string, config *BridgeConfig, log *logrus.Logger) (Br
break
}
if err := configureIPTables(config); err != nil {
if err := configureIPTables(config, ips); err != nil {
return bridgeType, errors.Wrap(err, "configuring iptables")
}
@@ -275,10 +283,16 @@ func EnsureBridge(procPath string, config *BridgeConfig, log *logrus.Logger) (Br
return bridgeType, err
}
if err := ConfigureARPCache(procPath, config.WeaveBridgeName); err != nil {
if err := configureARPCache(procPath, config.WeaveBridgeName); err != nil {
return bridgeType, errors.Wrapf(err, "configuring ARP cache on bridge %q", config.WeaveBridgeName)
}
// NB: No concurrent call to Expose is possible, as EnsureBridge is called
// before any service has been started.
if err := reexpose(config, log); err != nil {
return bridgeType, err
}
return bridgeType, nil
}
@@ -404,7 +418,7 @@ func (f fastdpImpl) attach(veth *netlink.Veth) error {
return odp.AddDatapathInterfaceIfNotExist(f.datapathName, veth.Attrs().Name)
}
func configureIPTables(config *BridgeConfig) error {
func configureIPTables(config *BridgeConfig, ips ipset.Interface) error {
ipt, err := iptables.New()
if err != nil {
return errors.Wrap(err, "creating iptables object")
@@ -444,14 +458,27 @@ func configureIPTables(config *BridgeConfig) error {
}
if config.NPC {
// Steer traffic via the NPC
_ = ipt.NewChain("filter", "WEAVE-NPC") // ignore error because it might already exist
// If WEAVE-NPC chain doesn't exist then creating a rule in the chain will fail
// Steer traffic via the NPC.
if err = ensureChains(ipt, "filter", npc.MainChain, npc.EgressChain); err != nil {
return err
}
// Steer egress traffic destined to local node.
if err = ipt.AppendUnique("filter", "INPUT", "-i", config.WeaveBridgeName, "-j", npc.EgressChain); err != nil {
return err
}
fwdRules = append(fwdRules,
[][]string{
// Might include ingress traffic which is fine as long as we do not
// ACCEPT in WEAVE-NPC-EGRESS chain
{"-i", config.WeaveBridgeName,
"-m", "comment", "--comment", "NOTE: this must go before '-j KUBE-FORWARD'",
"-j", npc.EgressChain},
// The following rules are for ingress NPC processing
{"-o", config.WeaveBridgeName,
"-m", "comment", "--comment", "NOTE: this must go before '-j KUBE-FORWARD'",
"-j", "WEAVE-NPC"},
"-j", npc.MainChain},
{"-o", config.WeaveBridgeName, "-m", "state", "--state", "NEW", "-j", "NFLOG", "--nflog-group", "86"},
{"-o", config.WeaveBridgeName, "-j", "DROP"},
}...)
@@ -462,8 +489,11 @@ func configureIPTables(config *BridgeConfig) error {
}
if !config.NPC {
// Create a chain for allowing ingress traffic when the bridge is exposed
_ = ipt.NewChain("filter", "WEAVE-EXPOSE")
// Create/Flush a chain for allowing ingress traffic when the bridge is exposed
if err := ipt.ClearChain("filter", "WEAVE-EXPOSE"); err != nil {
return errors.Wrap(err, "failed to clear/create filter/WEAVE-EXPOSE chain")
}
fwdRules = append(fwdRules, []string{"-o", config.WeaveBridgeName, "-j", "WEAVE-EXPOSE"})
}
@@ -476,13 +506,74 @@ func configureIPTables(config *BridgeConfig) error {
return err
}
// create a chain for masquerading
//
// NB: we do not clear the chain to preserve existing rules
// inserted by "weave expose".
_ = ipt.NewChain("nat", "WEAVE")
// Create a chain for masquerading
if err := ipt.ClearChain("nat", "WEAVE"); err != nil {
return errors.Wrap(err, "failed to clear/create nat/WEAVE chain")
}
if err := ipt.AppendUnique("nat", "POSTROUTING", "-j", "WEAVE"); err != nil {
return err
}
return ipt.AppendUnique("nat", "POSTROUTING", "-j", "WEAVE")
// For the cases where the weave bridge is the default gateway for
// containers (e.g. Kubernetes): create the ipset to store CIDRs allocated
// by IPAM for local containers. In the case of Kubernetes, external traffic
// sent to these CIDRs avoids SNAT'ing so that NodePort with
// `"externalTrafficPolicy":"Local"` would receive packets with correct
// src IP addr.
if config.NoMasqLocal {
ips := ipset.New(common.LogLogger(), 0)
_ = ips.Destroy(NoMasqLocalIpset)
if err := ips.Create(NoMasqLocalIpset, ipset.HashNet); err != nil {
return err
}
if err := ipt.Insert("nat", "WEAVE", 1,
"-m", "set", "--match-set", string(NoMasqLocalIpset), "dst",
"-m", "comment", "--comment", "Prevent SNAT to locally running containers",
"-j", "RETURN"); err != nil {
return err
}
}
return nil
}
type NoMasqLocalTracker struct {
ips ipset.Interface
owner types.UID
}
func NewNoMasqLocalTracker(ips ipset.Interface) *NoMasqLocalTracker {
return &NoMasqLocalTracker{
ips: ips,
owner: types.UID(0), // dummy ipset owner
}
}
func (t *NoMasqLocalTracker) String() string {
return "no-masq-local"
}
func (t *NoMasqLocalTracker) HandleUpdate(prevRanges, currRanges []address.Range, local bool) error {
if !local {
return nil
}
prev, curr := tracker.RemoveCommon(
address.NewCIDRs(tracker.Merge(prevRanges)),
address.NewCIDRs(tracker.Merge(currRanges)))
for _, cidr := range curr {
if err := t.ips.AddEntry(t.owner, NoMasqLocalIpset, cidr.String(), ""); err != nil {
return err
}
}
for _, cidr := range prev {
if err := t.ips.DelEntry(t.owner, NoMasqLocalIpset, cidr.String()); err != nil {
return err
}
}
return nil
}
func linkSetUpByName(linkName string) error {
@@ -493,37 +584,26 @@ func linkSetUpByName(linkName string) error {
return netlink.LinkSetUp(link)
}
// ensureRulesAtTop ensures the presence of given iptables rules.
//
// If any rule from the list is missing, the function deletes all given
// rules and re-inserts them at the top of the chain to ensure the order of the rules.
func ensureRulesAtTop(table, chain string, rulespecs [][]string, ipt *iptables.IPTables) error {
allFound := true
for _, rs := range rulespecs {
found, err := ipt.Exists(table, chain, rs...)
if err != nil {
return errors.Wrapf(err, "ipt.Exists(%s, %s, %s)", table, chain, rs)
}
if !found {
allFound = false
break
}
func reexpose(config *BridgeConfig, log *logrus.Logger) error {
// Get existing IP addrs of the weave bridge.
// If the bridge hasn't been exposed, then this functions does nothing.
//
// Ideally, we should consult IPAM for IP addrs allocated to "weave:expose",
// but we don't want to introduce dependency on IPAM, as weave should be able
// to run w/o IPAM.
link, err := netlink.LinkByName(config.WeaveBridgeName)
if err != nil {
return errors.Wrapf(err, "cannot find bridge %q", config.WeaveBridgeName)
}
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
if err != nil {
return errors.Wrapf(err, "cannot list IPv4 addrs of bridge %q", config.WeaveBridgeName)
}
// All rules exist, do nothing.
if allFound {
return nil
}
for pos, rs := range rulespecs {
// If any is missing, then delete all, as we need to preserve the order of
// given rules. Ignore errors, as rule might not exist.
if !allFound {
ipt.Delete(table, chain, rs...)
}
if err := ipt.Insert(table, chain, pos+1, rs...); err != nil {
return errors.Wrapf(err, "ipt.Append(%s, %s, %s)", table, chain, rs)
for _, addr := range addrs {
log.Infof("Re-exposing %s on bridge %q", addr.IPNet, config.WeaveBridgeName)
if err := Expose(config.WeaveBridgeName, addr.IPNet, config.AWSVPC, config.NPC); err != nil {
return errors.Wrapf(err, "unable to re-expose %s on bridge: %q", addr.IPNet, config.WeaveBridgeName)
}
}
+1 -1
View File
@@ -12,9 +12,9 @@ import (
"sync"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/coreos/go-iptables/iptables"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
"golang.org/x/crypto/hkdf"
+18 -2
View File
@@ -1,6 +1,9 @@
package ipset
import (
"crypto/rand"
"encoding/hex"
"fmt"
"log"
"os/exec"
"strings"
@@ -16,6 +19,7 @@ type Type string
const (
ListSet = Type("list:set")
HashIP = Type("hash:ip")
HashNet = Type("hash:net")
)
type Interface interface {
@@ -40,6 +44,7 @@ type entryKey struct {
type ipset struct {
*log.Logger
enableComments bool
maxListSize int
// List of users per ipset entry. User is either a namespace or a pod.
// There might be multiple users for the same ipset & entry pair because
// events from k8s API server might be out of order causing duplicate IPs:
@@ -47,15 +52,23 @@ type ipset struct {
users map[entryKey]map[types.UID]struct{}
}
func New(logger *log.Logger) Interface {
func New(logger *log.Logger, maxListSize int) Interface {
ips := &ipset{
Logger: logger,
enableComments: true,
maxListSize: maxListSize,
users: make(map[entryKey]map[types.UID]struct{}),
}
// Check for comment support
testIpsetName := Name("weave-test-comment")
// To prevent from a race when more than one process check for the support
// we append a random nonce to the test ipset name. The final name is
// shorter than 31 chars (max ipset name).
nonce := make([]byte, 4)
rand.Read(nonce)
testIpsetName := Name("weave-test-comment" + hex.EncodeToString(nonce))
// Clear it out if it already exists
_ = ips.Destroy(testIpsetName)
// Test for comment support
@@ -71,6 +84,9 @@ func New(logger *log.Logger) Interface {
func (i *ipset) Create(ipsetName Name, ipsetType Type) error {
args := []string{"create", string(ipsetName), string(ipsetType)}
if ipsetType == ListSet && i.maxListSize > 0 {
args = append(args, "size", fmt.Sprintf("%d", i.maxListSize))
}
if i.enableComments {
args = append(args, "comment")
}
+106
View File
@@ -0,0 +1,106 @@
package net
import (
"strings"
"github.com/coreos/go-iptables/iptables"
"github.com/pkg/errors"
)
// AddChainWithRules creates a chain and appends given rules to it.
//
// If the chain exists, but its rules are not the same as the given ones, the
// function will flush the chain and then will append the rules.
func AddChainWithRules(ipt *iptables.IPTables, table, chain string, rulespecs [][]string) error {
if err := ensureChains(ipt, table, chain); err != nil {
return err
}
currRuleSpecs, err := ipt.List(table, chain)
if err != nil {
return errors.Wrapf(err, "iptables -S. table: %q, chain: %q", table, chain)
}
// First returned rule is "-N $(chain)", so ignore it
currRules := strings.Join(currRuleSpecs[1:], "\n")
rules := make([]string, 0)
for _, r := range rulespecs {
rules = append(rules, strings.Join(r, " "))
}
reqRules := strings.Join(rules, "\n")
if currRules == reqRules {
return nil
}
if err := ipt.ClearChain(table, chain); err != nil {
return err
}
for _, r := range rulespecs {
if err := ipt.Append(table, chain, r...); err != nil {
return errors.Wrapf(err, "iptables -A. table: %q, chain: %q, rule: %s", table, chain, r)
}
}
return nil
}
// ensureChains creates given chains if they do not exist.
func ensureChains(ipt *iptables.IPTables, table string, chains ...string) error {
existingChains, err := ipt.ListChains(table)
if err != nil {
return errors.Wrapf(err, "ipt.ListChains(%s)", table)
}
chainMap := make(map[string]struct{})
for _, c := range existingChains {
chainMap[c] = struct{}{}
}
for _, c := range chains {
if _, found := chainMap[c]; !found {
if err := ipt.NewChain(table, c); err != nil {
return errors.Wrapf(err, "ipt.NewChain(%s, %s)", table, c)
}
}
}
return nil
}
// ensureRulesAtTop ensures the presence of given iptables rules.
//
// If any rule from the list is missing, the function deletes all given
// rules and re-inserts them at the top of the chain to ensure the order of the rules.
func ensureRulesAtTop(table, chain string, rulespecs [][]string, ipt *iptables.IPTables) error {
allFound := true
for _, rs := range rulespecs {
found, err := ipt.Exists(table, chain, rs...)
if err != nil {
return errors.Wrapf(err, "ipt.Exists(%s, %s, %s)", table, chain, rs)
}
if !found {
allFound = false
break
}
}
// All rules exist, do nothing.
if allFound {
return nil
}
for pos, rs := range rulespecs {
// If any is missing, then delete all, as we need to preserve the order of
// given rules. Ignore errors, as rule might not exist.
if !allFound {
ipt.Delete(table, chain, rs...)
}
if err := ipt.Insert(table, chain, pos+1, rs...); err != nil {
return errors.Wrapf(err, "ipt.Append(%s, %s, %s)", table, chain, rs)
}
}
return nil
}
+25 -13
View File
@@ -1,11 +1,9 @@
package net
import (
"encoding/json"
"fmt"
"net"
"os"
"strconv"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
@@ -17,7 +15,7 @@ type Dev struct {
CIDRs []*net.IPNet `json:"CIDRs,omitempty"`
}
func LinkToNetDev(link netlink.Link) (Dev, error) {
func linkToNetDev(link netlink.Link) (Dev, error) {
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
if err != nil {
return Dev{}, err
@@ -90,16 +88,30 @@ func GetNetDevsByVethPeerIds(processID int, peerIDs []int) ([]Dev, error) {
return nil, nil
}
// convert list of peerIDs into a map for faster lookup
indexes := make(map[int]struct{})
for _, id := range peerIDs {
indexes[id] = struct{}{}
}
var netdevs []Dev
peersStr := make([]string, len(peerIDs))
for i, id := range peerIDs {
peersStr[i] = strconv.Itoa(id)
}
nds, err := WithNetNSByPid(processID, "list-netdevs", peersStr...)
if err != nil {
return nil, fmt.Errorf("list-netdevs failed: %s", err)
}
err = json.Unmarshal(nds, &netdevs)
err = WithNetNS(netnsContainer, func() error {
links, err := netlink.LinkList()
if err != nil {
return err
}
for _, link := range links {
if _, found := indexes[link.Attrs().Index]; found {
netdev, err := linkToNetDev(link)
if err != nil {
return err
}
netdevs = append(netdevs, netdev)
}
}
return nil
})
return netdevs, err
}
@@ -112,5 +124,5 @@ func GetBridgeNetDev(bridgeName string) (Dev, error) {
return Dev{}, err
}
return LinkToNetDev(link)
return linkToNetDev(link)
}
+10 -36
View File
@@ -1,10 +1,10 @@
// +build go1.10
package net
import (
"bytes"
"errors"
"fmt"
"os/exec"
"path/filepath"
"runtime"
@@ -14,19 +14,7 @@ import (
var ErrLinkNotFound = errors.New("Link not found")
// NB: The following function is unsafe, because:
// - It changes a network namespace (netns) of an OS thread which runs
// the function. During execution, the Go runtime might clone a new OS thread
// for scheduling other go-routines, thus they might end up running in
// a "wrong" netns.
// - runtime.LockOSThread does not guarantee that a spawned go-routine on
// the locked thread will be run by it. Thus, the work function is
// not allowed to spawn any go-routine which is dependent on the given netns.
// Please see https://github.com/weaveworks/weave/issues/2388#issuecomment-228365069
// for more details and make sure that you understand the implications before
// using the function!
func WithNetNSUnsafe(ns netns.NsHandle, work func() error) error {
func WithNetNS(ns netns.NsHandle, work func() error) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
@@ -45,8 +33,8 @@ func WithNetNSUnsafe(ns netns.NsHandle, work func() error) error {
return err
}
func WithNetNSLinkUnsafe(ns netns.NsHandle, ifName string, work func(link netlink.Link) error) error {
return WithNetNSUnsafe(ns, func() error {
func WithNetNSLink(ns netns.NsHandle, ifName string, work func(link netlink.Link) error) error {
return WithNetNS(ns, func() error {
link, err := netlink.LinkByName(ifName)
if err != nil {
if err.Error() == errors.New("Link not found").Error() {
@@ -58,26 +46,12 @@ func WithNetNSLinkUnsafe(ns netns.NsHandle, ifName string, work func(link netlin
})
}
var WeaveUtilCmd = "weaveutil"
// A safe version of WithNetNS* which creates a process executing
// "nsenter --net=<ns-path> weaveutil <cmd> [args]".
func WithNetNS(nsPath string, cmd string, args ...string) ([]byte, error) {
var stdout, stderr bytes.Buffer
args = append([]string{"--net=" + nsPath, WeaveUtilCmd, cmd}, args...)
c := exec.Command("nsenter", args...)
c.Stdout = &stdout
c.Stderr = &stderr
if err := c.Run(); err != nil {
return nil, fmt.Errorf("%s: %s", string(stderr.Bytes()), err)
func WithNetNSByPath(path string, work func() error) error {
ns, err := netns.GetFromPath(path)
if err != nil {
return err
}
return stdout.Bytes(), nil
}
func WithNetNSByPid(pid int, cmd string, args ...string) ([]byte, error) {
return WithNetNS(NSPathByPid(pid), cmd, args...)
return WithNetNS(ns, work)
}
func NSPathByPid(pid int) string {
+36 -17
View File
@@ -104,7 +104,10 @@ const (
)
func interfaceExistsInNamespace(netNSPath string, ifName string) bool {
_, err := WithNetNS(netNSPath, "check-iface", ifName)
err := WithNetNSByPath(netNSPath, func() error {
_, err := netlink.LinkByName(ifName)
return err
})
return err == nil
}
@@ -125,7 +128,9 @@ func AttachContainer(netNSPath, id, ifName, bridgeName string, mtu int, withMult
if err := netlink.LinkSetNsFd(veth, int(ns)); err != nil {
return fmt.Errorf("failed to move veth to container netns: %s", err)
}
if _, err := WithNetNS(netNSPath, "setup-iface", peerName, ifName); err != nil {
if err := WithNetNS(ns, func() error {
return setupIface(peerName, ifName)
}); err != nil {
return fmt.Errorf("error setting up interface: %s", err)
}
return nil
@@ -139,19 +144,16 @@ func AttachContainer(netNSPath, id, ifName, bridgeName string, mtu int, withMult
}
args := []string{ifName, fmt.Sprintf("%t", withMulticastRoute)}
for _, cidr := range cidrs {
args = append(args, cidr.String())
}
if _, err := WithNetNS(netNSPath, "setup-iface-addrs", args...); err != nil {
if err := WithNetNSLink(ns, ifName, func(veth netlink.Link) error {
return setupIfaceAddrs(veth, withMulticastRoute, cidrs)
}); err != nil {
return fmt.Errorf("error setting up interface addresses: %s", err)
}
return nil
}
// SetupIfaceAddrs is the implementation of the 'setup-iface-addrs' call above,
// running in another process in the container's netns
func SetupIfaceAddrs(veth netlink.Link, withMulticastRoute bool, cidrs []*net.IPNet) error {
// setupIfaceAddrs expects to be called in the container's netns
func setupIfaceAddrs(veth netlink.Link, withMulticastRoute bool, cidrs []*net.IPNet) error {
newAddresses, err := AddAddresses(veth, cidrs)
if err != nil {
return err
@@ -203,9 +205,8 @@ func SetupIfaceAddrs(veth netlink.Link, withMulticastRoute bool, cidrs []*net.IP
return nil
}
// SetupIface is the implementation of the 'setup-iface' call above,
// running in another process in the container's netns
func SetupIface(ifaceName, newIfName string) error {
// setupIface expects to be called in the container's netns
func setupIface(ifaceName, newIfName string) error {
ipt, err := iptables.New()
if err != nil {
return err
@@ -219,14 +220,32 @@ func SetupIface(ifaceName, newIfName string) error {
return err
}
// This is only called by AttachContainer which is only called in host pid namespace
if err := ConfigureARPCache("/proc", newIfName); err != nil {
if err := configureARPCache("/proc", newIfName); err != nil {
return err
}
return ipt.Append("filter", "INPUT", "-i", newIfName, "-d", "224.0.0.0/4", "-j", "DROP")
}
// NB: This function can be used only by a process that terminates immediately
// after calling the function as it changes netns via WithNetNSLinkUnsafe.
// configureARP is a helper for the Docker plugin which doesn't set the addresses itself
func ConfigureARP(prefix, rootPath string) error {
links, err := netlink.LinkList()
if err != nil {
return err
}
for _, link := range links {
ifName := link.Attrs().Name
if strings.HasPrefix(ifName, prefix) {
configureARPCache(rootPath+"/proc", ifName)
if addrs, err := netlink.AddrList(link, netlink.FAMILY_V4); err == nil {
for _, addr := range addrs {
arping.GratuitousArpOverIfaceByName(addr.IPNet.IP, ifName)
}
}
}
}
return nil
}
func DetachContainer(netNSPath, id, ifName string, cidrs []*net.IPNet) error {
ns, err := netns.GetFromPath(netNSPath)
if err != nil {
@@ -239,7 +258,7 @@ func DetachContainer(netNSPath, id, ifName string, cidrs []*net.IPNet) error {
return err
}
return WithNetNSLinkUnsafe(ns, ifName, func(veth netlink.Link) error {
return WithNetNSLink(ns, ifName, func(veth netlink.Link) error {
existingAddrs, err := netlink.AddrList(veth, netlink.FAMILY_V4)
if err != nil {
return fmt.Errorf("failed to get IP address for %q: %v", veth.Attrs().Name, err)
+124 -134
View File
@@ -4,173 +4,163 @@ import (
"fmt"
apiv1 "k8s.io/api/core/v1"
extnapi "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"github.com/weaveworks/weave/npc/ipset"
"github.com/weaveworks/weave/net/ipset"
)
// analysePolicyLegacy is used to analyse extensions/v1beta1/NetworkPolicy (legacy) and
// implements pre-1.7 k8s netpol semantics, whilst analysePolicy - networking.k8s.io/v1/NetworkPolicy
// and 1.7 semantics.
func (ns *ns) analysePolicyLegacy(policy *extnapi.NetworkPolicy) (
rules map[string]*ruleSpec,
nsSelectors, podSelectors map[string]*selectorSpec,
err error) {
nsSelectors = make(map[string]*selectorSpec)
podSelectors = make(map[string]*selectorSpec)
rules = make(map[string]*ruleSpec)
dstSelector, err := newSelectorSpec(&policy.Spec.PodSelector, true, ns.name, ipset.HashIP)
if err != nil {
return nil, nil, nil, err
}
podSelectors[dstSelector.key] = dstSelector
for _, ingressRule := range policy.Spec.Ingress {
if ingressRule.Ports != nil && len(ingressRule.Ports) == 0 {
// Ports is empty, this rule matches no ports (no traffic matches).
continue
}
if ingressRule.From != nil && len(ingressRule.From) == 0 {
// From is empty, this rule matches no sources (no traffic matches).
continue
}
if ingressRule.From == nil {
// From is not provided, this rule matches all sources (traffic not restricted by source).
if ingressRule.Ports == nil {
// Ports is not provided, this rule matches all ports (traffic not restricted by port).
rule := newRuleSpec(nil, nil, dstSelector, nil)
rules[rule.key] = rule
} else {
// Ports is present and contains at least one item, then this rule allows traffic
// only if the traffic matches at least one port in the ports list.
withNormalisedProtoAndPortLegacy(ingressRule.Ports, func(proto, port string) {
rule := newRuleSpec(&proto, nil, dstSelector, &port)
rules[rule.key] = rule
})
}
} else {
// From is present and contains at least on item, this rule allows traffic only if the
// traffic matches at least one item in the from list.
for _, peer := range ingressRule.From {
var srcSelector *selectorSpec
if peer.PodSelector != nil {
srcSelector, err = newSelectorSpec(peer.PodSelector, false, ns.name, ipset.HashIP)
if err != nil {
return nil, nil, nil, err
}
podSelectors[srcSelector.key] = srcSelector
}
if peer.NamespaceSelector != nil {
srcSelector, err = newSelectorSpec(peer.NamespaceSelector, false, "", ipset.ListSet)
if err != nil {
return nil, nil, nil, err
}
nsSelectors[srcSelector.key] = srcSelector
}
if ingressRule.Ports == nil {
// Ports is not provided, this rule matches all ports (traffic not restricted by port).
rule := newRuleSpec(nil, srcSelector, dstSelector, nil)
rules[rule.key] = rule
} else {
// Ports is present and contains at least one item, then this rule allows traffic
// only if the traffic matches at least one port in the ports list.
withNormalisedProtoAndPortLegacy(ingressRule.Ports, func(proto, port string) {
rule := newRuleSpec(&proto, srcSelector, dstSelector, &port)
rules[rule.key] = rule
})
}
}
}
}
return rules, nsSelectors, podSelectors, nil
}
func (ns *ns) analysePolicy(policy *networkingv1.NetworkPolicy) (
rules map[string]*ruleSpec,
nsSelectors, podSelectors map[string]*selectorSpec,
ipBlocks map[string]*ipBlockSpec,
err error) {
nsSelectors = make(map[string]*selectorSpec)
podSelectors = make(map[string]*selectorSpec)
ipBlocks = make(map[string]*ipBlockSpec)
rules = make(map[string]*ruleSpec)
policyTypes := make([]policyType, 0)
for _, pt := range policy.Spec.PolicyTypes {
if pt == networkingv1.PolicyTypeIngress {
policyTypes = append(policyTypes, policyTypeIngress)
}
if pt == networkingv1.PolicyTypeEgress {
policyTypes = append(policyTypes, policyTypeEgress)
}
}
// If empty, matches all pods in a namespace
dstSelector, err := newSelectorSpec(&policy.Spec.PodSelector, true, ns.name, ipset.HashIP)
targetSelector, err := newSelectorSpec(&policy.Spec.PodSelector, policyTypes, ns.name, ipset.HashIP)
if err != nil {
return nil, nil, nil, err
}
podSelectors[dstSelector.key] = dstSelector
// If ingress is empty then this NetworkPolicy does not allow any traffic
if policy.Spec.Ingress == nil || len(policy.Spec.Ingress) == 0 {
return
return nil, nil, nil, nil, err
}
for _, ingressRule := range policy.Spec.Ingress {
// If Ports is empty or missing, this rule matches all ports
allPorts := ingressRule.Ports == nil || len(ingressRule.Ports) == 0
// If From is empty or missing, this rule matches all sources
allSources := ingressRule.From == nil || len(ingressRule.From) == 0
// To prevent targetSelector being overwritten by a subsequent selector with
// the same key, addIfNotExist MUST be used when adding to podSelectors.
// Otherwise, policyTypes of the selector might be lost resulting in
// an invalid content in any "default-allow" ipset.
addIfNotExist(targetSelector, podSelectors)
if allSources {
if allPorts {
rule := newRuleSpec(nil, nil, dstSelector, nil)
rules[rule.key] = rule
} else {
withNormalisedProtoAndPort(ingressRule.Ports, func(proto, port string) {
rule := newRuleSpec(&proto, nil, dstSelector, &port)
rules[rule.key] = rule
})
}
} else {
for _, peer := range ingressRule.From {
var srcSelector *selectorSpec
// NetworkPolicyPeer describes a peer to allow traffic from.
// Exactly one of its fields must be specified.
if peer.PodSelector != nil {
srcSelector, err = newSelectorSpec(peer.PodSelector, false, ns.name, ipset.HashIP)
if err != nil {
return nil, nil, nil, err
}
podSelectors[srcSelector.key] = srcSelector
} else if peer.NamespaceSelector != nil {
srcSelector, err = newSelectorSpec(peer.NamespaceSelector, false, "", ipset.ListSet)
if err != nil {
return nil, nil, nil, err
}
nsSelectors[srcSelector.key] = srcSelector
}
// If ingress is empty then this NetworkPolicy does not allow any ingress traffic
if policy.Spec.Ingress != nil && len(policy.Spec.Ingress) != 0 {
for _, ingressRule := range policy.Spec.Ingress {
// If Ports is empty or missing, this rule matches all ports
allPorts := ingressRule.Ports == nil || len(ingressRule.Ports) == 0
// If From is empty or missing, this rule matches all sources
allSources := ingressRule.From == nil || len(ingressRule.From) == 0
if allSources {
if allPorts {
rule := newRuleSpec(nil, srcSelector, dstSelector, nil)
rule := newRuleSpec(policyTypeIngress, nil, nil, targetSelector, nil)
rules[rule.key] = rule
} else {
withNormalisedProtoAndPort(ingressRule.Ports, func(proto, port string) {
rule := newRuleSpec(&proto, srcSelector, dstSelector, &port)
rule := newRuleSpec(policyTypeIngress, &proto, nil, targetSelector, &port)
rules[rule.key] = rule
})
}
} else {
for _, peer := range ingressRule.From {
var srcSelector *selectorSpec
// NetworkPolicyPeer describes a peer to allow traffic from.
// Exactly one of its fields must be specified.
if peer.PodSelector != nil {
srcSelector, err = newSelectorSpec(peer.PodSelector, nil, ns.name, ipset.HashIP)
if err != nil {
return nil, nil, nil, nil, err
}
addIfNotExist(srcSelector, podSelectors)
} else if peer.NamespaceSelector != nil {
srcSelector, err = newSelectorSpec(peer.NamespaceSelector, nil, "", ipset.ListSet)
if err != nil {
return nil, nil, nil, nil, err
}
nsSelectors[srcSelector.key] = srcSelector
}
if allPorts {
rule := newRuleSpec(policyTypeIngress, nil, srcSelector, targetSelector, nil)
rules[rule.key] = rule
} else {
withNormalisedProtoAndPort(ingressRule.Ports, func(proto, port string) {
rule := newRuleSpec(policyTypeIngress, &proto, srcSelector, targetSelector, &port)
rules[rule.key] = rule
})
}
}
}
}
}
return rules, nsSelectors, podSelectors, nil
// If egress is empty then this NetworkPolicy does not allow any egress traffic
if policy.Spec.Egress != nil && len(policy.Spec.Egress) != 0 {
for _, egressRule := range policy.Spec.Egress {
// If Ports is empty or missing, this rule matches all ports
allPorts := egressRule.Ports == nil || len(egressRule.Ports) == 0
// If To is empty or missing, this rule matches all destinations
allDestinations := egressRule.To == nil || len(egressRule.To) == 0
if allDestinations {
if allPorts {
rule := newRuleSpec(policyTypeEgress, nil, targetSelector, nil, nil)
rules[rule.key] = rule
} else {
withNormalisedProtoAndPort(egressRule.Ports, func(proto, port string) {
rule := newRuleSpec(policyTypeEgress, &proto, targetSelector, nil, &port)
rules[rule.key] = rule
})
}
} else {
for _, peer := range egressRule.To {
var dstSelector *selectorSpec
var dstRuleHost ruleHost
// NetworkPolicyPeer describes a peer to allow traffic to.
// Exactly one of its fields must be specified.
if peer.PodSelector != nil {
dstSelector, err = newSelectorSpec(peer.PodSelector, nil, ns.name, ipset.HashIP)
if err != nil {
return nil, nil, nil, nil, err
}
addIfNotExist(dstSelector, podSelectors)
dstRuleHost = dstSelector
} else if peer.NamespaceSelector != nil {
dstSelector, err = newSelectorSpec(peer.NamespaceSelector, nil, "", ipset.ListSet)
if err != nil {
return nil, nil, nil, nil, err
}
nsSelectors[dstSelector.key] = dstSelector
dstRuleHost = dstSelector
} else if peer.IPBlock != nil {
ipBlock := newIPBlockSpec(peer.IPBlock, ns.name)
ipBlocks[ipBlock.key] = ipBlock
dstRuleHost = ipBlock
}
if allPorts {
rule := newRuleSpec(policyTypeEgress, nil, targetSelector, dstRuleHost, nil)
rules[rule.key] = rule
} else {
withNormalisedProtoAndPort(egressRule.Ports, func(proto, port string) {
rule := newRuleSpec(policyTypeEgress, &proto, targetSelector, dstRuleHost, &port)
rules[rule.key] = rule
})
}
}
}
}
}
return rules, nsSelectors, podSelectors, ipBlocks, nil
}
func withNormalisedProtoAndPortLegacy(npps []extnapi.NetworkPolicyPort, f func(proto, port string)) {
for _, npp := range npps {
f(proto(npp.Protocol), port(npp.Port))
func addIfNotExist(s *selectorSpec, ss map[string]*selectorSpec) {
if _, ok := ss[s.key]; !ok {
ss[s.key] = s
}
}
+6
View File
@@ -7,6 +7,12 @@ const (
DefaultChain = "WEAVE-NPC-DEFAULT"
IngressChain = "WEAVE-NPC-INGRESS"
EgressChain = "WEAVE-NPC-EGRESS"
EgressDefaultChain = "WEAVE-NPC-EGRESS-DEFAULT"
EgressCustomChain = "WEAVE-NPC-EGRESS-CUSTOM"
EgressMarkChain = "WEAVE-NPC-EGRESS-ACCEPT"
EgressMark = "0x40000/0x40000"
IpsetNamePrefix = "weave-"
LocalIpset = IpsetNamePrefix + "local-pods"
+4 -7
View File
@@ -9,7 +9,7 @@ import (
networkingv1 "k8s.io/api/networking/v1"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/npc/ipset"
"github.com/weaveworks/weave/net/ipset"
"github.com/weaveworks/weave/npc/iptables"
)
@@ -37,19 +37,16 @@ type controller struct {
nss map[string]*ns // ns name -> ns struct
nsSelectors *selectorSet // selector string -> nsSelector
legacy bool // use legacy network policy semantics (k8s pre-1.7)
}
func New(nodeName string, legacy bool, ipt iptables.Interface, ips ipset.Interface) NetworkPolicyController {
func New(nodeName string, ipt iptables.Interface, ips ipset.Interface) NetworkPolicyController {
c := &controller{
nodeName: nodeName,
legacy: legacy,
ipt: ipt,
ips: ips,
nss: make(map[string]*ns)}
doNothing := func(*selector) error { return nil }
doNothing := func(*selector, policyType) error { return nil }
c.nsSelectors = newSelectorSet(ips, c.onNewNsSelector, doNothing, doNothing)
return c
@@ -71,7 +68,7 @@ func (npc *controller) onNewNsSelector(selector *selector) error {
func (npc *controller) withNS(name string, f func(ns *ns) error) error {
ns, found := npc.nss[name]
if !found {
newNs, err := newNS(name, npc.nodeName, npc.legacy, npc.ipt, npc.ips, npc.nsSelectors)
newNs, err := newNS(name, npc.nodeName, npc.ipt, npc.ips, npc.nsSelectors)
if err != nil {
return err
}
+248 -32
View File
@@ -1,12 +1,14 @@
package npc
import (
"fmt"
"log"
"strings"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"github.com/weaveworks/weave/npc/ipset"
"github.com/weaveworks/weave/net/ipset"
coreapi "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -103,18 +105,46 @@ func (i *mockIPSet) List(prefix string) ([]ipset.Name, error) {
}
type mockIPTables struct {
rules map[string]map[string]struct{} // chain -> rulespec -> struct{}
}
func newMockIPTables() *mockIPTables {
return &mockIPTables{rules: make(map[string]map[string]struct{})}
}
func (ipt *mockIPTables) Append(table, chain string, rulespec ...string) error {
if table != TableFilter {
return fmt.Errorf("invalid table: %q", table)
}
if _, found := ipt.rules[chain]; !found {
ipt.rules[chain] = make(map[string]struct{})
}
rule := strings.Join(rulespec, " ")
if _, found := ipt.rules[chain][rule]; found {
return fmt.Errorf("rule already exists. chain: %q, rule: %q", chain, rule)
}
ipt.rules[chain][rule] = struct{}{}
return nil
}
func (ipt *mockIPTables) Delete(table, chain string, rulespec ...string) error {
rule := strings.Join(rulespec, " ")
if _, found := ipt.rules[chain][rule]; !found {
return fmt.Errorf("rule does not exist. chain: %q rule: %q", chain, rule)
}
delete(ipt.rules[chain], rule)
return nil
}
func (ipt *mockIPTables) Insert(table, chain string, pos int, rulespec ...string) error {
return nil
return errors.New("Not Implemented")
}
func TestRegressionPolicyNamespaceOrdering3059(t *testing.T) {
@@ -144,6 +174,7 @@ func TestRegressionPolicyNamespaceOrdering3059(t *testing.T) {
Namespace: "destination",
},
Spec: networkingv1.NetworkPolicySpec{
PolicyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress},
Ingress: []networkingv1.NetworkPolicyIngressRule{
{
From: []networkingv1.NetworkPolicyPeer{
@@ -167,7 +198,7 @@ func TestRegressionPolicyNamespaceOrdering3059(t *testing.T) {
// Namespaces first
m := newMockIPSet()
controller := New("foo", false, &mockIPTables{}, &m)
controller := New("foo", newMockIPTables(), &m)
const (
selectorIPSetName = "weave-I239Zp%sCvoVt*D6u=A!2]YEk"
@@ -183,7 +214,7 @@ func TestRegressionPolicyNamespaceOrdering3059(t *testing.T) {
// NetworkPolicy first
m = newMockIPSet()
controller = New("foo", false, &mockIPTables{}, &m)
controller = New("foo", newMockIPTables(), &m)
controller.AddNetworkPolicy(networkPolicy)
@@ -193,17 +224,18 @@ func TestRegressionPolicyNamespaceOrdering3059(t *testing.T) {
require.Equal(t, true, len(m.sets[selectorIPSetName].subSets[sourceIPSetName]) > 0)
}
// Tests default-allow ipset behavior when running in non-legacy mode.
// Tests default-allow ipset behavior
func TestDefaultAllow(t *testing.T) {
const (
defaultAllowIPSetName = "weave-E.1.0W^NGSp]0_t5WwH/]gX@L"
fooPodIP = "10.32.0.10"
barPodIP = "10.32.0.11"
barPodNewIP = "10.32.0.12"
ingressDefaultAllowIPSetName = "weave-;rGqyMIl1HN^cfDki~Z$3]6!N"
egressDefaultAllowIPSetName = "weave-s_+ChJId4Uy_$}G;WdH|~TK)I"
fooPodIP = "10.32.0.10"
barPodIP = "10.32.0.11"
barPodNewIP = "10.32.0.12"
)
m := newMockIPSet()
controller := New("bar", false, &mockIPTables{}, &m)
controller := New("bar", newMockIPTables(), &m)
defaultNamespace := &coreapi.Namespace{
ObjectMeta: metav1.ObjectMeta{
@@ -213,7 +245,8 @@ func TestDefaultAllow(t *testing.T) {
controller.AddNamespace(defaultNamespace)
// Should create an ipset for default-allow
require.Contains(t, m.sets, defaultAllowIPSetName)
require.Contains(t, m.sets, ingressDefaultAllowIPSetName)
require.Contains(t, m.sets, egressDefaultAllowIPSetName)
podFoo := &coreapi.Pod{
ObjectMeta: metav1.ObjectMeta{
@@ -225,7 +258,8 @@ func TestDefaultAllow(t *testing.T) {
controller.AddPod(podFoo)
// Should add the foo pod to default-allow
require.True(t, m.entryExists(defaultAllowIPSetName, fooPodIP))
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, fooPodIP))
require.True(t, m.entryExists(egressDefaultAllowIPSetName, fooPodIP))
podBar := &coreapi.Pod{
ObjectMeta: metav1.ObjectMeta{
@@ -236,10 +270,12 @@ func TestDefaultAllow(t *testing.T) {
Status: coreapi.PodStatus{PodIP: barPodIP}}
podBarNoIP := &coreapi.Pod{ObjectMeta: podBar.ObjectMeta}
controller.AddPod(podBarNoIP)
controller.UpdatePod(podBarNoIP, podBar)
// Should add the bar pod to default-allow
require.True(t, m.entryExists(defaultAllowIPSetName, barPodIP))
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, barPodIP))
require.True(t, m.entryExists(egressDefaultAllowIPSetName, barPodIP))
// Allow access from the bar pod to the foo pod
netpol := &networkingv1.NetworkPolicy{
@@ -248,6 +284,10 @@ func TestDefaultAllow(t *testing.T) {
Namespace: "default",
},
Spec: networkingv1.NetworkPolicySpec{
PolicyTypes: []networkingv1.PolicyType{
networkingv1.PolicyTypeIngress,
networkingv1.PolicyTypeEgress,
},
PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"run": "foo"}},
Ingress: []networkingv1.NetworkPolicyIngressRule{{
From: []networkingv1.NetworkPolicyPeer{{
@@ -256,55 +296,74 @@ func TestDefaultAllow(t *testing.T) {
},
}},
}},
Egress: []networkingv1.NetworkPolicyEgressRule{{
To: []networkingv1.NetworkPolicyPeer{{
IPBlock: &networkingv1.IPBlock{CIDR: "192.168.48.0/24"},
}},
}},
},
}
controller.AddNetworkPolicy(netpol)
// Should remove the foo pod from default-allow as the netpol selects it
require.False(t, m.entryExists(defaultAllowIPSetName, fooPodIP))
require.True(t, m.entryExists(defaultAllowIPSetName, barPodIP))
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, fooPodIP))
require.False(t, m.entryExists(egressDefaultAllowIPSetName, fooPodIP))
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, barPodIP))
require.True(t, m.entryExists(egressDefaultAllowIPSetName, barPodIP))
podBarWithNewIP := *podBar
podBarWithNewIP.Status.PodIP = barPodNewIP
controller.UpdatePod(podBar, &podBarWithNewIP)
// Should update IP addr of the bar pod in default-allow
require.False(t, m.entryExists(defaultAllowIPSetName, barPodIP))
require.True(t, m.entryExists(defaultAllowIPSetName, barPodNewIP))
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, barPodIP))
require.False(t, m.entryExists(egressDefaultAllowIPSetName, barPodIP))
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, barPodNewIP))
require.True(t, m.entryExists(egressDefaultAllowIPSetName, barPodNewIP))
controller.UpdatePod(&podBarWithNewIP, podBarNoIP)
// Should remove the bar pod from default-allow as it does not have any IP addr
require.False(t, m.entryExists(defaultAllowIPSetName, barPodNewIP))
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, barPodNewIP))
require.False(t, m.entryExists(egressDefaultAllowIPSetName, barPodNewIP))
podFooWithNewLabel := *podFoo
podFooWithNewLabel.ObjectMeta.Labels = map[string]string{"run": "new-foo"}
controller.UpdatePod(podFoo, &podFooWithNewLabel)
// Should bring back the foo pod to default-allow as it does not match dst of any netpol
require.True(t, m.entryExists(defaultAllowIPSetName, fooPodIP))
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, fooPodIP))
require.True(t, m.entryExists(egressDefaultAllowIPSetName, fooPodIP))
controller.UpdatePod(&podFooWithNewLabel, podFoo)
// Should remove from default-allow as it matches the netpol after the update
require.False(t, m.entryExists(defaultAllowIPSetName, fooPodIP))
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, fooPodIP))
require.False(t, m.entryExists(egressDefaultAllowIPSetName, fooPodIP))
controller.DeleteNetworkPolicy(netpol)
// Should bring back the foo pod to default-allow as no netpol selects it
require.True(t, m.entryExists(defaultAllowIPSetName, fooPodIP))
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, fooPodIP))
require.True(t, m.entryExists(egressDefaultAllowIPSetName, fooPodIP))
controller.DeletePod(podFoo)
// Should remove foo pod from default-allow
require.False(t, m.entryExists(defaultAllowIPSetName, fooPodIP))
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, fooPodIP))
require.False(t, m.entryExists(egressDefaultAllowIPSetName, fooPodIP))
controller.DeleteNamespace(defaultNamespace)
// Should remove default ipset
require.NotContains(t, m.sets, ingressDefaultAllowIPSetName)
require.NotContains(t, m.sets, egressDefaultAllowIPSetName)
}
func TestOutOfOrderPodEvents(t *testing.T) {
const (
defaultAllowIPSetName = "weave-E.1.0W^NGSp]0_t5WwH/]gX@L"
runBarIPSetName = "weave-bZ~x=yBgzH)Ht()K*Uv3z{M]Y"
podIP = "10.32.0.10"
ingressDefaultAllowIPSetName = "weave-;rGqyMIl1HN^cfDki~Z$3]6!N"
runBarIPSetName = "weave-bZ~x=yBgzH)Ht()K*Uv3z{M]Y"
podIP = "10.32.0.10"
)
m := newMockIPSet()
controller := New("qux", false, &mockIPTables{}, &m)
controller := New("qux", newMockIPTables(), &m)
defaultNamespace := &coreapi.Namespace{
ObjectMeta: metav1.ObjectMeta{
@@ -323,7 +382,7 @@ func TestOutOfOrderPodEvents(t *testing.T) {
controller.AddPod(podFoo)
// Should be in default-allow as no netpol selects podFoo
require.True(t, m.entryExists(defaultAllowIPSetName, podIP))
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
netpol := &networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
@@ -331,6 +390,7 @@ func TestOutOfOrderPodEvents(t *testing.T) {
Namespace: "default",
},
Spec: networkingv1.NetworkPolicySpec{
PolicyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress},
PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"run": "foo"}},
Ingress: []networkingv1.NetworkPolicyIngressRule{{
From: []networkingv1.NetworkPolicyPeer{{
@@ -344,7 +404,7 @@ func TestOutOfOrderPodEvents(t *testing.T) {
controller.AddNetworkPolicy(netpol)
// Shouldn't be in default-allow as netpol above selects podFoo
require.False(t, m.entryExists(defaultAllowIPSetName, podIP))
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
podBar := &coreapi.Pod{
ObjectMeta: metav1.ObjectMeta{
@@ -356,8 +416,8 @@ func TestOutOfOrderPodEvents(t *testing.T) {
controller.AddPod(podBar)
// Should be in default-allow as no netpol selects podBar
require.True(t, m.entryExists(defaultAllowIPSetName, podIP))
require.True(t, m.Exist(podBar.ObjectMeta.UID, defaultAllowIPSetName, podIP))
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
require.True(t, m.Exist(podBar.ObjectMeta.UID, ingressDefaultAllowIPSetName, podIP))
// Should be in run=bar ipset
require.True(t, m.entryExists(runBarIPSetName, podIP))
@@ -368,11 +428,167 @@ func TestOutOfOrderPodEvents(t *testing.T) {
// Should be in default-allow as no netpol selects podBar and podFoo removal
// should not affect podBar in default-allow
require.True(t, m.entryExists(defaultAllowIPSetName, podIP))
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
controller.DeletePod(podBar)
// Should remove from default-allow and run=bar ipsets
require.Equal(t, 0, len(m.sets[defaultAllowIPSetName].subSets))
require.Equal(t, 0, len(m.sets[ingressDefaultAllowIPSetName].subSets))
require.False(t, m.entryExists(runBarIPSetName, podIP))
}
// Test case for https://github.com/weaveworks/weave/issues/3222
func TestNewTargetSelector(t *testing.T) {
const (
ingressDefaultAllowIPSetName = "weave-;rGqyMIl1HN^cfDki~Z$3]6!N"
podIP = "10.32.0.10"
)
m := newMockIPSet()
controller := New("baz", newMockIPTables(), &m)
defaultNamespace := &coreapi.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
},
}
controller.AddNamespace(defaultNamespace)
podFoo := &coreapi.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "foo",
Namespace: "default",
Name: "foo",
Labels: map[string]string{"run": "foo"}},
Status: coreapi.PodStatus{PodIP: podIP}}
controller.AddPod(podFoo)
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
netpolBar := &networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
UID: "netpol-bar",
Name: "allow-from-default-1",
Namespace: "default",
},
Spec: networkingv1.NetworkPolicySpec{
PolicyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress},
PodSelector: metav1.LabelSelector{},
Ingress: []networkingv1.NetworkPolicyIngressRule{{
From: []networkingv1.NetworkPolicyPeer{{
PodSelector: &metav1.LabelSelector{},
}},
}},
},
}
controller.AddNetworkPolicy(netpolBar)
// netpolBar target selector selects podFoo
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
netpolFoo := &networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
UID: "netpol-foo",
Name: "allow-from-default-2",
Namespace: "default",
},
Spec: networkingv1.NetworkPolicySpec{
PolicyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress},
PodSelector: metav1.LabelSelector{},
Ingress: []networkingv1.NetworkPolicyIngressRule{{
From: []networkingv1.NetworkPolicyPeer{{
PodSelector: &metav1.LabelSelector{},
}},
}},
},
}
controller.AddNetworkPolicy(netpolFoo)
controller.DeleteNetworkPolicy(netpolBar)
// netpolFoo target-selects podFoo
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
controller.DeleteNetworkPolicy(netpolFoo)
// No netpol target-selects podFoo
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
}
func TestEgressPolicyWithIPBlock(t *testing.T) {
const (
fooPodIP = "10.32.0.10"
exceptIPSetName = "weave-j:W$5om!$8JS})buYAD7q^#sX"
exceptIPSetNameInNonDefault = "weave-|*P{aL2C@#N/1IZ!IvQItv_pt"
)
m := newMockIPSet()
ipt := newMockIPTables()
controller := New("foo", ipt, &m)
netpolFoo := &networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
UID: "netpol-foo",
Name: "allow-from-bar-to-foo",
Namespace: "default",
},
Spec: networkingv1.NetworkPolicySpec{
PolicyTypes: []networkingv1.PolicyType{
networkingv1.PolicyTypeEgress,
},
PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"run": "bar"}},
Egress: []networkingv1.NetworkPolicyEgressRule{{
To: []networkingv1.NetworkPolicyPeer{{
IPBlock: &networkingv1.IPBlock{
CIDR: "192.168.48.0/24",
Except: []string{
"192.168.48.1/32",
"192.168.48.2/32",
},
},
}},
}},
},
}
err := controller.AddNetworkPolicy(netpolFoo)
require.NoError(t, err)
require.Equal(t, 2, len(m.sets[exceptIPSetName].subSets))
require.True(t, m.entryExists(exceptIPSetName, "192.168.48.1/32"))
require.True(t, m.entryExists(exceptIPSetName, "192.168.48.2/32"))
// Each egress rule is represented as two iptables rules (-J MARK and -J RETURN).
require.Equal(t, 2, len(ipt.rules[EgressCustomChain]))
for rule := range ipt.rules[EgressCustomChain] {
require.Contains(t, rule, "-d 192.168.48.0/24 -m set ! --match-set "+exceptIPSetName+" dst")
}
// Check that we create a new ipset for the ipBlock bellow. An ipset with the
// same content already exists (created by netpolFoo), but we need to create
// a new one, as netpolBar is in a different namespace and weave-npc does
// object refcounting only within namespace boundaries.
netpolBar := &networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
UID: "netpol-foo-bar",
Name: "allow-from-bar-to-foo-in-non-default",
Namespace: "non-default",
},
Spec: networkingv1.NetworkPolicySpec{
PolicyTypes: []networkingv1.PolicyType{
networkingv1.PolicyTypeEgress,
},
PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"run": "bar"}},
Egress: []networkingv1.NetworkPolicyEgressRule{{
To: []networkingv1.NetworkPolicyPeer{{
IPBlock: &networkingv1.IPBlock{
CIDR: "192.168.48.0/24",
Except: []string{
"192.168.48.1/32",
"192.168.48.2/32",
},
},
}},
}},
},
}
err = controller.AddNetworkPolicy(netpolBar)
require.NoError(t, err)
require.Equal(t, 2, len(m.sets[exceptIPSetNameInNonDefault].subSets))
}
+116
View File
@@ -0,0 +1,116 @@
package npc
import (
"fmt"
"sort"
"strings"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/net/ipset"
)
type ipBlockSpec struct {
key string
ipsetName ipset.Name // ipset for storing excepted CIDRs
ipBlock *networkingv1.IPBlock
nsName string // Namespace name
}
func newIPBlockSpec(ipb *networkingv1.IPBlock, nsName string) *ipBlockSpec {
spec := &ipBlockSpec{ipBlock: ipb, nsName: nsName}
if len(ipb.Except) > 0 {
sort.Strings(ipb.Except)
spec.key = strings.Join(ipb.Except, " ")
spec.ipsetName = ipset.Name(IpsetNamePrefix + shortName(nsName+":"+"ipblock-except:"+spec.key))
}
return spec
}
func (spec *ipBlockSpec) getRuleSpec(src bool) ([]string, string) {
dir, dirOpt := "dst", "-d"
if src {
dir, dirOpt = "src", "-s"
}
if spec.key == "" {
rule := []string{dirOpt, spec.ipBlock.CIDR}
comment := fmt.Sprintf("cidr: %s", spec.ipBlock.CIDR)
return rule, comment
}
rule := []string{dirOpt, spec.ipBlock.CIDR,
"-m", "set", "!", "--match-set", string(spec.ipsetName), dir}
comment := fmt.Sprintf("cidr: %s except [%s]", spec.ipBlock.CIDR, spec.key)
return rule, comment
}
type ipBlockSet struct {
ips ipset.Interface
users map[string]map[types.UID]struct{}
}
func newIPBlockSet(ips ipset.Interface) *ipBlockSet {
return &ipBlockSet{
ips: ips,
users: make(map[string]map[types.UID]struct{}),
}
}
func (s *ipBlockSet) deprovision(user types.UID, current, desired map[string]*ipBlockSpec) error {
for key, spec := range current {
if key == "" {
continue
}
if _, found := desired[key]; !found {
delete(s.users[key], user)
if len(s.users[key]) == 0 {
common.Log.Infof("destroying ipset: %#v", spec)
if err := s.ips.Destroy(spec.ipsetName); err != nil {
return err
}
delete(s.users, key)
}
}
}
return nil
}
func (s *ipBlockSet) provision(user types.UID, current, desired map[string]*ipBlockSpec) (err error) {
for key, spec := range desired {
if key == "" {
// No need to provision an ipBlock with empty list of excepted CIDRs
// (i.e. no ipset is needed for the related iptables rule).
continue
}
if _, found := current[key]; !found {
if _, found := s.users[key]; !found {
common.Log.Infof("creating ipset: %#v", spec)
if err := s.ips.Create(spec.ipsetName, ipset.HashNet); err != nil {
return err
}
// TODO(brb) Pass comment to ips.Create instead.
comment := "excepted from " + spec.ipBlock.CIDR
for _, cidr := range spec.ipBlock.Except {
if err = s.ips.AddEntry(user, spec.ipsetName, cidr, comment); err != nil {
return err
}
}
s.users[key] = make(map[types.UID]struct{})
}
s.users[key][user] = struct{}{}
}
}
return nil
}
+185 -156
View File
@@ -1,7 +1,6 @@
package npc
import (
"encoding/json"
"errors"
"fmt"
@@ -13,7 +12,7 @@ import (
"k8s.io/apimachinery/pkg/util/uuid"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/npc/ipset"
"github.com/weaveworks/weave/net/ipset"
"github.com/weaveworks/weave/npc/iptables"
)
@@ -32,20 +31,20 @@ type ns struct {
uid types.UID // surrogate UID to own allPods selector
allPods *selectorSpec // hash:ip ipset of all pod IPs in this namespace
// stores IP addrs of pods which are not selected by any dst podSelector of
// any netpol; used only in non-legacy mode and is used as a dst in
// the WEAVE-NPC-DEFAULT iptables chain.
defaultAllowIPSet ipset.Name
// stores IP addrs of pods which are not selected by any target podSelector of
// any netpol; used as a target in the WEAVE-NPC-{INGRESS,EGRESS}-DEFAULT
// iptables chains.
ingressDefaultAllowIPSet ipset.Name
egressDefaultAllowIPSet ipset.Name
nsSelectors *selectorSet
podSelectors *selectorSet
ipBlocks *ipBlockSet
rules *ruleSet
legacy bool
}
func newNS(name, nodeName string, legacy bool, ipt iptables.Interface, ips ipset.Interface, nsSelectors *selectorSet) (*ns, error) {
allPods, err := newSelectorSpec(&metav1.LabelSelector{}, false, name, ipset.HashIP)
func newNS(name, nodeName string, ipt iptables.Interface, ips ipset.Interface, nsSelectors *selectorSet) (*ns, error) {
allPods, err := newSelectorSpec(&metav1.LabelSelector{}, nil, name, ipset.HashIP)
if err != nil {
return nil, err
}
@@ -60,19 +59,24 @@ func newNS(name, nodeName string, legacy bool, ipt iptables.Interface, ips ipset
uid: uuid.NewUUID(),
allPods: allPods,
nsSelectors: nsSelectors,
ipBlocks: newIPBlockSet(ips),
rules: newRuleSet(ipt),
legacy: legacy}
ns.podSelectors = newSelectorSet(ips, ns.onNewPodSelector, ns.onNewDstPodSelector, ns.onDestroyDstPodSelector)
if !legacy {
defaultAllowIPSet := ipset.Name(IpsetNamePrefix + shortName("default-allow:"+name))
if err := ips.Create(defaultAllowIPSet, ipset.HashIP); err != nil {
return nil, err
}
ns.defaultAllowIPSet = defaultAllowIPSet
}
ns.podSelectors = newSelectorSet(ips, ns.onNewPodSelector, ns.onNewTargetPodSelector, ns.onDestroyTargetPodSelector)
ingressDefaultAllowIPSet := ipset.Name(IpsetNamePrefix + shortName("ingress-default-allow:"+name))
if err := ips.Create(ingressDefaultAllowIPSet, ipset.HashIP); err != nil {
return nil, err
}
ns.ingressDefaultAllowIPSet = ingressDefaultAllowIPSet
egressDefaultAllowIPSet := ipset.Name(IpsetNamePrefix + shortName("egress-default-allow:"+name))
if err := ips.Create(egressDefaultAllowIPSet, ipset.HashIP); err != nil {
return nil, err
}
ns.egressDefaultAllowIPSet = egressDefaultAllowIPSet
if err := ns.podSelectors.provision(ns.uid, nil, map[string]*selectorSpec{ns.allPods.key: ns.allPods}); err != nil {
return nil, err
}
@@ -102,16 +106,13 @@ func (ns *ns) onNewPodSelector(selector *selector) error {
return nil
}
func (ns *ns) onNewDstPodSelector(selector *selector) error {
if ns.legacy {
return nil
}
func (ns *ns) onNewTargetPodSelector(selector *selector, policyType policyType) error {
for _, pod := range ns.pods {
if hasIP(pod) {
// Remove the pod from default-allow if dst podselector matches the pod
ipset := ns.defaultAllowIPSetName(policyType)
if selector.matches(pod.ObjectMeta.Labels) {
if err := ns.ips.DelEntry(pod.ObjectMeta.UID, ns.defaultAllowIPSet, pod.Status.PodIP); err != nil {
if err := ns.ips.DelEntry(pod.ObjectMeta.UID, ipset, pod.Status.PodIP); err != nil {
return err
}
}
@@ -121,15 +122,11 @@ func (ns *ns) onNewDstPodSelector(selector *selector) error {
return nil
}
func (ns *ns) onDestroyDstPodSelector(selector *selector) error {
if ns.legacy {
return nil
}
func (ns *ns) onDestroyTargetPodSelector(selector *selector, policyType policyType) error {
for _, pod := range ns.pods {
if hasIP(pod) {
if selector.matches(pod.ObjectMeta.Labels) {
if err := ns.addToDefaultAllowIfNoMatching(pod); err != nil {
if err := ns.addToDefaultAllowIfNoMatching(pod, policyType); err != nil {
return err
}
}
@@ -139,18 +136,19 @@ func (ns *ns) onDestroyDstPodSelector(selector *selector) error {
return nil
}
// Add pod IP addr to default-allow ipset if there are no matching dst selectors
func (ns *ns) addToDefaultAllowIfNoMatching(pod *coreapi.Pod) error {
// Add pod IP addr to default-allow ipset if there are no matching target selectors
func (ns *ns) addToDefaultAllowIfNoMatching(pod *coreapi.Pod, policyType policyType) error {
found := false
// TODO(mp) optimize (avoid iterating over selectors) by ref counting IP addrs.
for _, s := range ns.podSelectors.entries {
if ns.podSelectors.dstSelectorExist(s) && s.matches(pod.ObjectMeta.Labels) {
if ns.podSelectors.targetSelectorExist(s, policyType) && s.matches(pod.ObjectMeta.Labels) {
found = true
break
}
}
if !found {
if err := ns.ips.AddEntry(pod.ObjectMeta.UID, ns.defaultAllowIPSet, pod.Status.PodIP, podComment(pod)); err != nil {
ipset := ns.defaultAllowIPSetName(policyType)
if err := ns.ips.AddEntry(pod.ObjectMeta.UID, ipset, pod.Status.PodIP, podComment(pod)); err != nil {
return err
}
}
@@ -175,13 +173,18 @@ func (ns *ns) addPod(obj *coreapi.Pod) error {
ns.ips.AddEntry(obj.ObjectMeta.UID, LocalIpset, obj.Status.PodIP, podComment(obj))
}
found, err := ns.podSelectors.addToMatching(obj.ObjectMeta.UID, obj.ObjectMeta.Labels, obj.Status.PodIP, podComment(obj))
foundIngress, foundEgress, err := ns.podSelectors.addToMatching(obj.ObjectMeta.UID, obj.ObjectMeta.Labels, obj.Status.PodIP, podComment(obj))
if err != nil {
return err
}
// If there are no matching dst selectors, add the pod to default-allow
if !ns.legacy && !found {
if err := ns.ips.AddEntry(obj.ObjectMeta.UID, ns.defaultAllowIPSet, obj.Status.PodIP, podComment(obj)); err != nil {
// If there are no matching target selectors, add the pod to default-allow
if !foundIngress {
if err := ns.ips.AddEntry(obj.ObjectMeta.UID, ns.ingressDefaultAllowIPSet, obj.Status.PodIP, podComment(obj)); err != nil {
return err
}
}
if !foundEgress {
if err := ns.ips.AddEntry(obj.ObjectMeta.UID, ns.egressDefaultAllowIPSet, obj.Status.PodIP, podComment(obj)); err != nil {
return err
}
}
@@ -202,10 +205,11 @@ func (ns *ns) updatePod(oldObj, newObj *coreapi.Pod) error {
ns.ips.DelEntry(oldObj.ObjectMeta.UID, LocalIpset, oldObj.Status.PodIP)
}
if !ns.legacy {
if err := ns.ips.DelEntry(oldObj.ObjectMeta.UID, ns.defaultAllowIPSet, oldObj.Status.PodIP); err != nil {
return err
}
if err := ns.ips.DelEntry(oldObj.ObjectMeta.UID, ns.ingressDefaultAllowIPSet, oldObj.Status.PodIP); err != nil {
return err
}
if err := ns.ips.DelEntry(oldObj.ObjectMeta.UID, ns.egressDefaultAllowIPSet, oldObj.Status.PodIP); err != nil {
return err
}
return ns.podSelectors.delFromMatching(oldObj.ObjectMeta.UID, oldObj.ObjectMeta.Labels, oldObj.Status.PodIP)
@@ -215,13 +219,18 @@ func (ns *ns) updatePod(oldObj, newObj *coreapi.Pod) error {
if ns.checkLocalPod(newObj) {
ns.ips.AddEntry(newObj.ObjectMeta.UID, LocalIpset, newObj.Status.PodIP, podComment(newObj))
}
found, err := ns.podSelectors.addToMatching(newObj.ObjectMeta.UID, newObj.ObjectMeta.Labels, newObj.Status.PodIP, podComment(newObj))
foundIngress, foundEgress, err := ns.podSelectors.addToMatching(newObj.ObjectMeta.UID, newObj.ObjectMeta.Labels, newObj.Status.PodIP, podComment(newObj))
if err != nil {
return err
}
if !ns.legacy && !found {
if err := ns.ips.AddEntry(newObj.ObjectMeta.UID, ns.defaultAllowIPSet, newObj.Status.PodIP, podComment(newObj)); err != nil {
if !foundIngress {
if err := ns.ips.AddEntry(newObj.ObjectMeta.UID, ns.ingressDefaultAllowIPSet, newObj.Status.PodIP, podComment(newObj)); err != nil {
return err
}
}
if !foundEgress {
if err := ns.ips.AddEntry(newObj.ObjectMeta.UID, ns.egressDefaultAllowIPSet, newObj.Status.PodIP, podComment(newObj)); err != nil {
return err
}
}
@@ -229,17 +238,11 @@ func (ns *ns) updatePod(oldObj, newObj *coreapi.Pod) error {
return nil
}
if !ns.legacy &&
oldObj.Status.PodIP != newObj.Status.PodIP &&
ns.ips.Exist(oldObj.ObjectMeta.UID, ns.defaultAllowIPSet, oldObj.Status.PodIP) {
// Instead of iterating over all selectors we check whether old pod IP
// has been inserted into default-allow ipset to decide whether the IP
// in the ipset has to be updated.
if err := ns.ips.DelEntry(oldObj.ObjectMeta.UID, ns.defaultAllowIPSet, oldObj.Status.PodIP); err != nil {
if oldObj.Status.PodIP != newObj.Status.PodIP {
if err := ns.updateDefaultAllowIPSetEntry(oldObj, newObj, ns.ingressDefaultAllowIPSet); err != nil {
return err
}
if err := ns.ips.AddEntry(newObj.ObjectMeta.UID, ns.defaultAllowIPSet, newObj.Status.PodIP, podComment(newObj)); err != nil {
if err := ns.updateDefaultAllowIPSetEntry(oldObj, newObj, ns.egressDefaultAllowIPSet); err != nil {
return err
}
}
@@ -264,17 +267,11 @@ func (ns *ns) updatePod(oldObj, newObj *coreapi.Pod) error {
}
}
if !ns.legacy && ns.podSelectors.dstSelectorExist(ps) {
switch {
case !oldMatch && newMatch:
if err := ns.ips.DelEntry(oldObj.ObjectMeta.UID, ns.defaultAllowIPSet, oldObj.Status.PodIP); err != nil {
return err
}
case oldMatch && !newMatch:
if err := ns.addToDefaultAllowIfNoMatching(newObj); err != nil {
return err
}
}
if err := ns.addOrRemoveToDefaultAllowIPSet(ps, oldObj, newObj, oldMatch, newMatch, policyTypeIngress); err != nil {
return err
}
if err := ns.addOrRemoveToDefaultAllowIPSet(ps, oldObj, newObj, oldMatch, newMatch, policyTypeEgress); err != nil {
return err
}
}
}
@@ -282,6 +279,23 @@ func (ns *ns) updatePod(oldObj, newObj *coreapi.Pod) error {
return nil
}
func (ns *ns) addOrRemoveToDefaultAllowIPSet(ps *selector, oldObj, newObj *coreapi.Pod, oldMatch, newMatch bool, policyType policyType) error {
ipset := ns.defaultAllowIPSetName(policyType)
if ns.podSelectors.targetSelectorExist(ps, policyType) {
switch {
case !oldMatch && newMatch:
if err := ns.ips.DelEntry(oldObj.ObjectMeta.UID, ipset, oldObj.Status.PodIP); err != nil {
return err
}
case oldMatch && !newMatch:
if err := ns.addToDefaultAllowIfNoMatching(newObj, policyType); err != nil {
return err
}
}
}
return nil
}
func (ns *ns) deletePod(obj *coreapi.Pod) error {
delete(ns.pods, obj.ObjectMeta.UID)
@@ -293,10 +307,11 @@ func (ns *ns) deletePod(obj *coreapi.Pod) error {
ns.ips.DelEntry(obj.ObjectMeta.UID, LocalIpset, obj.Status.PodIP)
}
if !ns.legacy {
if err := ns.ips.DelEntry(obj.ObjectMeta.UID, ns.defaultAllowIPSet, obj.Status.PodIP); err != nil {
return err
}
if err := ns.ips.DelEntry(obj.ObjectMeta.UID, ns.ingressDefaultAllowIPSet, obj.Status.PodIP); err != nil {
return err
}
if err := ns.ips.DelEntry(obj.ObjectMeta.UID, ns.egressDefaultAllowIPSet, obj.Status.PodIP); err != nil {
return err
}
return ns.podSelectors.delFromMatching(obj.ObjectMeta.UID, obj.ObjectMeta.Labels, obj.Status.PodIP)
@@ -305,7 +320,7 @@ func (ns *ns) deletePod(obj *coreapi.Pod) error {
func (ns *ns) addNetworkPolicy(obj interface{}) error {
// Analyse policy, determine which rules and ipsets are required
uid, rules, nsSelectors, podSelectors, err := ns.analyse(obj)
uid, rules, nsSelectors, podSelectors, ipBlocks, err := ns.analyse(obj)
if err != nil {
return err
}
@@ -317,16 +332,19 @@ func (ns *ns) addNetworkPolicy(obj interface{}) error {
if err := ns.podSelectors.provision(uid, nil, podSelectors); err != nil {
return err
}
if err := ns.ipBlocks.provision(uid, nil, ipBlocks); err != nil {
return err
}
return ns.rules.provision(uid, nil, rules)
}
func (ns *ns) updateNetworkPolicy(oldObj, newObj interface{}) error {
// Analyse the old and the new policy so we can determine differences
oldUID, oldRules, oldNsSelectors, oldPodSelectors, err := ns.analyse(oldObj)
oldUID, oldRules, oldNsSelectors, oldPodSelectors, oldIPBlocks, err := ns.analyse(oldObj)
if err != nil {
return err
}
newUID, newRules, newNsSelectors, newPodSelectors, err := ns.analyse(newObj)
newUID, newRules, newNsSelectors, newPodSelectors, newIPBlocks, err := ns.analyse(newObj)
if err != nil {
return err
}
@@ -344,18 +362,24 @@ func (ns *ns) updateNetworkPolicy(oldObj, newObj interface{}) error {
if err := ns.podSelectors.deprovision(oldUID, oldPodSelectors, newPodSelectors); err != nil {
return err
}
if err := ns.ipBlocks.deprovision(oldUID, oldIPBlocks, newIPBlocks); err != nil {
return err
}
if err := ns.nsSelectors.provision(oldUID, oldNsSelectors, newNsSelectors); err != nil {
return err
}
if err := ns.podSelectors.provision(oldUID, oldPodSelectors, newPodSelectors); err != nil {
return err
}
if err := ns.ipBlocks.provision(oldUID, oldIPBlocks, newIPBlocks); err != nil {
return err
}
return ns.rules.provision(oldUID, oldRules, newRules)
}
func (ns *ns) deleteNetworkPolicy(obj interface{}) error {
// Analyse network policy to free resources
uid, rules, nsSelectors, podSelectors, err := ns.analyse(obj)
uid, rules, nsSelectors, podSelectors, ipBlocks, err := ns.analyse(obj)
if err != nil {
return err
}
@@ -369,73 +393,89 @@ func (ns *ns) deleteNetworkPolicy(obj interface{}) error {
if err := ns.nsSelectors.deprovision(uid, nsSelectors, nil); err != nil {
return err
}
return ns.podSelectors.deprovision(uid, podSelectors, nil)
}
func bypassRule(nsIpsetName ipset.Name, namespace string) []string {
return []string{"-m", "set", "--match-set", string(nsIpsetName), "dst", "-j", "ACCEPT", "-m", "comment", "--comment", "DefaultAllow isolation for namespace: " + namespace}
}
func (ns *ns) ensureBypassRule() error {
var ipset ipset.Name
if ns.legacy {
ipset = ns.allPods.ipsetName
} else {
ipset = ns.defaultAllowIPSet
if err := ns.podSelectors.deprovision(uid, podSelectors, nil); err != nil {
return err
}
if err := ns.ipBlocks.deprovision(uid, ipBlocks, nil); err != nil {
return err
}
common.Log.Debugf("ensuring rule for DefaultAllow in namespace: %s, set %s", ns.name, ipset)
return ns.ipt.Append(TableFilter, DefaultChain, bypassRule(ipset, ns.name)...)
return nil
}
func (ns *ns) deleteBypassRule() error {
var ipset ipset.Name
if ns.legacy {
ipset = ns.allPods.ipsetName
} else {
ipset = ns.defaultAllowIPSet
func (ns *ns) updateDefaultAllowIPSetEntry(oldObj, newObj *coreapi.Pod, ipsetName ipset.Name) error {
// Instead of iterating over all selectors we check whether old pod IP
// has been inserted into default-allow ipset to decide whether the IP
// in the ipset has to be updated.
if ns.ips.Exist(oldObj.ObjectMeta.UID, ipsetName, oldObj.Status.PodIP) {
if err := ns.ips.DelEntry(oldObj.ObjectMeta.UID, ipsetName, oldObj.Status.PodIP); err != nil {
return err
}
if err := ns.ips.AddEntry(newObj.ObjectMeta.UID, ipsetName, newObj.Status.PodIP, podComment(newObj)); err != nil {
return err
}
}
return nil
}
func bypassRules(namespace string, ingress, egress ipset.Name) map[string][][]string {
return map[string][][]string{
DefaultChain: {
{"-m", "set", "--match-set", string(ingress), "dst", "-j", "ACCEPT",
"-m", "comment", "--comment", "DefaultAllow ingress isolation for namespace: " + namespace},
},
EgressDefaultChain: {
{"-m", "set", "--match-set", string(egress), "src", "-j", EgressMarkChain,
"-m", "comment", "--comment", "DefaultAllow egress isolation for namespace: " + namespace},
{"-m", "set", "--match-set", string(egress), "src", "-j", "RETURN",
"-m", "comment", "--comment", "DefaultAllow egress isolation for namespace: " + namespace},
},
}
}
func (ns *ns) ensureBypassRules() error {
for chain, rules := range bypassRules(ns.name, ns.ingressDefaultAllowIPSet, ns.egressDefaultAllowIPSet) {
for _, rule := range rules {
common.Log.Debugf("adding rule for DefaultAllow in namespace: %s, chain: %s, %s", ns.name, chain, rule)
if err := ns.ipt.Append(TableFilter, chain, rule...); err != nil {
return err
}
}
}
common.Log.Debugf("removing default rule in namespace: %s, set %s", ns.name, ipset)
return ns.ipt.Delete(TableFilter, DefaultChain, bypassRule(ipset, ns.name)...)
return nil
}
func (ns *ns) deleteBypassRules() error {
for chain, rules := range bypassRules(ns.name, ns.ingressDefaultAllowIPSet, ns.egressDefaultAllowIPSet) {
for _, rule := range rules {
common.Log.Debugf("removing rule for DefaultAllow in namespace: %s, chain: %s, %s", ns.name, chain, rule)
if err := ns.ipt.Delete(TableFilter, chain, rule...); err != nil {
return err
}
}
}
return nil
}
func (ns *ns) addNamespace(obj *coreapi.Namespace) error {
ns.namespace = obj
// Insert a rule to bypass policies if namespace is DefaultAllow
if !ns.isDefaultDeny(obj) {
if err := ns.ensureBypassRule(); err != nil {
return err
}
// Insert a rule to bypass policies
if err := ns.ensureBypassRules(); err != nil {
return err
}
// Add namespace ipset to matching namespace selectors
_, err := ns.nsSelectors.addToMatching(obj.ObjectMeta.UID, obj.ObjectMeta.Labels, string(ns.allPods.ipsetName), namespaceComment(ns))
_, _, err := ns.nsSelectors.addToMatching(obj.ObjectMeta.UID, obj.ObjectMeta.Labels, string(ns.allPods.ipsetName), namespaceComment(ns))
return err
}
func (ns *ns) updateNamespace(oldObj, newObj *coreapi.Namespace) error {
ns.namespace = newObj
// Update bypass rule if ingress default has changed
oldDefaultDeny := ns.isDefaultDeny(oldObj)
newDefaultDeny := ns.isDefaultDeny(newObj)
if oldDefaultDeny != newDefaultDeny {
common.Log.Infof("namespace DefaultDeny changed from %t to %t", oldDefaultDeny, newDefaultDeny)
if oldDefaultDeny {
if err := ns.ensureBypassRule(); err != nil {
return err
}
}
if newDefaultDeny {
if err := ns.deleteBypassRule(); err != nil {
return err
}
}
}
// Re-evaluate namespace selector membership if labels have changed
if !equals(oldObj.ObjectMeta.Labels, newObj.ObjectMeta.Labels) {
for _, selector := range ns.nsSelectors.entries {
@@ -464,37 +504,24 @@ func (ns *ns) deleteNamespace(obj *coreapi.Namespace) error {
ns.namespace = nil
// Remove bypass rule
if !ns.isDefaultDeny(obj) {
if err := ns.deleteBypassRule(); err != nil {
return err
}
if err := ns.deleteBypassRules(); err != nil {
return err
}
// Remove namespace ipset from any matching namespace selectors
return ns.nsSelectors.delFromMatching(obj.ObjectMeta.UID, obj.ObjectMeta.Labels, string(ns.allPods.ipsetName))
}
func (ns *ns) isDefaultDeny(namespace *coreapi.Namespace) bool {
nnpJSON, found := namespace.ObjectMeta.Annotations["net.beta.kubernetes.io/network-policy"]
if !found {
return false
err := ns.nsSelectors.delFromMatching(obj.ObjectMeta.UID, obj.ObjectMeta.Labels, string(ns.allPods.ipsetName))
if err != nil {
return err
}
if !ns.legacy {
common.Log.Warn("DefaultDeny annotation is supported only in legacy mode (--use-legacy-netpol)")
return false
if err := ns.ips.Destroy(ns.ingressDefaultAllowIPSet); err != nil {
return err
}
if err := ns.ips.Destroy(ns.egressDefaultAllowIPSet); err != nil {
return err
}
var nnp NamespaceNetworkPolicy
if err := json.Unmarshal([]byte(nnpJSON), &nnp); err != nil {
common.Log.Warn("Ignoring network policy annotation: unmarshal failed:", err)
// If we can't understand the annotation, behave as if it isn't present
return false
}
return nnp.Ingress != nil &&
nnp.Ingress.Isolation != nil &&
*(nnp.Ingress.Isolation) == DefaultDeny
return nil
}
func hasIP(pod *coreapi.Pod) bool {
@@ -523,10 +550,19 @@ func podComment(pod *coreapi.Pod) string {
return fmt.Sprintf("namespace: %s, pod: %s", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name)
}
func (ns *ns) defaultAllowIPSetName(pt policyType) ipset.Name {
ipset := ns.ingressDefaultAllowIPSet
if pt == policyTypeEgress {
ipset = ns.egressDefaultAllowIPSet
}
return ipset
}
func (ns *ns) analyse(obj interface{}) (
uid types.UID,
rules map[string]*ruleSpec,
nsSelectors, podSelectors map[string]*selectorSpec,
ipBlocks map[string]*ipBlockSpec,
err error) {
switch p := obj.(type) {
@@ -541,16 +577,9 @@ func (ns *ns) analyse(obj interface{}) (
ns.policies[uid] = obj
// Analyse policy, determine which rules and ipsets are required
if ns.legacy {
rules, nsSelectors, podSelectors, err = ns.analysePolicyLegacy(obj.(*extnapi.NetworkPolicy))
if err != nil {
return
}
} else {
rules, nsSelectors, podSelectors, err = ns.analysePolicy(obj.(*networkingv1.NetworkPolicy))
if err != nil {
return
}
rules, nsSelectors, podSelectors, ipBlocks, err = ns.analysePolicy(obj.(*networkingv1.NetworkPolicy))
if err != nil {
return
}
return
+64 -23
View File
@@ -2,6 +2,7 @@ package npc
import (
"fmt"
"reflect"
"strings"
"k8s.io/apimachinery/pkg/types"
@@ -10,38 +11,71 @@ import (
"github.com/weaveworks/weave/npc/iptables"
)
type ruleSpec struct {
key string
args []string
type ruleHost interface {
// getRuleSpec returns source or destination specification and comment which
// are used in an iptables rule.
//
// If src=true then the rulespec is for source, otherwise - for destination.
getRuleSpec(src bool) ([]string, string)
}
func newRuleSpec(proto *string, srcHost *selectorSpec, dstHost *selectorSpec, dstPort *string) *ruleSpec {
type ruleSpec struct {
key string
args []string
policyType policyType
}
func newRuleSpec(policyType policyType, proto *string, srcHost, dstHost ruleHost, dstPort *string) *ruleSpec {
args := []string{}
if proto != nil {
args = append(args, "-p", *proto)
}
srcComment := "anywhere"
if srcHost != nil {
args = append(args, "-m", "set", "--match-set", string(srcHost.ipsetName), "src")
if srcHost.nsName != "" {
srcComment = fmt.Sprintf("pods: namespace: %s, selector: %s", srcHost.nsName, srcHost.key)
} else {
srcComment = fmt.Sprintf("namespaces: selector: %s", srcHost.key)
}
if srcHost != nil && !reflect.ValueOf(srcHost).IsNil() {
rule, comment := srcHost.getRuleSpec(true)
args = append(args, rule...)
srcComment = comment
}
dstComment := "anywhere"
if dstHost != nil {
args = append(args, "-m", "set", "--match-set", string(dstHost.ipsetName), "dst")
dstComment = fmt.Sprintf("pods: namespace: %s, selector: %s", dstHost.nsName, dstHost.key)
if dstHost != nil && !reflect.ValueOf(dstHost).IsNil() {
rule, comment := dstHost.getRuleSpec(false)
args = append(args, rule...)
dstComment = comment
}
if dstPort != nil {
args = append(args, "--dport", *dstPort)
}
args = append(args, "-j", "ACCEPT")
args = append(args, "-m", "comment", "--comment", fmt.Sprintf("%s -> %s", srcComment, dstComment))
// NOTE: if you remove the comment bellow, then embed `policyType` into `key`.
// Otherwise, the rule won't be provisioned if it exists for other policy type.
args = append(args, "-m", "comment", "--comment", fmt.Sprintf("%s -> %s (%s)", srcComment, dstComment, policyTypeStr(policyType)))
key := strings.Join(args, " ")
return &ruleSpec{key, args}
return &ruleSpec{key, args, policyType}
}
func (spec *ruleSpec) iptChain() string {
if spec.policyType == policyTypeEgress {
return EgressCustomChain
}
return IngressChain
}
func (spec *ruleSpec) iptRuleSpecs() [][]string {
if spec.policyType == policyTypeIngress {
rule := make([]string, len(spec.args))
copy(rule, spec.args)
rule = append(rule, "-j", "ACCEPT")
return [][]string{rule}
}
// policyTypeEgress
ruleMark := make([]string, len(spec.args))
copy(ruleMark, spec.args)
ruleMark = append(ruleMark, "-j", EgressMarkChain)
ruleReturn := make([]string, len(spec.args))
copy(ruleReturn, spec.args)
ruleReturn = append(ruleReturn, "-j", "RETURN")
return [][]string{ruleMark, ruleReturn}
}
type ruleSet struct {
@@ -58,10 +92,14 @@ func (rs *ruleSet) deprovision(user types.UID, current, desired map[string]*rule
if _, found := desired[key]; !found {
delete(rs.users[key], user)
if len(rs.users[key]) == 0 {
common.Log.Infof("deleting rule: %v", spec.args)
if err := rs.ipt.Delete(TableFilter, IngressChain, spec.args...); err != nil {
return err
chain := spec.iptChain()
for _, rule := range spec.iptRuleSpecs() {
common.Log.Infof("deleting rule %v from %q chain", rule, chain)
if err := rs.ipt.Delete(TableFilter, chain, rule...); err != nil {
return err
}
}
delete(rs.users, key)
}
}
@@ -74,9 +112,12 @@ func (rs *ruleSet) provision(user types.UID, current, desired map[string]*ruleSp
for key, spec := range desired {
if _, found := current[key]; !found {
if _, found := rs.users[key]; !found {
common.Log.Infof("adding rule: %v", spec.args)
if err := rs.ipt.Append(TableFilter, IngressChain, spec.args...); err != nil {
return err
chain := spec.iptChain()
for _, rule := range spec.iptRuleSpecs() {
common.Log.Infof("adding rule %v to %q chain", rule, chain)
if err := rs.ipt.Append(TableFilter, chain, rule...); err != nil {
return err
}
}
rs.users[key] = make(map[types.UID]struct{})
}
+1 -1
View File
@@ -9,7 +9,7 @@ func TestRegressionHandleNil3095(t *testing.T) {
// Test for handling nil values
// https://github.com/weaveworks/weave/issues/3095
rs := newRuleSpec(nil, nil, nil, nil)
rs := newRuleSpec(policyTypeIngress, nil, nil, nil, nil)
require.NotEqual(t, rs.key, "")
+66 -38
View File
@@ -1,34 +1,36 @@
package npc
import (
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/npc/ipset"
"github.com/weaveworks/weave/net/ipset"
)
type selectorSpec struct {
key string // string representation (for hash keying/equality comparison)
selector labels.Selector // k8s Selector object (for matching)
dst bool // destination selector
key string // string representation (for hash keying/equality comparison)
selector labels.Selector // k8s Selector object (for matching)
policyTypes []policyType // If non-empty, then selectorSpec is a target selector for given policyTypes.
ipsetType ipset.Type // type of ipset to provision
ipsetName ipset.Name // generated ipset name
nsName string // Namespace name
}
func newSelectorSpec(json *metav1.LabelSelector, dst bool, nsName string, ipsetType ipset.Type) (*selectorSpec, error) {
func newSelectorSpec(json *metav1.LabelSelector, policyType []policyType, nsName string, ipsetType ipset.Type) (*selectorSpec, error) {
selector, err := metav1.LabelSelectorAsSelector(json)
if err != nil {
return nil, err
}
key := selector.String()
return &selectorSpec{
key: key,
selector: selector,
dst: dst,
key: key,
selector: selector,
policyTypes: policyType,
// We prefix the selector string with the namespace name when generating
// the shortname because you can specify the same selector in multiple
// namespaces - we need those to map to distinct ipsets
@@ -37,6 +39,23 @@ func newSelectorSpec(json *metav1.LabelSelector, dst bool, nsName string, ipsetT
nsName: nsName}, nil
}
func (spec *selectorSpec) getRuleSpec(src bool) ([]string, string) {
dir := "dst"
if src {
dir = "src"
}
rule := []string{"-m", "set", "--match-set", string(spec.ipsetName), dir}
comment := "anywhere"
if spec.nsName != "" {
comment = fmt.Sprintf("pods: namespace: %s, selector: %s", spec.nsName, spec.key)
} else {
comment = fmt.Sprintf("namespaces: selector: %s", spec.key)
}
return rule, comment
}
type selector struct {
ips ipset.Interface
spec *selectorSpec
@@ -55,49 +74,54 @@ func (s *selector) delEntry(user types.UID, entry string) error {
}
type selectorFn func(selector *selector) error
type selectorWithPolicyTypeFn func(selector *selector, policyType policyType) error
type selectorSet struct {
ips ipset.Interface
onNewSelector selectorFn
// invoked after dst selector has been provisioned for the first time
onNewDstSelector selectorFn
// invoked after the last instance of dst selector has been deprovisioned
onDestroyDstSelector selectorFn
// invoked after target selector has been provisioned for the first time
onNewTargetSelector selectorWithPolicyTypeFn
// invoked after the last instance of target selector has been deprovisioned
onDestroyTargetSelector selectorWithPolicyTypeFn
users map[string]map[types.UID]struct{} // list of users per selector
entries map[string]*selector
// We need to keep track of dst selector instances to be able to invoke
// onNewDstSelector and onDestroyDstSelector callbacks at the right time;
// selectorSpec.Key -> count
dstSelectorsCount map[string]int
// We need to keep track of target selector instances to be able to invoke
// onNewTargetSelector and onDestroyTargetSelector callbacks at the right time;
// selectorSpec.Key -> policyType -> count
targetSelectorsCount map[string]map[policyType]int
}
func newSelectorSet(ips ipset.Interface, onNewSelector, onNewDstSelector selectorFn, onDestroyDstSelector selectorFn) *selectorSet {
func newSelectorSet(ips ipset.Interface, onNewSelector selectorFn, onNewTargetSelector, onDestroyTargetSelector selectorWithPolicyTypeFn) *selectorSet {
return &selectorSet{
ips: ips,
onNewSelector: onNewSelector,
onNewDstSelector: onNewDstSelector,
onDestroyDstSelector: onDestroyDstSelector,
ips: ips,
onNewSelector: onNewSelector,
onNewTargetSelector: onNewTargetSelector,
onDestroyTargetSelector: onDestroyTargetSelector,
users: make(map[string]map[types.UID]struct{}),
entries: make(map[string]*selector),
dstSelectorsCount: make(map[string]int)}
targetSelectorsCount: make(map[string]map[policyType]int)}
}
func (ss *selectorSet) addToMatching(user types.UID, labelMap map[string]string, entry string, comment string) (bool, error) {
found := false
func (ss *selectorSet) addToMatching(user types.UID, labelMap map[string]string, entry string, comment string) (bool, bool, error) {
foundIngress := false
foundEgress := false
for _, s := range ss.entries {
if s.matches(labelMap) {
if ss.dstSelectorExist(s) {
found = true
if ss.targetSelectorExist(s, policyTypeIngress) {
foundIngress = true
}
if ss.targetSelectorExist(s, policyTypeEgress) {
foundEgress = true
}
if err := s.addEntry(user, entry, comment); err != nil {
return found, err
return foundIngress, foundEgress, err
}
}
}
return found, nil
return foundIngress, foundEgress, nil
}
func (ss *selectorSet) delFromMatching(user types.UID, labelMap map[string]string, entry string) error {
@@ -111,8 +135,8 @@ func (ss *selectorSet) delFromMatching(user types.UID, labelMap map[string]strin
return nil
}
func (ss *selectorSet) dstSelectorExist(s *selector) bool {
return ss.dstSelectorsCount[s.spec.key] > 0
func (ss *selectorSet) targetSelectorExist(s *selector, policyType policyType) bool {
return ss.targetSelectorsCount[s.spec.key][policyType] > 0
}
func (ss *selectorSet) deprovision(user types.UID, current, desired map[string]*selectorSpec) error {
@@ -129,13 +153,14 @@ func (ss *selectorSet) deprovision(user types.UID, current, desired map[string]*
delete(ss.users, key)
}
if spec.dst {
ss.dstSelectorsCount[key]--
if ss.dstSelectorsCount[key] == 0 {
if err := ss.onDestroyDstSelector(&selector{ss.ips, spec}); err != nil {
for _, policyType := range spec.policyTypes {
ss.targetSelectorsCount[key][policyType]--
if ss.targetSelectorsCount[key][policyType] == 0 {
if err := ss.onDestroyTargetSelector(&selector{ss.ips, spec}, policyType); err != nil {
return err
}
}
// TODO(brb) delete(...)
}
}
}
@@ -160,10 +185,13 @@ func (ss *selectorSet) provision(user types.UID, current, desired map[string]*se
}
ss.users[key][user] = struct{}{}
if spec.dst {
ss.dstSelectorsCount[key]++
if ss.dstSelectorsCount[key] == 1 {
if err := ss.onNewDstSelector(selector); err != nil {
for _, pt := range spec.policyTypes {
if _, found := ss.targetSelectorsCount[key]; !found {
ss.targetSelectorsCount[key] = make(map[policyType]int)
}
ss.targetSelectorsCount[key][pt]++
if ss.targetSelectorsCount[key][pt] == 1 {
if err := ss.onNewTargetSelector(selector, pt); err != nil {
return err
}
}
+15
View File
@@ -0,0 +1,15 @@
package npc
type policyType byte
const (
policyTypeIngress policyType = iota
policyTypeEgress
)
func policyTypeStr(policyType policyType) string {
if policyType == policyTypeEgress {
return "egress"
}
return "ingress"
}
+4
View File
@@ -110,6 +110,10 @@ func (i *Ipam) ReleaseAddress(poolID string, address net.IP) error {
return i.weave.ReleaseIPsFor(address.String())
}
func (i *Ipam) IsBuiltIn() bool {
return false
}
// Functions required by ipamapi "contract" but not actually used.
func (i *Ipam) DiscoverNew(discoverapi.DiscoveryType, interface{}) error {
+18 -2
View File
@@ -117,6 +117,16 @@ func (c *CNIPlugin) CmdAdd(args *skel.CmdArgs) error {
}
defer ns.Close()
hostNs, err := netns.Get()
if err != nil {
return fmt.Errorf("error accessing host network namespace: %s", err)
}
defer hostNs.Close()
if ns.Equal(hostNs) {
return fmt.Errorf("can not specify host network namespace as network namespace to which container should be added")
}
id := args.ContainerID
if len(id) < 5 {
data := make([]byte, 5)
@@ -130,7 +140,7 @@ func (c *CNIPlugin) CmdAdd(args *skel.CmdArgs) error {
if err := weavenet.AttachContainer(args.Netns, id, args.IfName, conf.BrName, conf.MTU, false, []*net.IPNet{&ip.Address}, false, conf.HairpinMode); err != nil {
return err
}
if err := weavenet.WithNetNSLinkUnsafe(ns, args.IfName, func(link netlink.Link) error {
if err := weavenet.WithNetNSLink(ns, args.IfName, func(link netlink.Link) error {
return setupRoutes(link, args.IfName, ip.Address, ip.Gateway, result.Routes)
}); err != nil {
return fmt.Errorf("error setting up routes: %s", err)
@@ -177,7 +187,13 @@ func (c *CNIPlugin) CmdDel(args *skel.CmdArgs) error {
// As of CNI 0.3 spec, runtimes can send blank if they just want the address deallocated
if args.Netns != "" {
if _, err = weavenet.WithNetNS(args.Netns, "del-iface", args.IfName); err != nil {
if err = weavenet.WithNetNSByPath(args.Netns, func() error {
link, err := netlink.LinkByName(args.IfName)
if err != nil {
return err
}
return netlink.LinkDel(link)
}); err != nil {
// We log the error and carry on instead of returning nil, as there may still be resources to free in IPAM.
logOnStderr(fmt.Errorf("error removing interface %q: %s", args.IfName, err))
}
+3 -1
View File
@@ -49,7 +49,9 @@ func (w *watcher) ContainerStarted(id string) {
rootDir = "/host"
}
netNSPath := weavenet.NSPathByPidWithRoot(rootDir, info.State.Pid)
if _, err := weavenet.WithNetNS(netNSPath, "configure-arp", weavenet.VethName, rootDir); err != nil {
if err := weavenet.WithNetNSByPath(netNSPath, func() error {
return weavenet.ConfigureARP(weavenet.VethName, rootDir)
}); err != nil {
w.driver.warn("ContainerStarted", "unable to configure interfaces: %s", err)
}
}
-236
View File
@@ -1,236 +0,0 @@
/*
Package main deals with weave-net peers on the cluster.
This involves peer management, such as getting the latest peers or removing defunct peers from the cluster
*/
package main
import (
"flag"
"fmt"
"net"
"os"
api "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
weaveapi "github.com/weaveworks/weave/api"
"github.com/weaveworks/weave/common"
)
type nodeInfo struct {
name string
addr string
}
// return the IP addresses of all nodes in the cluster
func getKubePeers(c *kubernetes.Clientset) ([]nodeInfo, error) {
nodeList, err := c.CoreV1().Nodes().List(api.ListOptions{})
if err != nil {
return nil, err
}
addresses := make([]nodeInfo, 0, len(nodeList.Items))
for _, peer := range nodeList.Items {
var internalIP, externalIP string
for _, addr := range peer.Status.Addresses {
// Check it's a valid ipv4 address
ip := net.ParseIP(addr.Address)
if ip == nil || ip.To4() == nil {
continue
}
if addr.Type == "InternalIP" {
internalIP = ip.To4().String()
} else if addr.Type == "ExternalIP" {
externalIP = ip.To4().String()
}
}
// Fallback for cases where a Node has an ExternalIP but no InternalIP
if internalIP != "" {
addresses = append(addresses, nodeInfo{name: peer.Name, addr: internalIP})
} else if externalIP != "" {
addresses = append(addresses, nodeInfo{name: peer.Name, addr: externalIP})
}
}
return addresses, nil
}
const (
configMapName = "weave-net"
configMapNamespace = "kube-system"
)
// update the list of all peers that have gone through this code path
func addMyselfToPeerList(cml *configMapAnnotations, c *kubernetes.Clientset, peerName, name string) (*peerList, error) {
var list *peerList
err := cml.LoopUpdate(func() error {
var err error
list, err = cml.GetPeerList()
if err != nil {
return err
}
if !list.contains(peerName) {
list.add(peerName, name)
err = cml.UpdatePeerList(*list)
if err != nil {
return err
}
}
return nil
})
return list, err
}
func checkIamInPeerList(cml *configMapAnnotations, c *kubernetes.Clientset, peerName string) (bool, error) {
if err := cml.Init(); err != nil {
return false, err
}
list, err := cml.GetPeerList()
if err != nil {
return false, err
}
common.Log.Debugf("[kube-peers] Checking peer %q against list %v", peerName, list)
return list.contains(peerName), nil
}
// For each of those peers that is no longer listed as a node by
// Kubernetes, remove it from Weave IPAM
func reclaimRemovedPeers(weave *weaveapi.Client, cml *configMapAnnotations, nodes []nodeInfo, myPeerName string) error {
for {
if err := cml.Init(); err != nil {
return err
}
// 1. Compare peers stored in the peerList against all peers reported by k8s now.
storedPeerList, err := cml.GetPeerList()
if err != nil {
return err
}
peerMap := make(map[string]peerInfo, len(storedPeerList.Peers))
for _, peer := range storedPeerList.Peers {
peerMap[peer.NodeName] = peer
}
for _, node := range nodes {
delete(peerMap, node.name)
}
common.Log.Debugln("[kube-peers] Nodes that have disappeared:", peerMap)
if len(peerMap) == 0 {
break
}
// 2. Loop for each X in the first set and not in the second - we wish to remove X from our data structures
for _, peer := range peerMap {
common.Log.Debugln("[kube-peers] Preparing to remove disappeared peer", peer)
okToRemove := false
// 3. Check if there is an existing annotation with key X
if existingAnnotation, found := cml.GetAnnotation(KubePeersPrefix + peer.PeerName); found {
common.Log.Debugln("[kube-peers] Existing annotation", existingAnnotation)
// 4. If annotation already contains my identity, ok;
if existingAnnotation == myPeerName {
okToRemove = true
}
} else {
// 5. If non-existent, write an annotation with key X and contents "my identity"
common.Log.Debugln("[kube-peers] Noting I plan to remove ", peer.PeerName)
if err := cml.UpdateAnnotation(KubePeersPrefix+peer.PeerName, myPeerName); err == nil {
okToRemove = true
} else {
common.Log.Debugln("[kube-peers] error from UpdateAnnotation: ", err)
}
}
if okToRemove {
// 6. If step 4 or 5 succeeded, rmpeer X
result, err := weave.RmPeer(peer.PeerName)
common.Log.Infof("[kube-peers] rmpeer of %s: %s", peer.PeerName, result)
if err != nil {
return err
}
cml.LoopUpdate(func() error {
// 7aa. Remove any annotations Z* that have contents X
if err := cml.RemoveAnnotationsWithValue(peer.PeerName); err != nil {
return err
}
// 7a. Remove X from peerList
storedPeerList.remove(peer.PeerName)
if err := cml.UpdatePeerList(*storedPeerList); err != nil {
return err
}
// 7b. Remove annotation with key X
return cml.RemoveAnnotation(KubePeersPrefix + peer.PeerName)
})
}
// 8. If step 5 failed due to optimistic lock conflict, stop: someone else is handling X
// Step 3-5 is to protect against two simultaneous rmpeers of X
// Step 4 is to pick up again after a restart between step 5 and step 7b
// If the peer doing the reclaim disappears between steps 5 and 7a, then someone will clean it up in step 7aa
// If peer doing the reclaim disappears forever between 7a and 7b then we get a dangling annotation
// This should be sufficiently rare that we don't care.
}
// 9. Go back to step 1 until there is no difference between the two sets
}
// Question: Should we narrow step 2 by checking against Weave Net IPAM?
// i.e. If peer X owns any address space and is marked unreachable, we want to rmpeer X
return nil
}
func main() {
var (
justReclaim bool
justCheck bool
peerName string
nodeName string
logLevel string
)
flag.BoolVar(&justReclaim, "reclaim", false, "reclaim IP space from dead peers")
flag.BoolVar(&justCheck, "check-peer-new", false, "return success if peer name is not stored in annotation")
flag.StringVar(&peerName, "peer-name", "unknown", "name of this Weave Net peer")
flag.StringVar(&nodeName, "node-name", "unknown", "name of this Kubernetes node")
flag.StringVar(&logLevel, "log-level", "info", "logging level (debug, info, warning, error)")
flag.Parse()
common.SetLogLevel(logLevel)
config, err := rest.InClusterConfig()
if err != nil {
common.Log.Fatalf("[kube-peers] Could not get cluster config: %v", err)
}
c, err := kubernetes.NewForConfig(config)
if err != nil {
common.Log.Fatalf("[kube-peers] Could not make Kubernetes connection: %v", err)
}
if justCheck {
cml := newConfigMapAnnotations(configMapNamespace, configMapName, c)
exists, err := checkIamInPeerList(cml, c, peerName)
if err != nil {
common.Log.Fatalf("[kube-peers] Could not check peer list: %v", err)
}
if exists {
os.Exit(9)
} else {
os.Exit(0)
}
}
peers, err := getKubePeers(c)
if err != nil {
common.Log.Fatalf("[kube-peers] Could not get peers: %v", err)
}
if justReclaim {
cml := newConfigMapAnnotations(configMapNamespace, configMapName, c)
list, err := addMyselfToPeerList(cml, c, peerName, nodeName)
if err != nil {
common.Log.Fatalf("[kube-peers] Could not update peer list: %v", err)
}
common.Log.Infoln("[kube-peers] Added myself to peer list", list)
weave := weaveapi.NewClient(os.Getenv("WEAVE_HTTP_ADDR"), common.Log)
err = reclaimRemovedPeers(weave, cml, peers, peerName)
if err != nil {
common.Log.Fatalf("[kube-peers] Error while reclaiming space: %v", err)
}
return
}
for _, node := range peers {
fmt.Println(node.addr)
}
}
@@ -2,13 +2,10 @@
In order to keep track of active weave peers, we use annotations on the Kubernetes cluster.
Kubernetes uses etcd to distribute and synchronise these annotations so we don't have to.
This module deals with operations on the peerlist backed by Kubernetes' annotation mechanism.
*/
package main
import (
"encoding/json"
"log"
"time"
@@ -29,43 +26,11 @@ type configMapAnnotations struct {
cm *v1.ConfigMap
}
func newConfigMapAnnotations(ns string, configMapName string, clientset *kubernetes.Clientset) *configMapAnnotations {
func newConfigMapAnnotations(ns string, configMapName string, c kubernetes.Interface) *configMapAnnotations {
return &configMapAnnotations{
Namespace: ns,
ConfigMapName: configMapName,
Client: clientset.CoreV1(),
}
}
type peerList struct {
Peers []peerInfo
}
type peerInfo struct {
PeerName string // Weave internal unique ID
NodeName string // Kubernetes node name
}
func (pl peerList) contains(peerName string) bool {
for _, peer := range pl.Peers {
if peer.PeerName == peerName {
return true
}
}
return false
}
func (pl *peerList) add(peerName string, name string) {
pl.Peers = append(pl.Peers, peerInfo{PeerName: peerName, NodeName: name})
}
func (pl *peerList) remove(peerNameToRemove string) {
for i := 0; i < len(pl.Peers); {
if pl.Peers[i].PeerName == peerNameToRemove {
pl.Peers = append(pl.Peers[:i], pl.Peers[i+1:]...)
} else {
i++
}
Client: c.CoreV1(),
}
}
@@ -111,27 +76,6 @@ func (cml *configMapAnnotations) Init() error {
return nil
}
func (cml *configMapAnnotations) GetPeerList() (*peerList, error) {
var record peerList
if cml.cm == nil {
return nil, errors.New("endpoint not initialized, call Init first")
}
if recordBytes, found := cml.cm.Annotations[KubePeersAnnotationKey]; found {
if err := json.Unmarshal([]byte(recordBytes), &record); err != nil {
return nil, err
}
}
return &record, nil
}
func (cml *configMapAnnotations) UpdatePeerList(list peerList) error {
recordBytes, err := json.Marshal(list)
if err != nil {
return err
}
return cml.UpdateAnnotation(KubePeersAnnotationKey, string(recordBytes))
}
// Clean up a string so it meets the Kubernetes requiremements for Annotation keys:
// name part must consist of alphanumeric characters, '-', '_' or '.', and must
// start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc')
+30
View File
@@ -0,0 +1,30 @@
// Adapted from k8s.io/client-go/kubernetes/fake to use a modified ObjectTracker
package main
import (
"k8s.io/apimachinery/pkg/runtime"
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/testing"
weavetracker "github.com/weaveworks/weave/testing/kubernetes/testing"
)
var scheme = runtime.NewScheme()
var codecs = serializer.NewCodecFactory(scheme)
func NewSimpleClientset(objects ...runtime.Object) *fake.Clientset {
o := weavetracker.NewObjectTracker(scheme, codecs.UniversalDecoder())
for _, obj := range objects {
if err := o.Add(obj); err != nil {
panic(err)
}
}
ret := fake.Clientset{}
ret.AddReactor("*", "*", weavetracker.ObjectReaction(o))
ret.AddWatchReactor("*", testing.DefaultWatchReactor(watch.NewFake(), nil))
return &ret
}
+254
View File
@@ -0,0 +1,254 @@
/*
Package main deals with weave-net peers on the cluster.
This involves peer management, such as getting the latest peers or removing defunct peers from the cluster
*/
package main
import (
"flag"
"fmt"
"net"
"os"
api "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
weaveapi "github.com/weaveworks/weave/api"
"github.com/weaveworks/weave/common"
)
type nodeInfo struct {
name string
addr string
}
// return the IP addresses of all nodes in the cluster
func getKubePeers(c kubernetes.Interface, includeWithNoIPAddr bool) ([]nodeInfo, error) {
nodeList, err := c.CoreV1().Nodes().List(api.ListOptions{})
if err != nil {
return nil, err
}
addresses := make([]nodeInfo, 0, len(nodeList.Items))
for _, peer := range nodeList.Items {
var internalIP, externalIP string
for _, addr := range peer.Status.Addresses {
// Check it's a valid ipv4 address
ip := net.ParseIP(addr.Address)
if ip == nil || ip.To4() == nil {
continue
}
if addr.Type == "InternalIP" {
internalIP = ip.To4().String()
} else if addr.Type == "ExternalIP" {
externalIP = ip.To4().String()
}
}
// Fallback for cases where a Node has an ExternalIP but no InternalIP
if internalIP != "" {
addresses = append(addresses, nodeInfo{name: peer.Name, addr: internalIP})
} else if externalIP != "" {
addresses = append(addresses, nodeInfo{name: peer.Name, addr: externalIP})
} else if includeWithNoIPAddr {
addresses = append(addresses, nodeInfo{name: peer.Name, addr: ""})
}
}
return addresses, nil
}
// (minimal, incomplete) interface so weaver can be mocked for testing.
type weaveClient interface {
RmPeer(peerName string) (string, error)
}
// For each of those peers that is no longer listed as a node by
// Kubernetes, remove it from Weave IPAM
func reclaimRemovedPeers(kube kubernetes.Interface, weave weaveClient, cml *configMapAnnotations, myPeerName, myNodeName string) error {
for loopsWhenNothingChanged := 0; loopsWhenNothingChanged < 3; loopsWhenNothingChanged++ {
if err := cml.Init(); err != nil {
return err
}
// 1. Compare peers stored in the peerList against all peers reported by k8s now.
storedPeerList, err := cml.GetPeerList()
if err != nil {
return err
}
nodes, err := getKubePeers(kube, true)
nodeSet := make(map[string]struct{}, len(nodes))
for _, node := range nodes {
nodeSet[node.name] = struct{}{}
}
peerMap := make(map[string]peerInfo, len(storedPeerList.Peers))
for _, peer := range storedPeerList.Peers {
peerMap[peer.PeerName] = peer
}
// remove entries from the peer map that are current nodes
for key, peer := range peerMap {
if _, found := nodeSet[peer.NodeName]; found {
// unless they have a duplicate of my NodeName but are not me
if peer.NodeName == myNodeName && peer.PeerName != myPeerName {
continue
}
delete(peerMap, key)
}
}
// so the remainder is everything we want to clean up
common.Log.Debugln("[kube-peers] Nodes that have disappeared:", peerMap)
if len(peerMap) == 0 {
break
}
// 2. Loop for each X in the first set and not in the second - we wish to remove X from our data structures
for _, peer := range peerMap {
if peer.PeerName == myPeerName { // Don't remove myself.
common.Log.Warnln("[kube-peers] not removing myself", peer)
continue
}
changed, err := reclaimPeer(weave, cml, peer.PeerName, myPeerName)
if err != nil {
return err
}
if changed {
loopsWhenNothingChanged = 0
}
}
// 9. Go back to step 1 until there is no difference between the two sets
// (or we hit the counter that says we've been round the loop 3 times and nothing happened)
}
return nil
}
// Attempt to reclaim the IP addresses owned by peerName, using the
// Kubernetes api-server as a point of consensus so that only one peer
// actions the reclaim.
// Return a bool to show whether we attempted to change anything,
// and an error if something went wrong.
func reclaimPeer(weave weaveClient, cml *configMapAnnotations, peerName string, myPeerName string) (changed bool, err error) {
common.Log.Debugln("[kube-peers] Preparing to remove disappeared peer", peerName)
okToRemove := false
// 3. Check if there is an existing annotation with key X
if existingAnnotation, found := cml.GetAnnotation(KubePeersPrefix + peerName); found {
common.Log.Debugln("[kube-peers] Existing annotation", existingAnnotation)
// 4. If annotation already contains my identity, ok;
if existingAnnotation == myPeerName {
okToRemove = true
}
} else {
// 5. If non-existent, write an annotation with key X and contents "my identity"
common.Log.Debugln("[kube-peers] Noting I plan to remove ", peerName)
if err := cml.UpdateAnnotation(KubePeersPrefix+peerName, myPeerName); err == nil {
okToRemove = true
} else {
common.Log.Errorln("[kube-peers] error from UpdateAnnotation: ", err)
}
}
if !okToRemove {
return false, nil
}
// 6. If step 4 or 5 succeeded, rmpeer X
result, err := weave.RmPeer(peerName)
common.Log.Infof("[kube-peers] rmpeer of %s: %s", peerName, result)
if err != nil {
return false, err
}
err = cml.LoopUpdate(func() error {
// 7aa. Remove any annotations Z* that have contents X
if err := cml.RemoveAnnotationsWithValue(peerName); err != nil {
return err
}
// 7a. Remove X from peerList
storedPeerList, err := cml.GetPeerList()
if err != nil {
return err
}
storedPeerList.remove(peerName)
if err := cml.UpdatePeerList(*storedPeerList); err != nil {
return err
}
// 7b. Remove annotation with key X
return cml.RemoveAnnotation(KubePeersPrefix + peerName)
})
// 8. If step 5 failed due to optimistic lock conflict, stop: someone else is handling X
// Step 3-5 is to protect against two simultaneous rmpeers of X
// Step 4 is to pick up again after a restart between step 5 and step 7b
// If the peer doing the reclaim disappears between steps 5 and 7a, then someone will clean it up in step 7aa
// If peer doing the reclaim disappears forever between 7a and 7b then we get a dangling annotation
// This should be sufficiently rare that we don't care.
// Question: Should we narrow step 2 by checking against Weave Net IPAM?
// i.e. If peer X owns any address space and is marked unreachable, we want to rmpeer X
return true, err
}
func main() {
var (
justReclaim bool
justCheck bool
justSetNodeStatus bool
peerName string
nodeName string
logLevel string
)
flag.BoolVar(&justReclaim, "reclaim", false, "reclaim IP space from dead peers")
flag.BoolVar(&justCheck, "check-peer-new", false, "return success if peer name is not stored in annotation")
flag.BoolVar(&justSetNodeStatus, "set-node-status", false, "set NodeNetworkUnavailable to false")
flag.StringVar(&peerName, "peer-name", "unknown", "name of this Weave Net peer")
flag.StringVar(&nodeName, "node-name", "unknown", "name of this Kubernetes node")
flag.StringVar(&logLevel, "log-level", "info", "logging level (debug, info, warning, error)")
flag.Parse()
common.SetLogLevel(logLevel)
config, err := rest.InClusterConfig()
if err != nil {
common.Log.Fatalf("[kube-peers] Could not get cluster config: %v", err)
}
c, err := kubernetes.NewForConfig(config)
if err != nil {
common.Log.Fatalf("[kube-peers] Could not make Kubernetes connection: %v", err)
}
if justCheck {
cml := newConfigMapAnnotations(configMapNamespace, configMapName, c)
exists, err := checkIamInPeerList(cml, c, peerName)
if err != nil {
common.Log.Fatalf("[kube-peers] Could not check peer list: %v", err)
}
if exists {
os.Exit(9)
} else {
os.Exit(0)
}
}
if justSetNodeStatus {
err := setNodeNetworkUnavailableFalse(c, nodeName)
if err != nil {
common.Log.Fatalf("[kube-peers] could not set node status: %v", err)
}
return
}
if err != nil {
common.Log.Fatalf("[kube-peers] Could not get peers: %v", err)
}
if justReclaim {
cml := newConfigMapAnnotations(configMapNamespace, configMapName, c)
list, err := addMyselfToPeerList(cml, c, peerName, nodeName)
if err != nil {
common.Log.Fatalf("[kube-peers] Could not update peer list: %v", err)
}
common.Log.Infoln("[kube-peers] Added myself to peer list", list)
weave := weaveapi.NewClient(os.Getenv("WEAVE_HTTP_ADDR"), common.Log)
err = reclaimRemovedPeers(c, weave, cml, peerName, nodeName)
if err != nil {
common.Log.Fatalf("[kube-peers] Error while reclaiming space: %v", err)
}
return
}
peers, err := getKubePeers(c, false)
for _, node := range peers {
fmt.Println(node.addr)
}
}
+29
View File
@@ -0,0 +1,29 @@
package main
import (
"encoding/json"
"fmt"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
// based on k8s.io/kubernetes/pkg/controller/nodeipam/ipam/adapter.go
func setNodeNetworkUnavailableFalse(c kubernetes.Interface, nodeName string) error {
condition := v1.NodeCondition{
Type: v1.NodeNetworkUnavailable,
Status: v1.ConditionFalse,
Reason: "WeaveIsUp",
Message: "Weave pod has set this",
LastTransitionTime: metav1.Now(),
LastHeartbeatTime: metav1.Now(),
}
raw, err := json.Marshal(&[]v1.NodeCondition{condition})
if err != nil {
return err
}
patch := []byte(fmt.Sprintf(`{"status":{"conditions":%s}}`, raw))
_, err = c.CoreV1().Nodes().PatchStatus(nodeName, patch)
return err
}
+104
View File
@@ -0,0 +1,104 @@
/*
This module deals with operations on the peerlist backed by Kubernetes' annotation mechanism.
*/
package main
import (
"encoding/json"
"github.com/pkg/errors"
"k8s.io/client-go/kubernetes"
"github.com/weaveworks/weave/common"
)
const (
configMapName = "weave-net"
configMapNamespace = "kube-system"
)
type peerList struct {
Peers []peerInfo
}
type peerInfo struct {
PeerName string // Weave internal unique ID
NodeName string // Kubernetes node name
}
func (pl peerList) contains(peerName string) bool {
for _, peer := range pl.Peers {
if peer.PeerName == peerName {
return true
}
}
return false
}
func (pl *peerList) add(peerName string, name string) {
pl.Peers = append(pl.Peers, peerInfo{PeerName: peerName, NodeName: name})
}
func (pl *peerList) remove(peerNameToRemove string) {
for i := 0; i < len(pl.Peers); {
if pl.Peers[i].PeerName == peerNameToRemove {
pl.Peers = append(pl.Peers[:i], pl.Peers[i+1:]...)
} else {
i++
}
}
}
func (cml *configMapAnnotations) GetPeerList() (*peerList, error) {
var record peerList
if cml.cm == nil {
return nil, errors.New("endpoint not initialized, call Init first")
}
if recordBytes, found := cml.cm.Annotations[KubePeersAnnotationKey]; found {
if err := json.Unmarshal([]byte(recordBytes), &record); err != nil {
return nil, err
}
}
return &record, nil
}
func (cml *configMapAnnotations) UpdatePeerList(list peerList) error {
recordBytes, err := json.Marshal(list)
if err != nil {
return err
}
return cml.UpdateAnnotation(KubePeersAnnotationKey, string(recordBytes))
}
// update the list of all peers that have gone through this code path
func addMyselfToPeerList(cml *configMapAnnotations, c kubernetes.Interface, peerName, name string) (*peerList, error) {
var list *peerList
err := cml.LoopUpdate(func() error {
var err error
list, err = cml.GetPeerList()
if err != nil {
return err
}
if !list.contains(peerName) {
list.add(peerName, name)
err = cml.UpdatePeerList(*list)
if err != nil {
return err
}
}
return nil
})
return list, err
}
func checkIamInPeerList(cml *configMapAnnotations, c kubernetes.Interface, peerName string) (bool, error) {
if err := cml.Init(); err != nil {
return false, err
}
list, err := cml.GetPeerList()
if err != nil {
return false, err
}
common.Log.Debugf("[kube-peers] Checking peer %q against list %v", peerName, list)
return list.contains(peerName), nil
}
+181
View File
@@ -0,0 +1,181 @@
package main
import (
"fmt"
"math/rand"
"sync"
"testing"
"github.com/stretchr/testify/require"
)
const (
peerName1 = "01:00:00:00:00:00"
peerName2 = "02:00:00:00:00:00"
peerName3 = "03:00:00:00:00:00"
nodeName1 = "fake-node-1"
nodeName2 = "fake-node-2"
testIters = 1000
)
func TestPeerListBasic(t *testing.T) {
c := NewSimpleClientset()
cml := newConfigMapAnnotations(configMapNamespace, configMapName, c)
err := cml.Init()
require.NoError(t, err)
list1, err := addMyselfToPeerList(cml, c, peerName1, nodeName1)
require.NoError(t, err)
require.Equal(t, 1, len(list1.Peers))
list2, err := addMyselfToPeerList(cml, c, peerName2, nodeName2)
require.NoError(t, err)
require.Equal(t, 2, len(list2.Peers))
check1, err := checkIamInPeerList(cml, c, peerName1)
require.NoError(t, err)
require.Equal(t, true, check1)
check3, err := checkIamInPeerList(cml, c, peerName3)
require.NoError(t, err)
require.Equal(t, false, check3)
storedPeerList, err := cml.GetPeerList()
require.NoError(t, err)
storedPeerList.remove(peerName1)
require.Equal(t, 1, len(storedPeerList.Peers))
err = cml.UpdatePeerList(*storedPeerList)
require.NoError(t, err)
check1, err = checkIamInPeerList(cml, c, peerName1)
require.NoError(t, err)
require.Equal(t, false, check1)
}
// Run function _f_ _iterations_ times, in _concurrency_
// number of goroutines
func doConcurrentIterations(iterations, concurrency int, f func(int)) {
iterationsPerThread := iterations / concurrency
wg := sync.WaitGroup{}
for i := 0; i < concurrency; i++ {
wg.Add(1)
go func(j int) {
defer wg.Done()
for k := 0; k < iterationsPerThread; k++ {
f((j * iterationsPerThread) + k)
}
}(i)
}
wg.Wait()
}
func TestPeerListFuzz(t *testing.T) {
const (
testIters = 1000
maxNodes = 100
concurrency = 5
)
peerName := func(i int) string { return fmt.Sprintf("%02d:00:00:02:00:00", i) }
nodeName := func(i int) string { return fmt.Sprintf("node%d", i) }
stateLock := sync.Mutex{}
nodes := []int{}
nodeMap := map[int]bool{} // true if an operation is pending
c := NewSimpleClientset()
addNode := func() {
i := rand.Intn(maxNodes)
// add to test state first, then to Kubernetes
stateLock.Lock()
pending, found := nodeMap[i]
if !found {
nodes = append(nodes, i)
}
nodeMap[i] = true
stateLock.Unlock()
if pending { // someone else is adding or deleting this node
return
}
cml := newConfigMapAnnotations(configMapNamespace, configMapName, c)
err := cml.Init()
require.NoError(t, err)
_, err = addMyselfToPeerList(cml, c, peerName(i), nodeName(i))
require.NoError(t, err)
stateLock.Lock()
nodeMap[i] = false
stateLock.Unlock()
}
removeNode := func() {
stateLock.Lock()
if len(nodes) == 0 { // can't remove if there aren't any nodes
stateLock.Unlock()
return
}
pos := rand.Intn(len(nodes))
i := nodes[pos]
if pending, found := nodeMap[i]; found {
if pending {
stateLock.Unlock()
return // someone else is adding or deleting this node
}
nodeMap[i] = true
} else {
t.Errorf("missing entry from node map: %d", i)
}
stateLock.Unlock()
// remove from Kubernetes first, then from test state
cml := newConfigMapAnnotations(configMapNamespace, configMapName, c)
require.NoError(t, cml.Init())
storedPeerList, err := cml.GetPeerList()
require.NoError(t, err)
found := storedPeerList.contains(peerName(i))
require.True(t, found, "peer %d not found in stored list", i)
_, err = reclaimPeer(mockWeave{}, cml, peerName(i), fmt.Sprintf("deleter-%d", i))
require.NoError(t, err)
storedPeerList, err = cml.GetPeerList()
require.NoError(t, err)
stateLock.Lock()
delete(nodeMap, i)
for pos = 0; pos < len(nodes); pos++ {
if nodes[pos] == i {
// Remove item from list by swapping it with last and reducing slice length by 1
nodes[pos] = nodes[len(nodes)-1]
nodes = nodes[:len(nodes)-1]
break
}
}
stateLock.Unlock()
}
doConcurrentIterations(testIters, concurrency, func(iteration int) {
r := rand.Float32()
switch {
case 0.0 <= r && r < 0.4:
addNode()
case (0.4 <= r && r < 0.8):
removeNode()
}
})
cml := newConfigMapAnnotations(configMapNamespace, configMapName, c)
err := cml.Init()
require.NoError(t, err)
storedPeerList, err := cml.GetPeerList()
require.NoError(t, err)
require.Equal(t, len(storedPeerList.Peers), len(nodes))
}
type mockWeave struct{}
func (mockWeave) RmPeer(peerName string) (string, error) {
return "", nil
}
+12 -1
View File
@@ -1,3 +1,14 @@
FROM DOCKERHUB_USER/weaveARCH_EXT
ADD ./launch.sh ./kube-peers /home/weave/
# These labels are pretty static, and can therefore be added early on:
LABEL maintainer="Weaveworks <help@weave.works>" \
org.opencontainers.image.title="weave-kube" \
org.opencontainers.image.source="https://github.com/weaveworks/weave" \
org.opencontainers.image.vendor="Weaveworks"
ADD ./launch.sh ./kube-utils /home/weave/
ENTRYPOINT ["/home/weave/launch.sh"]
# This label will change for every build, and should therefore be the last layer of the image:
ARG revision
LABEL org.opencontainers.image.revision="${revision}"
+21 -6
View File
@@ -27,9 +27,9 @@ xt_set_exists() {
# Default if not supplied - same as weave net default
IPALLOC_RANGE=${IPALLOC_RANGE:-10.32.0.0/12}
HTTP_ADDR=${WEAVE_HTTP_ADDR:-127.0.0.1:6784}
STATUS_ADDR=${WEAVE_STATUS_ADDR:-0.0.0.0:6782}
METRICS_ADDR=${WEAVE_METRICS_ADDR:-0.0.0.0:6782}
HOST_ROOT=${HOST_ROOT:-/host}
CONN_LIMIT=${CONN_LIMIT:-30}
CONN_LIMIT=${CONN_LIMIT:-100}
DB_PREFIX=${DB_PREFIX:-/weavedb/weave-net}
# Check if the IP range overlaps anything existing on the host
@@ -54,12 +54,23 @@ else
fi
fi
STATUS_OPTS="--metrics-addr=$METRICS_ADDR"
# --status-addr exposes internal information, so only turn it on if asked to.
if [ -n "${WEAVE_STATUS_ADDR}" ]; then
STATUS_OPTS="$STATUS_OPTS --status-addr=$WEAVE_STATUS_ADDR"
fi
# Default is that npc will be running; allow caller to override
WEAVE_NPC_OPTS="--expect-npc"
if [ "${EXPECT_NPC}" = "0" ]; then
WEAVE_NPC_OPTS=""
fi
NO_MASQ_LOCAL_OPT=""
if [ -n "${NO_MASQ_LOCAL}" ]; then
NO_MASQ_LOCAL_OPT="--no-masq-local"
fi
# Kubernetes sets HOSTNAME to the host's hostname
# when running a pod in host namespace.
NICKNAME_ARG=""
@@ -74,7 +85,7 @@ router_bridge_opts() {
}
if [ -z "$KUBE_PEERS" ]; then
if ! KUBE_PEERS=$(/home/weave/kube-peers) || [ -z "$KUBE_PEERS" ]; then
if ! KUBE_PEERS=$(/home/weave/kube-utils) || [ -z "$KUBE_PEERS" ]; then
echo Failed to get peers >&2
exit 1
fi
@@ -99,7 +110,7 @@ fi
if [ -f ${DB_PREFIX}data.db ]; then
if /usr/bin/weaveutil get-db-flag "$DB_PREFIX" peer-reclaim >/dev/null ; then
# If this peer name is not stored in the list then we were removed by another peer.
if /home/weave/kube-peers -check-peer-new -peer-name="$PEERNAME" -log-level=debug ; then
if /home/weave/kube-utils -check-peer-new -peer-name="$PEERNAME" -log-level=debug ; then
# In order to avoid a CRDT clash, clean up
echo "Peer not in list; removing persisted data" >&2
rm -f ${DB_PREFIX}data.db
@@ -133,10 +144,13 @@ post_start_actions() {
/home/weave/weave --local setup-cni
# Attempt to run the reclaim process, but don't halt the script if it fails
/home/weave/kube-peers -reclaim -node-name="$HOSTNAME" -peer-name="$PEERNAME" -log-level=debug || true
/home/weave/kube-utils -reclaim -node-name="$HOSTNAME" -peer-name="$PEERNAME" -log-level=debug || true
# Expose the weave network so host processes can communicate with pods
/home/weave/weave --local expose $WEAVE_EXPOSE_IP
# Mark network as up
/home/weave/kube-utils -set-node-status -node-name="$HOSTNAME"
}
post_start_actions &
@@ -144,11 +158,12 @@ post_start_actions &
/home/weave/weaver $EXTRA_ARGS --port=6783 $(router_bridge_opts) \
--name="$PEERNAME" \
--host-root=$HOST_ROOT \
--http-addr=$HTTP_ADDR --status-addr=$STATUS_ADDR --docker-api='' --no-dns \
--http-addr=$HTTP_ADDR $STATUS_OPTS --docker-api='' --no-dns \
--db-prefix="$DB_PREFIX" \
--ipalloc-range=$IPALLOC_RANGE $NICKNAME_ARG \
--ipalloc-init $IPALLOC_INIT \
--conn-limit=$CONN_LIMIT \
$WEAVE_NPC_OPTS \
$NO_MASQ_LOCAL_OPT \
"$@" \
$KUBE_PEERS
+9 -9
View File
@@ -25,14 +25,6 @@ items:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- networkpolicies
verbs:
- get
- list
- watch
- apiGroups:
- 'networking.k8s.io'
resources:
@@ -41,6 +33,13 @@ items:
- get
- list
- watch
- apiGroups:
- ''
resources:
- nodes/status
verbs:
- patch
- update
- apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
@@ -101,6 +100,8 @@ items:
name: weave-net
namespace: kube-system
spec:
# Wait 5 seconds to let pod connect before rolling next pod
minReadySeconds: 5
template:
metadata:
labels:
@@ -154,7 +155,6 @@ items:
fieldPath: spec.nodeName
image: 'weaveworks/weave-npc:latest'
imagePullPolicy: Always
#npc-args
resources:
requests:
cpu: 10m
@@ -0,0 +1,209 @@
apiVersion: v1
kind: List
items:
- apiVersion: v1
kind: ServiceAccount
metadata:
name: weave-net
labels:
name: weave-net
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: weave-net
labels:
name: weave-net
rules:
- apiGroups:
- ''
resources:
- pods
- namespaces
- nodes
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- networkpolicies
verbs:
- get
- list
- watch
- apiGroups:
- 'networking.k8s.io'
resources:
- networkpolicies
verbs:
- get
- list
- watch
- apiGroups:
- ''
resources:
- nodes/status
verbs:
- patch
- update
- apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: weave-net
labels:
name: weave-net
roleRef:
kind: ClusterRole
name: weave-net
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: weave-net
namespace: kube-system
- apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: weave-net
namespace: kube-system
labels:
name: weave-net
rules:
- apiGroups:
- ''
resources:
- configmaps
resourceNames:
- weave-net
verbs:
- get
- update
- apiGroups:
- ''
resources:
- configmaps
verbs:
- create
- apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: weave-net
namespace: kube-system
labels:
name: weave-net
roleRef:
kind: Role
name: weave-net
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: weave-net
namespace: kube-system
- apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: weave-net
labels:
name: weave-net
namespace: kube-system
spec:
# Wait 5 seconds to let pod connect before rolling next pod
minReadySeconds: 5
template:
metadata:
labels:
name: weave-net
spec:
containers:
- name: weave
command:
- /home/weave/launch.sh
env:
- name: HOSTNAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: spec.nodeName
image: 'weaveworks/weave-kube:latest'
imagePullPolicy: Always
livenessProbe:
httpGet:
host: 127.0.0.1
path: /status
port: 6784
initialDelaySeconds: 30
resources:
requests:
cpu: 10m
securityContext:
privileged: true
volumeMounts:
- name: weavedb
mountPath: /weavedb
- name: cni-bin
mountPath: /host/opt
- name: cni-bin2
mountPath: /host/home
- name: cni-conf
mountPath: /host/etc
- name: dbus
mountPath: /host/var/lib/dbus
- name: lib-modules
mountPath: /lib/modules
- name: xtables-lock
mountPath: /run/xtables.lock
readOnly: false
- name: weave-npc
env:
- name: HOSTNAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: spec.nodeName
image: 'weaveworks/weave-npc:latest'
imagePullPolicy: Always
#npc-args
resources:
requests:
cpu: 10m
securityContext:
privileged: true
volumeMounts:
- name: xtables-lock
mountPath: /run/xtables.lock
readOnly: false
hostNetwork: true
hostPID: true
restartPolicy: Always
securityContext:
seLinuxOptions: {}
serviceAccountName: weave-net
tolerations:
- effect: NoSchedule
operator: Exists
volumes:
- name: weavedb
hostPath:
path: /var/lib/weave
- name: cni-bin
hostPath:
path: /opt
- name: cni-bin2
hostPath:
path: /home
- name: cni-conf
hostPath:
path: /etc
- name: dbus
hostPath:
path: /var/lib/dbus
- name: lib-modules
hostPath:
path: /lib/modules
- name: xtables-lock
hostPath:
path: /run/xtables.lock
type: FileOrCreate
updateStrategy:
type: RollingUpdate
+18 -6
View File
@@ -1,17 +1,24 @@
FROM ALPINE_BASEIMAGE
# If we're building for another architecture than amd64, the CROSS_BUILD_ placeholder is removed so e.g. CROSS_BUILD_COPY turns into COPY
# If we're building normally, for amd64, CROSS_BUILD lines are removed
CROSS_BUILD_COPY qemu-QEMUARCH-static /usr/bin/
LABEL maintainer "Weaveworks Inc <help@weave.works>"
LABEL works.weave.role=system \
# These labels are pretty static, and can therefore be added early on:
LABEL works.weave.role="system" \
maintainer="Weaveworks <help@weave.works>" \
org.opencontainers.image.title="Weave Net" \
org.opencontainers.image.description="Weave Net creates a virtual network that connects Docker containers across multiple hosts and enables their automatic discovery" \
org.opencontainers.image.url="https://weave.works" \
org.opencontainers.image.source="https://github.com/weaveworks/weave" \
org.opencontainers.image.vendor="Weaveworks" \
org.label-schema.schema-version="1.0" \
org.label-schema.name="Weave Net" \
org.label-schema.description="Weave Net creates a virtual network that connects Docker containers across multiple hosts and enables their automatic discovery" \
org.label-schema.url="https://weave.works" \
org.label-schema.vcs-url="https://github.com/weaveworks/weave" \
org.label-schema.vendor="Weaveworks"
# If we're building for another architecture than amd64, the CROSS_BUILD_ placeholder is removed so e.g. CROSS_BUILD_COPY turns into COPY
# If we're building normally, for amd64, CROSS_BUILD lines are removed
CROSS_BUILD_COPY qemu-QEMUARCH-static /usr/bin/
RUN apk add --update \
iptables \
ipset \
@@ -21,3 +28,8 @@ RUN apk add --update \
COPY ./weave-npc /usr/bin/weave-npc
COPY ./ulogd.conf /etc/ulogd.conf
ENTRYPOINT ["/usr/bin/weave-npc"]
# These labels will change for every build, and should therefore be the last layer of the image:
ARG revision
LABEL org.opencontainers.image.revision="${revision}" \
org.label-schema.vcs-ref="${revision}"
+107 -20
View File
@@ -8,7 +8,6 @@ import (
"github.com/coreos/go-iptables/iptables"
"github.com/spf13/cobra"
coreapi "k8s.io/api/core/v1"
extnapi "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
@@ -18,8 +17,9 @@ import (
"k8s.io/client-go/tools/cache"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/net"
"github.com/weaveworks/weave/net/ipset"
"github.com/weaveworks/weave/npc"
"github.com/weaveworks/weave/npc/ipset"
"github.com/weaveworks/weave/npc/metrics"
"github.com/weaveworks/weave/npc/ulogd"
)
@@ -30,7 +30,7 @@ var (
logLevel string
allowMcast bool
nodeName string
legacy bool
maxList int
)
func handleError(err error) { common.CheckFatal(err) }
@@ -52,11 +52,31 @@ func resetIPTables(ipt *iptables.IPTables) error {
return err
}
return ipt.ClearChain(npc.TableFilter, npc.MainChain)
if err := ipt.ClearChain(npc.TableFilter, npc.MainChain); err != nil {
return err
}
if err := ipt.ClearChain(npc.TableFilter, npc.EgressMarkChain); err != nil {
return err
}
if err := ipt.ClearChain(npc.TableFilter, npc.EgressCustomChain); err != nil {
return err
}
if err := ipt.ClearChain(npc.TableFilter, npc.EgressDefaultChain); err != nil {
return err
}
// We do not clear npc.EgressChain here because otherwise, in the case of restarting
// weave-npc process, all egress traffic is allowed for a short period of time.
// The chain is created in createBaseRules.
return nil
}
func resetIPSets(ips ipset.Interface) error {
// Remove ipsets prefixed `weave-` only
// Remove ipsets prefixed `weave-` only.
sets, err := ips.List(npc.IpsetNamePrefix)
if err != nil {
@@ -76,6 +96,11 @@ func resetIPSets(ips ipset.Interface) error {
}
for _, s := range sets {
// LocalIPset might be used by WEAVE-NPC-EGRESS chain which we do not
// flush, so we cannot destroy it.
if s == npc.LocalIpset {
continue
}
common.Log.Debugf("Destroying ipset '%s'", string(s))
if err := ips.Destroy(s); err != nil {
common.Log.Errorf("Failed to destroy ipset '%s'", string(s))
@@ -111,11 +136,81 @@ func createBaseRules(ipt *iptables.IPTables, ips ipset.Interface) error {
}
// If the destination address is not any of the local pods, let it through
if err := ips.Create(npc.LocalIpset, ipset.HashIP); err != nil {
found, err := ipsetExist(ips, npc.LocalIpset)
if err != nil {
return err
}
return ipt.Append(npc.TableFilter, npc.MainChain,
"-m", "set", "!", "--match-set", npc.LocalIpset, "dst", "-j", "ACCEPT")
if !found {
if err := ips.Create(npc.LocalIpset, ipset.HashIP); err != nil {
return err
}
}
if err := ipt.Append(npc.TableFilter, npc.MainChain,
"-m", "set", "!", "--match-set", npc.LocalIpset, "dst", "-j", "ACCEPT"); err != nil {
return err
}
if err := ipt.Append(npc.TableFilter, npc.EgressMarkChain,
"-j", "MARK", "--set-xmark", npc.EgressMark); err != nil {
return err
}
// Egress rules:
//
// -A WEAVE-NPC-EGRESS -m state --state RELATED,ESTABLISHED -j ACCEPT
// -A WEAVE-NPC-EGRESS -m set ! --match-set weave-local-pods src -j RETURN
// -A WEAVE-NPC-EGRESS -d 224.0.0.0/4 -j RETURN
// -A WEAVE-NPC-EGRESS -m state --state NEW -j WEAVE-NPC-EGRESS-DEFAULT
// -A WEAVE-NPC-EGRESS -m state --state NEW -m mark ! --mark 0x40000/0x40000 -j WEAVE-NPC-EGRESS-CUSTOM
// -A WEAVE-NPC-EGRESS -m state --state NEW -m mark ! --mark 0x40000/0x40000 -j NFLOG --nflog-group 86
// -A WEAVE-NPC-EGRESS -m mark ! --mark 0x40000/0x40000 -j DROP
//
// -A WEAVE-NPC-EGRESS-CUSTOM <rulespec> -j MARK --set-xmark 0x40000/0x40000
// -A WEAVE-NPC-EGRESS-CUSTOM <rulespec> -j RETURN
//
// -A WEAVE-NPC-EGRESS-DEFAULT <rulespec> -j MARK --set-xmark 0x40000/0x40000
// -A WEAVE-NPC-EGRESS-DEFAULT <rulespec> -j RETURN
//
// For each rule we create two (mark and return). We cannot just accept
// a packet if it matches any rule, as a packet might need to traverse
// the ingress npc as well which happens later in the chain (in some cases
// we cannot detect whether packet is ingress or egress, so we need to
// check both chains).
ruleSpecs := [][]string{
{"-m", "state", "--state", "RELATED,ESTABLISHED", "-j", "ACCEPT"},
{"-m", "state", "--state", "NEW", "-m", "set", "!", "--match-set", npc.LocalIpset, "src", "-j", "RETURN"},
}
if allowMcast {
ruleSpecs = append(ruleSpecs, []string{"-d", "224.0.0.0/4", "-j", "RETURN"})
}
ruleSpecs = append(ruleSpecs, [][]string{
{"-m", "state", "--state", "NEW", "-j", string(npc.EgressDefaultChain)},
{"-m", "state", "--state", "NEW", "-m", "mark", "!", "--mark", npc.EgressMark, "-j", string(npc.EgressCustomChain)},
{"-m", "state", "--state", "NEW", "-m", "mark", "!", "--mark", npc.EgressMark, "-j", "NFLOG", "--nflog-group", "86"},
{"-m", "mark", "!", "--mark", npc.EgressMark, "-j", "DROP"},
}...)
if err := net.AddChainWithRules(ipt, npc.TableFilter, npc.EgressChain, ruleSpecs); err != nil {
return err
}
return nil
}
// Dummy way to check whether a given ipset exists.
// TODO(brb) Use "ipset -exist create <..>" for our purpose instead (for some reasons
// creating an ipset with -exist fails).
func ipsetExist(ips ipset.Interface, name ipset.Name) (bool, error) {
sets, err := ips.List(string(name))
if err != nil {
return false, err
}
for _, s := range sets {
if s == name {
return true, nil
}
}
return false, nil
}
func root(cmd *cobra.Command, args []string) {
@@ -131,10 +226,6 @@ func root(cmd *cobra.Command, args []string) {
}
common.Log.Infof("Starting Weaveworks NPC %s; node name %q", version, nodeName)
if legacy {
common.Log.Info("Running in legacy mode (k8s pre-1.7 network policy semantics)")
}
if err := metrics.Start(metricsAddr); err != nil {
common.Log.Fatalf("Failed to start metrics: %v", err)
}
@@ -152,13 +243,13 @@ func root(cmd *cobra.Command, args []string) {
ipt, err := iptables.New()
handleError(err)
ips := ipset.New(common.LogLogger())
ips := ipset.New(common.LogLogger(), maxList)
handleError(resetIPTables(ipt))
handleError(resetIPSets(ips))
handleError(createBaseRules(ipt, ips))
npc := npc.New(nodeName, legacy, ipt, ips)
npc := npc.New(nodeName, ipt, ips)
nsController := makeController(client.Core().RESTClient(), "namespaces", &coreapi.Namespace{},
cache.ResourceEventHandlerFuncs{
@@ -219,11 +310,7 @@ func root(cmd *cobra.Command, args []string) {
handleError(npc.UpdateNetworkPolicy(old, new))
},
}
if legacy {
npController = makeController(client.Extensions().RESTClient(), "networkpolicies", &extnapi.NetworkPolicy{}, npHandlers)
} else {
npController = makeController(client.NetworkingV1().RESTClient(), "networkpolicies", &networkingv1.NetworkPolicy{}, npHandlers)
}
npController = makeController(client.NetworkingV1().RESTClient(), "networkpolicies", &networkingv1.NetworkPolicy{}, npHandlers)
go nsController.Run(wait.NeverStop)
go podController.Run(wait.NeverStop)
@@ -244,7 +331,7 @@ func main() {
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "debug", "logging level (debug, info, warning, error)")
rootCmd.PersistentFlags().BoolVar(&allowMcast, "allow-mcast", true, "allow all multicast traffic")
rootCmd.PersistentFlags().StringVar(&nodeName, "node-name", "", "only generate rules that apply to this node")
rootCmd.PersistentFlags().BoolVar(&legacy, "use-legacy-netpol", false, "use legacy network policies (pre k8s 1.7 vsn)")
rootCmd.PersistentFlags().IntVar(&maxList, "max-list-size", 1024, "maximum size of ipset list (for namespaces)")
handleError(rootCmd.Execute())
}
+12 -2
View File
@@ -1,7 +1,17 @@
# This is a nearly-empty image that we use to create a data-only container for persistence
FROM scratch
LABEL maintainer "Weaveworks Inc <help@weave.works>"
LABEL works.weave.role=system
# These labels are pretty static, and can therefore be added early on:
LABEL works.weave.role="system" \
maintainer="Weaveworks <help@weave.works>" \
org.opencontainers.image.title="weavedb" \
org.opencontainers.image.source="https://github.com/weaveworks/weave" \
org.opencontainers.image.vendor="Weaveworks"
ENTRYPOINT ["data-only"]
# Work round Docker refusing to save an empty image
COPY Dockerfile /
# This label will change for every build, and should therefore be the last layer of the image:
ARG revision
LABEL org.opencontainers.image.revision="${revision}"
+10
View File
@@ -1,5 +1,11 @@
FROM DOCKERHUB_USER/weaveARCH_EXT
# These labels are pretty static, and can therefore be added early on:
LABEL maintainer="Weaveworks <help@weave.works>" \
org.opencontainers.image.title="weaveexec" \
org.opencontainers.image.source="https://github.com/weaveworks/weave" \
org.opencontainers.image.vendor="Weaveworks"
ENTRYPOINT ["/home/weave/sigproxy", "/home/weave/weave"]
ADD ./sigproxy ./symlink /home/weave/
@@ -7,3 +13,7 @@ ADD ./weavewait /w/w
ADD ./weavewait_noop /w-noop/w
ADD ./weavewait_nomcast /w-nomcast/w
WORKDIR /home/weave
# This label will change for every build, and should therefore be the last layer of the image:
ARG revision
LABEL org.opencontainers.image.revision="${revision}"
+19 -7
View File
@@ -1,17 +1,24 @@
FROM ALPINE_BASEIMAGE
# If we're building for another architecture than amd64, the CROSS_BUILD_ placeholder is removed so e.g. CROSS_BUILD_COPY turns into COPY
# If we're building normally, for amd64, CROSS_BUILD lines are removed
CROSS_BUILD_COPY qemu-QEMUARCH-static /usr/bin/
LABEL maintainer "Weaveworks Inc <help@weave.works>"
LABEL works.weave.role=system \
# These labels are pretty static, and can therefore be added early on:
LABEL works.weave.role="system" \
maintainer="Weaveworks <help@weave.works>" \
org.opencontainers.image.title="Weave Net" \
org.opencontainers.image.description="Weave Net creates a virtual network that connects Docker containers across multiple hosts and enables their automatic discovery" \
org.opencontainers.image.url="https://weave.works" \
org.opencontainers.image.source="https://github.com/weaveworks/weave" \
org.opencontainers.image.vendor="Weaveworks" \
org.label-schema.schema-version="1.0" \
org.label-schema.name="Weave Net" \
org.label-schema.description="Weave Net creates a virtual network that connects Docker containers across multiple hosts and enables their automatic discovery" \
org.label-schema.url="https://weave.works" \
org.label-schema.vcs-url="https://github.com/weaveworks/weave" \
org.label-schema.vendor="Weaveworks"
# If we're building for another architecture than amd64, the CROSS_BUILD_ placeholder is removed so e.g. CROSS_BUILD_COPY turns into COPY
# If we're building normally, for amd64, CROSS_BUILD lines are removed
CROSS_BUILD_COPY qemu-QEMUARCH-static /usr/bin/
RUN apk add --update \
curl \
ethtool \
@@ -21,11 +28,16 @@ RUN apk add --update \
util-linux \
conntrack-tools \
bind-tools \
ca-certificates \
&& rm -rf /var/cache/apk/*
ADD ./weave ./weaver /home/weave/
ADD ./weaveutil /usr/bin/
ADD weavedata.db /weavedb/
COPY ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/home/weave/weaver"]
WORKDIR /home/weave
# These labels will change for every build, and should therefore be the last layer of the image:
ARG revision
LABEL org.opencontainers.image.revision="${revision}" \
org.label-schema.vcs-ref="${revision}"
File diff suppressed because it is too large. Load diff
+7 -2
View File
@@ -329,7 +329,7 @@ func HandleHTTP(muxRouter *mux.Router, version string, router *weave.NetworkRout
w.Write(json)
})
muxRouter.Methods("GET").Path("/report").Queries("format", "{format}").HandlerFunc(
muxRouter.Methods("GET").Path("/report").HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
funcs := template.FuncMap{
"json": func(v interface{}) string {
@@ -337,7 +337,12 @@ func HandleHTTP(muxRouter *mux.Router, version string, router *weave.NetworkRout
return string(a)
},
}
formatTemplate, err := template.New("format").Funcs(funcs).Parse(mux.Vars(r)["format"])
format, ok := r.URL.Query()["format"]
if !ok || len(format) != 1 {
http.Error(w, "error: must supply one format parameter", http.StatusBadRequest)
return
}
formatTemplate, err := template.New("format").Funcs(funcs).Parse(format[0])
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
+24 -7
View File
@@ -26,6 +26,7 @@ import (
"github.com/weaveworks/weave/nameserver"
weavenet "github.com/weaveworks/weave/net"
"github.com/weaveworks/weave/net/address"
"github.com/weaveworks/weave/net/ipset"
"github.com/weaveworks/weave/plugin"
weaveproxy "github.com/weaveworks/weave/proxy"
weave "github.com/weaveworks/weave/router"
@@ -155,6 +156,7 @@ func main() {
noDiscovery bool
httpAddr string
statusAddr string
metricsAddr string
ipamConfig ipamConfig
dockerAPI string
peers []string
@@ -186,12 +188,13 @@ func main() {
mflag.StringVar(&logLevel, []string{"-log-level"}, "info", "logging level (debug, info, warning, error)")
mflag.BoolVar(&pktdebug, []string{"-pkt-debug"}, false, "enable per-packet debug logging")
mflag.StringVar(&prof, []string{"-profile"}, "", "enable profiling and write profiles to given path")
mflag.IntVar(&config.ConnLimit, []string{"-conn-limit"}, 30, "connection limit (0 for unlimited)")
mflag.IntVar(&config.ConnLimit, []string{"-conn-limit"}, 100, "connection limit (0 for unlimited)")
mflag.BoolVar(&noDiscovery, []string{"-no-discovery"}, false, "disable peer discovery")
mflag.IntVar(&bufSzMB, []string{"-bufsz"}, 8, "capture buffer size in MB")
mflag.IntVar(&bridgeConfig.MTU, []string{"-mtu"}, 0, "MTU size")
mflag.StringVar(&httpAddr, []string{"-http-addr"}, "", "address to bind HTTP interface to (disabled if blank, absolute path indicates unix domain socket)")
mflag.StringVar(&statusAddr, []string{"-status-addr"}, "", "address to bind status+metrics interface to (disabled if blank, absolute path indicates unix domain socket)")
mflag.StringVar(&metricsAddr, []string{"-metrics-addr"}, "", "address to bind metrics interface to (disabled if blank, absolute path indicates unix domain socket)")
mflag.StringVar(&ipamConfig.Mode, []string{"-ipalloc-init"}, "", "allocator initialisation strategy (consensus, seed or observer)")
mflag.StringVar(&ipamConfig.IPRangeCIDR, []string{"-ipalloc-range"}, "", "IP address range reserved for automatic allocation, in CIDR notation")
mflag.StringVar(&ipamConfig.IPSubnetCIDR, []string{"-ipalloc-default-subnet"}, "", "subnet to allocate within by default, in CIDR notation")
@@ -219,6 +222,7 @@ func main() {
mflag.BoolVar(&pluginConfig.EnableV2Multicast, []string{"-plugin-v2-multicast"}, false, "enable multicast for Docker plugin (v2)")
mflag.StringVar(&pluginConfig.Socket, []string{"-plugin-socket"}, "/run/docker/plugins/weave.sock", "plugin socket on which to listen")
mflag.StringVar(&pluginConfig.MeshSocket, []string{"-plugin-mesh-socket"}, "/run/docker/plugins/weavemesh.sock", "plugin socket on which to listen in mesh mode")
mflag.BoolVar(&bridgeConfig.NoMasqLocal, []string{"-no-masq-local"}, false, "do not SNAT external traffic sent to containers running on this node")
proxyConfig := newProxyConfig()
@@ -303,7 +307,8 @@ func main() {
bridgeConfig.Mac = name.String()
bridgeConfig.Port = config.Port
bridgeType, err := weavenet.EnsureBridge(procPath, &bridgeConfig, Log)
ips := ipset.New(common.LogLogger(), 0)
bridgeType, err := weavenet.EnsureBridge(procPath, &bridgeConfig, Log, ips)
checkFatal(err)
Log.Println("Bridge type is", bridgeType)
@@ -334,6 +339,9 @@ func main() {
if bridgeConfig.AWSVPC && !ipamConfig.Enabled() {
Log.Fatalf("--awsvpc mode requires IPAM enabled")
}
if bridgeConfig.AWSVPC && bridgeConfig.NoMasqLocal {
Log.Fatalf("--awsvpc mode is not compatible with the --no-masq-local option")
}
db, err := db.NewBoltDB(dbPrefix)
checkFatal(err)
@@ -393,17 +401,19 @@ func main() {
var (
allocator *ipam.Allocator
defaultSubnet address.CIDR
trackerName string
)
if ipamConfig.Enabled() {
var t tracker.LocalRangeTracker
if bridgeConfig.AWSVPC {
Log.Infoln("Creating AWSVPC LocalRangeTracker")
t, err = tracker.NewAWSVPCTracker(bridgeConfig.WeaveBridgeName)
if err != nil {
Log.Fatalf("Cannot create AWSVPC LocalRangeTracker: %s", err)
}
trackerName = "awsvpc"
} else if bridgeConfig.NoMasqLocal {
t = weavenet.NewNoMasqLocalTracker(ips)
}
if t != nil {
Log.Infof("Using %q LocalRangeTracker", t)
}
preClaims, err := findExistingAddresses(dockerCli, bridgeConfig.WeaveBridgeName)
@@ -451,7 +461,7 @@ func main() {
if httpAddr != "" {
muxRouter := mux.NewRouter()
if allocator != nil {
allocator.HandleHTTP(muxRouter, defaultSubnet, trackerName, dockerCli)
allocator.HandleHTTP(muxRouter, defaultSubnet, dockerCli)
}
if ns != nil {
ns.HandleHTTP(muxRouter, dockerCli)
@@ -474,10 +484,17 @@ func main() {
muxRouter.Methods("GET").Path("/metrics").Handler(metricsHandler(router, allocator, ns, dnsserver))
statusMux := http.NewServeMux()
statusMux.Handle("/", muxRouter)
Log.Println("Listening for metrics requests on", statusAddr)
Log.Println("Listening for status+metrics requests on", statusAddr)
go listenAndServeHTTP(statusAddr, statusMux)
}
if metricsAddr != "" {
metricsMux := http.NewServeMux()
metricsMux.Handle("/metrics", metricsHandler(router, allocator, ns, dnsserver))
Log.Println("Listening for metrics requests on", metricsAddr)
go listenAndServeHTTP(metricsAddr, metricsMux)
}
if plugin != nil {
go plugin.Start(httpAddr, dockerCli, waitReady.Add())
}
+24 -5
View File
@@ -89,8 +89,9 @@ func containerFQDN(args []string) error {
return nil
}
func runContainer(args []string) error {
func parseContainerArgs(args []string) docker.CreateContainerOptions {
env := []string{}
labels := map[string]string{}
name := ""
net := ""
pid := ""
@@ -109,6 +110,10 @@ func runContainer(args []string) error {
env = append(env, args[i+1])
}
args = append(args[:i], args[i+2:]...)
case "-l", "--label":
key, value := parseLabel(args[i+1])
labels[key] = value
args = append(args[:i], args[i+2:]...)
case "--name":
name = args[i+1]
args = append(args[:i], args[i+2:]...)
@@ -161,19 +166,25 @@ func runContainer(args []string) error {
image := args[0]
cmds := args[1:]
config := docker.Config{Image: image, Env: env, Cmd: cmds, Labels: labels}
hostConfig := docker.HostConfig{NetworkMode: net, PidMode: pid, Privileged: privileged, RestartPolicy: restart, Binds: volumes, VolumesFrom: volumesFrom}
return docker.CreateContainerOptions{Name: name, Config: &config, HostConfig: &hostConfig}
}
func runContainer(args []string) error {
containerOptions := parseContainerArgs(args)
c, err := docker.NewVersionedClientFromEnv("1.18")
if err != nil {
return fmt.Errorf("unable to connect to docker: %s", err)
}
config := docker.Config{Image: image, Env: env, Cmd: cmds}
hostConfig := docker.HostConfig{NetworkMode: net, PidMode: pid, Privileged: privileged, RestartPolicy: restart, Binds: volumes, VolumesFrom: volumesFrom}
container, err := c.CreateContainer(docker.CreateContainerOptions{Name: name, Config: &config, HostConfig: &hostConfig})
container, err := c.CreateContainer(containerOptions)
if err != nil {
return fmt.Errorf("unable to create container: %s", err)
}
err = c.StartContainer(container.ID, &hostConfig)
err = c.StartContainer(container.ID, containerOptions.HostConfig)
if err != nil {
return fmt.Errorf("unable to start container: %s", err)
}
@@ -182,6 +193,14 @@ func runContainer(args []string) error {
return nil
}
func parseLabel(s string) (key, value string) {
pos := strings.Index(s, "=")
if pos == -1 { // no value - set it to blank
return s, ""
}
return s[:pos], s[pos+1:]
}
func listContainers(args []string) error {
if len(args) > 1 {
cmdUsage("list-containers", "[<label>]")
+36
View File
@@ -0,0 +1,36 @@
package main
import (
"testing"
docker "github.com/fsouza/go-dockerclient"
"github.com/stretchr/testify/require"
)
func TestParseContainerArgs(t *testing.T) {
args := []string{"--name", "weave", "--privileged", "--net", "host",
"-l", "foo=bar", "-l", "foobar",
"-v", "/var/run/docker.sock:/var/run/docker.sock", "-v", "/etc:/host/etc",
"--restart", "always", "--pid", "host", "--volumes-from", "weavedb",
"-e", "WEAVE_DEBUG", "-e", "EXEC_IMAGE=weaveworks/weaveexec:latest",
"weaveworks/weave:latest", "cmd", "arg1", "arg2"}
expected := docker.CreateContainerOptions{
Name: "weave",
Config: &docker.Config{
Image: "weaveworks/weave:latest",
Env: []string{"WEAVE_DEBUG", "EXEC_IMAGE=weaveworks/weaveexec:latest"},
Cmd: []string{"cmd", "arg1", "arg2"},
Labels: map[string]string{"foo": "bar", "foobar": ""},
},
HostConfig: &docker.HostConfig{
NetworkMode: "host",
PidMode: "host",
Privileged: true,
RestartPolicy: docker.RestartPolicy{Name: "always"},
Binds: []string{"/var/run/docker.sock:/var/run/docker.sock", "/etc:/host/etc"},
VolumesFrom: []string{"weavedb"},
},
}
have := parseContainerArgs(args)
require.Equal(t, expected, have)
}
-17
View File
@@ -4,10 +4,7 @@ package main
import (
"fmt"
"os"
"path/filepath"
"strings"
weavenet "github.com/weaveworks/weave/net"
)
var commands map[string]func([]string) error
@@ -36,12 +33,6 @@ func init() {
"create-volume-container": createVolumeContainer,
"attach-container": attach,
"detach-container": detach,
"configure-arp": configureARP,
"check-iface": checkIface,
"del-iface": delIface,
"setup-iface": setupIface,
"setup-iface-addrs": setupIfaceAddrs,
"list-netdevs": listNetDevs,
"cni-net": cniNet,
"cni-ipam": cniIPAM,
"bridge-ip": bridgeIP,
@@ -55,14 +46,6 @@ func init() {
}
func main() {
// force re-exec of this binary
selfPath, err := filepath.EvalSymlinks("/proc/self/exe")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
weavenet.WeaveUtilCmd = selfPath
// If no args passed, act as CNI plugin based on executable name
switch {
case len(os.Args) == 1 && strings.HasSuffix(os.Args[0], "weave-ipam"):
-141
View File
@@ -1,141 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/j-keck/arping"
"github.com/vishvananda/netlink"
weavenet "github.com/weaveworks/weave/net"
)
// checkIface returns an error if the given interface cannot be found.
func checkIface(args []string) error {
if len(args) != 1 {
cmdUsage("check-iface", "<iface-name>")
}
ifaceName := args[0]
if _, err := netlink.LinkByName(ifaceName); err != nil {
return err
}
return nil
}
func delIface(args []string) error {
if len(args) != 1 {
cmdUsage("del-iface", "<iface-name>")
}
ifName := args[0]
link, err := netlink.LinkByName(ifName)
if err != nil {
return err
}
return netlink.LinkDel(link)
}
// setupIface renames the given iface and configures its ARP cache settings.
func setupIface(args []string) error {
if len(args) != 2 {
cmdUsage("setup-iface", "<iface-name> <new-iface-name>")
}
ifaceName := args[0]
newIfName := args[1]
return weavenet.SetupIface(ifaceName, newIfName)
}
// setupIfaceAddrs sets up addresses on an interface. It expects to be called inside the container's netns.
func setupIfaceAddrs(args []string) error {
if len(args) < 1 {
cmdUsage("setup-iface-addrs", "<iface-name> <with-multicast> <cidr>...")
}
link, err := netlink.LinkByName(args[0])
if err != nil {
return err
}
withMulticastRoute, err := strconv.ParseBool(args[1])
if err != nil {
return err
}
cidrs, err := parseCIDRs(args[2:])
if err != nil {
return err
}
return weavenet.SetupIfaceAddrs(link, withMulticastRoute, cidrs)
}
func configureARP(args []string) error {
if len(args) != 2 {
cmdUsage("configure-arp", "<iface-name-prefix> <root-path>")
}
prefix := args[0]
rootPath := args[1]
links, err := netlink.LinkList()
if err != nil {
return err
}
for _, link := range links {
ifName := link.Attrs().Name
if strings.HasPrefix(ifName, prefix) {
weavenet.ConfigureARPCache(rootPath+"/proc", ifName)
if addrs, err := netlink.AddrList(link, netlink.FAMILY_V4); err == nil {
for _, addr := range addrs {
arping.GratuitousArpOverIfaceByName(addr.IPNet.IP, ifName)
}
}
}
}
return nil
}
// listNetDevs outputs network ifaces identified by the given indexes
// in the format of weavenet.Dev.
func listNetDevs(args []string) error {
if len(args) == 0 {
cmdUsage("list-netdevs", "<iface-index>[ <iface-index>]")
}
indexes := make(map[int]struct{})
for _, index := range args {
if index != "" {
id, err := strconv.Atoi(index)
if err != nil {
return err
}
indexes[id] = struct{}{}
}
}
links, err := netlink.LinkList()
if err != nil {
return err
}
var netdevs []weavenet.Dev
for _, link := range links {
if _, found := indexes[link.Attrs().Index]; found {
netdev, err := weavenet.LinkToNetDev(link)
if err != nil {
return err
}
netdevs = append(netdevs, netdev)
}
}
nds, err := json.Marshal(netdevs)
if err != nil {
return err
}
fmt.Println(string(nds))
return nil
}
+2 -1
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"strings"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
docker "github.com/fsouza/go-dockerclient"
)
@@ -58,7 +59,7 @@ func isDockerPluginEnabled(args []string) error {
ctx := context.Background()
plugins, err := c.PluginList(ctx)
plugins, err := c.PluginList(ctx, filters.Args{})
if err != nil {
return err
}
+17 -15
View File
@@ -6,27 +6,28 @@ search_type: Documentation
You only need to build Weave Net if you want to work on the Weave Net codebase
(or you just enjoy building software).
Apart from the `weave` shell script, weave is delivered as a set of
Apart from the `weave` shell script, Weave Net is delivered as a set of
container images. There is no distribution-specific packaging, so in
principle it shouldn't matter which Linux distribution you build
under. But naturally, Docker is a prerequisite (version 1.6.0 or
later).
The only way to build is by using the the build container; the
Makefile is setup to make this transparent. This method is
`Makefile` is setup to make this transparent. This method is
documented below, and can be run directly on your machine or using
a Vagrant VM.
## <a name="ubuntu"></a>Building directly on your machine
The weave git repository should be cloned into
`$GOPATH/src/github.com/weaveworks/weave`, in accordance with [the go
`$GOPATH/src/github.com/weaveworks/weave`, in accordance with [the Go
workspace conventions](https://golang.org/doc/code.html#Workspaces):
```
$ WEAVE=github.com/weaveworks/weave
$ git clone https://$WEAVE $GOPATH/src/$WEAVE
$ cd $GOPATH/src/$WEAVE
$ git submodule update --init
```
Next install Docker if you haven't already, by following the instructions
@@ -44,7 +45,7 @@ On a fresh repository, the Makefile will do the following:
- build the weave components in the build container
- package them into two Docker images (`weaveworks/weave`,
`weaveworks/weaveexec`)
- Exported these images as `weave.tar.gz`
- export these images as `weave.tar.gz`.
The first two steps may take a while - don't worry, they are
are cached and should not need to be redone very often.
@@ -63,18 +64,19 @@ First, check out the code:
```
$ git clone https://github.com/weaveworks/weave
$ cd weave
$ git submodule update --init
```
The `Vagrantfile` in the top directory constructs a VM that has
The `Vagrantfile` in the top directory constructs a VM that has:
* docker installed
* go tools installed
* weave dependencies installed
* $GOPATH set to ~
* Docker installed
* Go tools installed
* Weave's dependencies installed
* `$GOPATH` set to `~`
* the local working directory mapped as a synced folder into the
right place in $GOPATH
  right place in `$GOPATH`.
Once you are in the working directory you can issue
Once you are in the working directory you can issue:
```
$ vagrant up
@@ -86,7 +88,7 @@ installation is done just once). The working directory is sync'ed with
use git and so on in the regular filesystem.
To build and run the code, you need to use the VM. To log in and build
the weave image, do
the weave image, do:
```
$ vagrant ssh
@@ -94,7 +96,7 @@ vm$ cd src/github.com/weaveworks/weave
vm$ make
```
The Docker daemon is also running in this VM, so you can then do
The Docker daemon is also running in this VM, so you can then do:
```
vm$ sudo ./weave launch
@@ -104,7 +106,7 @@ vm$ sudo docker ps
and so on.
If you are looking to just do a build and not run anything on this VM,
you can do so with
you can do so with:
```
$ vagrant ssh -c 'make -C src/github.com/weaveworks/weave'
@@ -112,7 +114,7 @@ $ vagrant ssh -c 'make -C src/github.com/weaveworks/weave'
you should then find a `weave.tar.gz` container snapshot tarball in the
top-level directory. You can use that snapshot with `docker load`
against a different host, e.g.
against a different host, e.g.:
```
$ export DOCKER_HOST=tcp://<HOST:PORT>
+14 -1
View File
@@ -40,6 +40,18 @@ A simple way to accomplish this would be to run Weave on the host and then run,
Yet another option is to expose a port from the container on host B and then connect to it. You can read about exposing ports in [Exporting Services](/site/tasks/manage/service-management.md#exporting).
<a name="duplicate-peer"></a>
**Q: Why am I seeing "peer names collision" and failed connections?**
This sometimes happens when machines are cloned; we require each
machine in your cluster to have a unique identity.
For more information see [Peer Names](/site/operational-guide/concepts.md#peer-name).
Depending on your Linux distribution you may need to [set up
machine-id](https://www.freedesktop.org/software/systemd/man/machine-id.html)
or [generate a dbus id](https://dbus.freedesktop.org/doc/dbus-uuidgen.1.html).
<a name="duplicate-ip"></a>
**Q: Why am I seeing the same IP address assigned to two different containers on different hosts?**
@@ -92,7 +104,8 @@ Yes, of course! Weave allows you to run isolated networks and still allow open
You must permit traffic to flow through TCP 6783 and UDP 6783/6784,
which are Weave’s control and data ports.
The daemon also uses TCP port 6782 for [metrics](/site/tasks/manage/metrics.md), but
The daemon also uses TCP 6781/6782 for
[metrics](/site/tasks/manage/metrics.md#metrics-endpoint-addresses), but
you would only need to open up this port if you wish to collect metrics
from another host.
-1
View File
@@ -31,7 +31,6 @@ Install Weave Net from the command line on its own or if you are using Docker, K
* [Integrating Kubernetes and Mesos via the CNI Plugin](https://www.weave.works/docs/net/latest/kubernetes/)
* [Integrating Kubernetes via the Addon](https://www.weave.works/docs/net/latest/kubernetes/kube-addon/)
* [Installation](https://www.weave.works/docs/net/latest/kubernetes/kube-addon/#install)
* [Upgrading Kubernetes to version 1.6](https://www.weave.works/docs/net/latest/kubernetes/kube-addon/#kube-1.6-upgrade)
* [Upgrading the Daemon Sets](https://www.weave.works/docs/net/latest/kubernetes/kube-addon/#daemon-sets)
* [CPU and Memory Requirements](https://www.weave.works/docs/net/latest/kubernetes/kube-addon/#resources)
* [Pod Eviction](https://www.weave.works/docs/net/latest/kubernetes/kube-addon/#eviction)
+2 -4
View File
@@ -48,10 +48,8 @@ To disable this check, run the following before launching Weave Net:
### Guides for Specific Platforms
CoreOS users see [here](/guides/networking-docker-containers-with-weave-on-coreos/) for an example of installing Weave using cloud-config.
Amazon ECS users see [here](https://github.com/weaveworks/integrations/blob/master/aws/ecs/README.md)
for the latest Weave AMIs and [here](https://www.weave.works/docs/tutorials/old-guides/ecs/) to get started with Weave Net on ECS.
Amazon ECS users see [here](https://www.weave.works/docs/scope/latest/ami/)
for the latest Weave AMIs.
If you're on Amazon EC2, the standard installation instructions at the
top of this page, provide the simplest setup and the most flexibility.
+80 -41
View File
@@ -7,21 +7,25 @@ search_type: Documentation
The following topics are discussed:
* [Installation](#install)
* [Upgrading Kubernetes to version 1.6](#kube-1.6-upgrade)
* [Upgrading the Daemon Sets](#daemon-sets)
* [CPU and Memory Requirements](#resources)
* [Pod Eviction](#eviction)
* [Upgrading the Daemon Sets](#daemon-sets)
* [CPU and Memory Requirements](#resources)
* [Pod Eviction](#eviction)
* [Features](#features)
* [Pod Network](#pod-network)
* [Network Policy](#npc)
* [Pod Network](#pod-network)
* [Network Policy](#npc)
* [Troubleshooting](#troubleshooting)
* [Troubleshooting Blocked Connections](#blocked-connections)
* [Things to watch out for](#key-points)
* [Reading the logs](#logs)
* [Troubleshooting Blocked Connections](#blocked-connections)
* [Things to watch out for](#key-points)
* [Changing Configuration Options](#configuration-options)
## <a name="install"></a> Installation
*Before installing Weave Net, you should make sure the following ports are not
blocked by your firewall: TCP 6783 and UDP 6783/6784.
For more details, see the [FAQ](/site/faq.md#ports).*
Weave Net can be installed onto your CNI-enabled Kubernetes cluster
with a single command:
@@ -59,38 +63,13 @@ Shut down Kubernetes, and _on all nodes_ perform the following:
Then relaunch Kubernetes and install the addon as described
above.
## <a name="kube-1.6-upgrade"></a> Upgrading Kubernetes to version 1.6
In version 1.6, Kubernetes has increased security, so we need to
create a special service account to run Weave Net. This is done in
the file `weave-daemonset-k8s-1.6.yaml` attached to the [Weave Net
release](https://github.com/weaveworks/weave/releases/latest).
Also, the
[toleration](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/taint-toleration-dedicated.md)
required to let Weave Net run on master nodes has moved from an
annotation to a field on the DaemonSet spec object.
If you have edited the Weave Net DaemonSet from a previous release,
you will need to re-make your changes against the new version.
### <a name="daemon-sets"></a> Upgrading the Daemon Sets
For Kubernetes 1.6 and above the DaemonSet definition specifies
[Rolling Updates](https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/),
The DaemonSet definition specifies [Rolling
Updates](https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/),
so when you apply a new version Kubernetes will automatically restart
the Weave Net pods one by one.
Kubernetes v1.5 and below does not support rolling upgrades of daemon sets,
and so you will need to perform the procedure manually:
* Apply the updated addon manifest `kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"`
* Kill each Weave Net pod with `kubectl delete` and then wait for it to reboot before moving on to the next pod.
**Note:** In versions prior to Weave Net 2.0, deleting all Weave Net pods at the same time
will result in them losing track of IP address range ownership, possibly leading to
duplicate IP addresses if you then start a new copy of Weave Net.
## <a name="resources"></a>CPU and Memory Requirements
Kubernetes manages
@@ -186,12 +165,58 @@ definition](https://v1-7.docs.kubernetes.io/docs/api-reference/v1.7/#networkpoli
## <a name="troubleshooting"></a> Troubleshooting
The first thing to check is whether Weave Net is up and
running. The `kubectl apply` command you used to install it only
_requests_ that it be downloaded and started; if anything goes wrong
at startup, those details will only be visible in the [logs](#logs) of
the container(s).
To check what is running:
```
$ kubectl get pods -n kube-system -l name=weave-net
NAME READY STATUS RESTARTS AGE
weave-net-1jkl6 2/2 Running 0 1d
weave-net-bskbv 2/2 Running 0 1d
weave-net-m4x1b 2/2 Running 0 1d
```
You should see one line for each node in your cluster; each line
should have STATUS "Running", and READY should be 2 out of 2. If you
see a STATUS like "Error" or "CrashLoopBackoff", look in the logs of
the container with that status.
### <a name="logs"></a> Reading the logs
Pick one of the pods from the list output by `kubectl get pods` and
ask for the logs like this:
```
$ kubectl logs -n kube-system weave-net-1jkl6 weave
```
For easier viewing, pipe the output into a file , especially if
it is long.
By default log level of `weave` container is set to `info` level. If you wish to see more detailed logs you can set the desired log level for the `-log-level` flag through the `EXTRA_ARGS` environment variable for the `weave` container in the weave-net daemon set. Add environment variable as below.
```yaml
containers:
- command:
- /home/weave/launch.sh
name: weave
env:
- name: EXTRA_ARGS
value: --log-level=debug
```
Many Kubernetes network issues occur at a higher level than Weave Net.
The [Kubernetes Service Debugging Guide]
(https://kubernetes.io/docs/tasks/debug-application-cluster/debug-service/)
has a detailed step-by-step guide.
The status of Weave Net can be checked by running its CLI commands. This can be done in various ways:
Once it is up and running, the status of Weave Net can be checked by
running its CLI commands. This can be done in various ways:
1\. [Install the `weave` script](/site/installing-weave.md) and run:
@@ -302,7 +327,15 @@ UDP connection from 10.32.0.7:56648 to 10.32.0.11:80 blocked by Weave NPC.
impossible to correctly enforce network policies that restrict which
pods can talk.
- If you do set the `--cluster-cidr` option on kube-proxy, make sure
it matches the `IPALLOC_RANGE` given to Weave Net (see below)
it matches the `IPALLOC_RANGE` given to Weave Net (see below).
- IP forwarding must be enabled on each node, in order for pods to
access Kubernetes services or other IP addresses on another
network. Check this with `sysctl net.ipv4.ip_forward`; the result
should be `1`. (Be aware that there can be security implications of
enabling IP forwarding).
- Weave Net can be run on minikube v0.28 or later with the default CNI config shipped with minikube
being disabled. See [#3124](https://github.com/weaveworks/weave/issues/3124#issuecomment-397820940)
for more details.
## <a name="configuration-options"></a> Changing Configuration Options
@@ -311,11 +344,11 @@ UDP connection from 10.32.0.7:56648 to 10.32.0.11:80 blocked by Weave NPC.
You can customise the YAML you get from `cloud.weave.works` by passing some of Weave Net's options, arguments and environment variables as query parameters:
- `version`: Weave Net's version. Default: `latest`, i.e. latest release. *N.B.*: This only changes the specified version inside the generated YAML file, it does not ensure that the rest of the YAML is compatible with that version. To freeze the YAML version save a copy of the YAML file from the [release page](https://github.com/weaveworks/weave/releases) and use that copy instead of downloading it each time from `cloud.weave.works`.
- `password-secret`: name of the Kubernetes secret containing your password.
- `password-secret`: name of the Kubernetes secret containing your password. *N.B*: The Kubernetes secret name must correspond to a name of a file containing your password.
Example:
$ echo "s3cr3tp4ssw0rd" > /var/lib/weave/weave-passwd.txt
$ kubectl create secret -n kube-system generic weave-passwd --from-file=/var/lib/weave/weave-passwd.txt
$ echo "s3cr3tp4ssw0rd" > /var/lib/weave/weave-passwd
$ kubectl create secret -n kube-system generic weave-passwd --from-file=/var/lib/weave/weave-passwd
$ kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')&password-secret=weave-passwd"
- `known-peers`: comma-separated list of hosts. Default: empty.
@@ -323,7 +356,6 @@ You can customise the YAML you get from `cloud.weave.works` by passing some of W
- `disable-npc`: boolean (`true|false`). Default: `false`.
- `env.NAME=VALUE`: add environment variable `NAME` and set it to `VALUE`.
- `seLinuxOptions.NAME=VALUE`: add SELinux option `NAME` and set it to `VALUE`, e.g. `seLinuxOptions.type=spc_t`
- `use-legacy-netpol`: use [legacy NetworkPolicy semantics](https://v1-6.docs.kubernetes.io/docs/api-reference/v1.6/#networkpolicy-v1beta1-extensions), boolean (`true|false`). Default: `true` for Kubernetes version <= 1.6, `false` for > 1.6.
The list of variables you can set is:
@@ -346,11 +378,18 @@ The list of variables you can set is:
* `WEAVE_EXPOSE_IP` - set the IP address used as a gateway from the
Weave network to the host network - this is useful if you are
configuring the addon as a static pod.
* `WEAVE_METRICS_ADDR` - address and port that the Weave Net
daemon will serve Prometheus-style metrics on (defaults to 0.0.0.0:6782)
* `WEAVE_STATUS_ADDR` - address and port that the Weave Net
daemon will serve status requests on (defaults to disabled)
* `WEAVE_MTU` - Weave Net defaults to 1376 bytes, but you can set a
smaller size if your underlying network has a tighter limit, or set
a larger size for better performance if your network supports jumbo
frames - see [here](/site/tasks/manage/fastdp.md#mtu) for more
details.
* `NO_MASQ_LOCAL` - set to 1 to preserve the client source IP address when
accessing Service annotated with `service.spec.externalTrafficPolicy=Local`.
The feature works only with Weave IPAM (default).
Example:
```
+15
View File
@@ -67,3 +67,18 @@ Columns are as follows:
partition, it may be because the peer has failed and needs to be
removed administratively - see [Starting, Stopping and Removing
Peers](/site/tasks/ipam/stop-remove-peers-ipam.md) for more details.
### <a name="seeded-different-peers"></a>Seeded by Different Peers
If you see the message: `IP allocation was seeded by different peers`,
this means that some Weave Net peers were initialized into one cluster
and some into another cluster; Weave Net cannot operate in this state.
To recover, you need to eliminate the IPAM data from the affected
nodes and restart. If you installed via the Kubernetes Addon, this
data will be in a file under `/var/lib/weave` on the node - delete
this file and restart the node.
For other installations, run `weave reset` and restart. Any existing
connections to containers will be lost.
+22 -17
View File
@@ -10,9 +10,6 @@ controller](/site/kubernetes/kube-addon.md#npc).
### Router Metrics
The endpoint address is `localhost:6782`; the following metrics are
exposed:
* `weave_connections` - Number of peer-to-peer connections.
* `weave_connection_terminations_total` - Number of peer-to-peer
connections terminated.
@@ -25,27 +22,35 @@ exposed:
* `weave_ipam_pending_allocates` - Number of pending allocates.
* `weave_ipam_pending_claims` - Number of pending claims.
#### Publish Router Metrics Endpoint
By default, when started via `weave launch`, weave listens on its local
interface to serve metrics. To publish your metrics throughout your cluster,
e.g. if your prometheus server is installed on a different host machine,
you need to set `WEAVE_STATUS_ADDR` to your corresponding IP and port.
Default port is 6782.
`WEAVE_STATUS_ADDR=X.X.X.X:PORT`
You can set `WEAVE_STATUS_ADDR=0.0.0.0:6782` to listen on all interfaces,
but be aware, this may expose your metrics to the public internet.
### Kubernetes Network Policy Controller Metrics
The endpoint address is `localhost:6781`; the following metric is
The following metric is
exposed:
* `weavenpc_blocked_connections_total` - Connection attempts blocked
by policy controller.
### Metrics Endpoint Addresses
When installed as a Kubernetes Addon, the router listens for metrics
requests on 0.0.0.0:6782 and the Network Policy Controller listens on
0.0.0.0:6781. No other requests are served on these endpoints.
>Note: If your Kubernetes hosts are exposed to the public internet
then these metrics endpoints will also be exposed.
When started via `weave launch`, by default weave listens on its local
interface to serve metrics and other read-only status requests. To
publish your metrics throughout your cluster, you can set
`WEAVE_STATUS_ADDR`:
`WEAVE_STATUS_ADDR=X.X.X.X:PORT`
Set it to an empty string to disable.
You can also pass the parameter `--metrics-addr=X.X.X.X:PORT` to
`weave launch` to specify an address to listen for metrics only.
# Static Configuration for Weave Net
The following YAML fragment can be used in your Prometheus configuration to
+4
View File
@@ -157,6 +157,9 @@ The columns are as follows:
the encryption mode, data transport method, remote peer name and
nickname for pending and established connections, mtu if known
Specific error messages:
* `IP allocation was seeded by different peers` - [more details here](/site/ipam/troubleshooting-ipam.md#seeded-different-peers)
### <a name="weave-status-peers"></a>List Peers
Detailed information on peers can be obtained with `weave status
@@ -329,5 +332,6 @@ and the container image versions as git hashes.
**See Also**
* [Troubleshooting the Kubernetes Addon](/site/kubernetes/kube-addon/#troubleshooting)
* [Troubleshooting IPAM](/site/tasks/ipam/troubleshooting-ipam.md)
* [Troubleshooting the Proxy](/site/tasks/weave-docker-api/using-proxy.md)
+124 -28
View File
@@ -4,7 +4,7 @@
tear_down_kubeadm() {
for host in $HOSTS; do
run_on $host "sudo kubeadm reset && sudo rm -r -f /opt/cni/bin/*weave*"
run_on $host "sudo kubeadm reset --force && sudo rm -r -f /opt/cni/bin/*weave*"
# Remove the rule to ensure that api-server is not accessible while kube-proxy
# is not ready. Otherwise, we have a race between "-j KUBE-FORWARD" and "-j WEAVE-NPC"
# which makes the NodePort isolation test case to fail.
@@ -34,27 +34,25 @@ docker_on $HOST1 run --rm --privileged --net=host --entrypoint=/usr/sbin/ipset w
# kubeadm init upgrades to latest Kubernetes version by default, therefore we try to lock the version using the below option:
k8s_version="$(run_on $HOST1 "kubelet --version" | grep -oP "(?<=Kubernetes )v[\d\.\-beta]+")"
k8s_version_option="$([[ "$k8s_version" > "v1.6" ]] && echo "kubernetes-version" || echo "use-kubernetes-version")"
for host in $HOSTS; do
if [ $host = $HOST1 ] ; then
run_on $host "sudo systemctl start kubelet && sudo kubeadm init --$k8s_version_option=$k8s_version --token=$TOKEN --pod-network-cidr=$WEAVE_NETWORK"
run_on $host "sudo systemctl start kubelet && sudo kubeadm init --kubernetes-version=$k8s_version --token=$TOKEN --pod-network-cidr=$WEAVE_NETWORK"
else
run_on $host "sudo systemctl start kubelet && sudo kubeadm join --token=$TOKEN --discovery-token-unsafe-skip-ca-verification $HOST1IP:$KUBE_PORT"
fi
done
if [ -n "$COVERAGE" ]; then
COVERAGE_ARGS="env:\\n - name: EXTRA_ARGS\\n value: \"-test.coverprofile=/home/weave/cover.prof --\""
WEAVE_ENV_VARS="env:\\n - name: EXTRA_ARGS\\n value: \"-test.coverprofile=/home/weave/cover.prof --\""
else
COVERAGE_ARGS="env:"
WEAVE_ENV_VARS="env:"
fi
# Ensure Kubernetes uses locally built container images and inject code coverage environment variable (or do nothing depending on $COVERAGE):
sed -e "s%imagePullPolicy: Always%imagePullPolicy: Never%" \
-e "s%env:%$COVERAGE_ARGS%" \
-e "s%#npc-args% args:\n - '--use-legacy-netpol'%" \
"$(dirname "$0")/../prog/weave-kube/weave-daemonset-k8s-1.7.yaml" | run_on "$HOST1" "$KUBECTL apply -n kube-system -f -"
-e "s%env:%$WEAVE_ENV_VARS%" \
"$(dirname "$0")/../prog/weave-kube/weave-daemonset-k8s-1.8.yaml" | run_on "$HOST1" "$KUBECTL apply -n kube-system -f -"
sleep 5
@@ -64,7 +62,7 @@ check_connections() {
assert_raises 'wait_for_x check_connections "connections to establish"'
# Check we can ping between the Weave bridg IPs on each host
# Check we can ping between the Weave bridge IPs on each host
HOST1EXPIP=$($SSH $HOST1 "weave expose" || true)
HOST2EXPIP=$($SSH $HOST2 "weave expose" || true)
HOST3EXPIP=$($SSH $HOST3 "weave expose" || true)
@@ -77,10 +75,19 @@ assert "run_on $HOST1 ps aux | grep -c '[d]efunct'" "0"
assert "run_on $HOST2 ps aux | grep -c '[d]efunct'" "0"
assert "run_on $HOST3 ps aux | grep -c '[d]efunct'" "0"
# Set up a simple network policy so all our test pods can talk to each other
run_on $HOST1 "$KUBECTL annotate ns default net.beta.kubernetes.io/network-policy='{\"ingress\":{\"isolation\":\"DefaultDeny\"}}'"
# Set up simple network policies so all our test pods can talk to each other
run_on $HOST1 "$KUBECTL apply -f -" <<EOF
apiVersion: extensions/v1beta1
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
run_on $HOST1 "$KUBECTL apply -f -" <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test840
@@ -103,7 +110,7 @@ EOF
# Another policy, this time with no 'from' section, just to check that doesn't cause a crash
run_on $HOST1 "$KUBECTL apply -f -" <<EOF
apiVersion: extensions/v1beta1
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test840f
@@ -180,17 +187,6 @@ run_on $HOST1 "$KUBECTL run nettest-deny --labels=\"access=deny,run=nettest-deny
denyPodName=$($SSH $HOST1 "$KUBECTL get pods -l run=nettest-deny -o go-template='{{(index .items 0).metadata.name}}'")
assert_raises "! $SSH $HOST1 $KUBECTL exec $denyPodName -- curl -s -S -f -m 2 http://$DOMAIN:8080/status >/dev/null"
# Restart weave-net with npc in non-legacy mode
$SSH $HOST1 "$KUBECTL delete ds weave-net -n=kube-system"
sed -e "s%imagePullPolicy: Always%imagePullPolicy: Never%" \
-e "s%env:%$COVERAGE_ARGS%" \
"$(dirname "$0")/../prog/weave-kube/weave-daemonset-k8s-1.7.yaml" | run_on "$HOST1" "$KUBECTL apply -n kube-system -f -"
assert_raises 'wait_for_x check_all_pods_communicate pods'
# nettest-deny should still not be able to reach nettest pods
assert_raises "! $SSH $HOST1 $KUBECTL exec $denyPodName -- curl -s -S -f -m 2 http://$DOMAIN:8080/status >/dev/null"
# check access via virtual IP
VIRTUAL_IP=$($SSH $HOST1 $KUBECTL get service netvirt -o template --template={{.spec.clusterIP}})
assert_raises "$SSH $HOST1 $KUBECTL exec $podName -- curl -s -S -f -m 2 http://$VIRTUAL_IP/status >/dev/null"
@@ -226,12 +222,39 @@ run_on $HOST1 "$KUBECTL delete netpol allow-nettest-deny"
# nettest-deny should still not be able to reach nettest pods
assert_raises "! $SSH $HOST1 $KUBECTL exec $denyPodName -- curl -s -S -f -m 2 http://$DOMAIN:8080/status >/dev/null"
# allow access for all
# Create many namespaces to stress namespaceSelector
for n in 1 2 3 4 5 6 7 8 9 10; do
run_on $HOST1 "$KUBECTL create namespace namespace${n}"
done
# allow access from any namespace
run_on $HOST1 "$KUBECTL apply -f -" <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-nettest-deny
name: allow-any-namespace
namespace: default
spec:
podSelector: {}
ingress:
- from:
- namespaceSelector: {}
EOF
# Should be able to access from the "deny" pod now
assert_raises "$SSH $HOST1 $KUBECTL exec $denyPodName -- curl -s -S -f -m 2 http://$DOMAIN:8080/status >/dev/null"
# host should still not be able to reach pods via service virtual IP or NodePort
# because host is not in a namespace
assert_raises "! $SSH $HOST1 curl -s -S -f -m 2 http://$VIRTUAL_IP/status >/dev/null"
assert_raises "! $SSH $HOST1 curl -s -S -f -m 2 http://$HOST2:31138/status >/dev/null"
# allow access from anywhere
run_on $HOST1 "$KUBECTL apply -f -" <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-anywhere
namespace: default
spec:
podSelector: {}
@@ -239,12 +262,85 @@ spec:
- {}
EOF
assert_raises "$SSH $HOST1 $KUBECTL exec $denyPodName -- curl -s -S -f -m 2 http://$DOMAIN:8080/status >/dev/null"
# Virtual IP and NodePort should now work
assert_raises "$SSH $HOST1 curl -s -S -f -m 2 http://$VIRTUAL_IP/status >/dev/null"
assert_raises "$SSH $HOST1 curl -s -S -f -m 2 http://$HOST2:31138/status >/dev/null"
# allow nettest-deny to access only to 8.8.8.0/24
run_on $HOST1 "$KUBECTL apply -f -" <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: egress-nettest-deny-dns
namespace: default
spec:
podSelector:
matchLabels:
run: nettest-deny
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 8.8.8.0/24
except:
- 8.8.8.4/32
EOF
assert_raises "! $SSH $HOST1 $KUBECTL exec $denyPodName -- curl -s -S -f -m 2 http://$DOMAIN:8080/status >/dev/null"
assert_raises "! $SSH $HOST1 $KUBECTL exec $denyPodName -- dig @8.8.8.4 google.com >/dev/null"
assert_raises "$SSH $HOST1 $KUBECTL exec $denyPodName -- dig @8.8.8.8 google.com >/dev/null"
# also, allow nettest-deny to access nettest and kube-system (for kube-dns)
run_on $HOST1 "$KUBECTL label namespace kube-system name=kube-system"
run_on $HOST1 "$KUBECTL replace -f -" <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: egress-nettest-deny-dns
namespace: default
spec:
podSelector:
matchLabels:
run: nettest-deny
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 8.8.8.0/24
except:
- 8.8.8.4/32
- podSelector:
matchLabels:
run: nettest
- namespaceSelector:
matchLabels:
name: kube-system
EOF
assert_raises "$SSH $HOST1 $KUBECTL exec $denyPodName -- curl -s -S -f -m 2 http://$DOMAIN:8080/status >/dev/null"
# Passing --no-masq-local and setting externalTrafficPolicy to Local must preserve
# the client source IP addr
CLIENT_IP_MASQ="$($SSH $HOST1 curl -sS http://$HOST2:31138/client_ip)"
WEAVE_ENV_VARS="${WEAVE_ENV_VARS}\\n - name: NO_MASQ_LOCAL\\n value: \"1\""
$SSH $HOST1 "$KUBECTL delete ds weave-net -n=kube-system"
sed -e "s%imagePullPolicy: Always%imagePullPolicy: Never%" \
-e "s%env:%$WEAVE_ENV_VARS%" \
"$(dirname "$0")/../prog/weave-kube/weave-daemonset-k8s-1.8.yaml" | run_on "$HOST1" "$KUBECTL apply -n kube-system -f -"
sleep 5
run_on $HOST1 "$KUBECTL patch svc netvirt -p '{\"spec\":{\"externalTrafficPolicy\":\"Local\"}}'"
CLIENT_IP_NO_MASQ="$($SSH $HOST1 curl -sS http://$HOST2:31138/client_ip)"
assert_raises "[ $CLIENT_IP_NO_MASQ != $CLIENT_IP_MASQ ]"
assert_raises "[ $HOST1IP == $CLIENT_IP_NO_MASQ ]"
tear_down_kubeadm
# Destroy our test ipset, and implicitly check it is still there
@@ -27,7 +27,7 @@ fi
function teardown_kubernetes_cluster {
greyly echo "Tearing down kubernetes cluster"
for host in $HOSTS; do
run_on $host "sudo kubeadm reset && sudo rm -r -f /opt/cni/bin/*weave*"
run_on $host "sudo kubeadm reset --force && sudo rm -r -f /opt/cni/bin/*weave*"
done
}
@@ -38,11 +38,10 @@ function setup_kubernetes_cluster {
# kubeadm init upgrades to latest Kubernetes version by default, therefore we try to lock the version using the below option:
k8s_version="$(run_on $HOST1 "kubelet --version" | grep -oP "(?<=Kubernetes )v[\d\.\-beta]+")"
k8s_version_option="$([[ "$k8s_version" > "v1.6" ]] && echo "kubernetes-version" || echo "use-kubernetes-version")"
for host in $HOSTS; do
if [ $host = $HOST1 ] ; then
run_on $host "sudo systemctl start kubelet && sudo kubeadm init --$k8s_version_option=$k8s_version --token=$TOKEN"
run_on $host "sudo systemctl start kubelet && sudo kubeadm init --kubernetes-version=$k8s_version --token=$TOKEN"
else
run_on $host "sudo systemctl start kubelet && sudo kubeadm join --token=$TOKEN --discovery-token-unsafe-skip-ca-verification $HOST1IP:$KUBE_PORT"
fi
+1 -1
View File
@@ -337,7 +337,7 @@ start_suite() {
# Common postconditions to assert on each host, after each test:
assert_common_postconditions() {
# Ensure we do not generate any defunct (a.k.a. zombie) process:
assert "run_on $1 ps aux | grep -c '[d]efunct'" "0"
assert "run_on $1 ps aux | grep '[d]efunct'" ""
}
end_suite() {
+1 -1
View File
@@ -13,7 +13,7 @@
# limitations under the License.
FROM bash
RUN apk add --no-cache curl
RUN apk add --no-cache curl bind-tools
ADD webserver webserver
EXPOSE 8080
ENTRYPOINT ["/webserver"]
+17
View File
@@ -13,6 +13,8 @@
// Visit /status to see pass/running/fail determination. (literally, it will
// return one of those words.)
//
// Visit /client_ip to see the source IP addr of the request.
//
// /write is used by other network test servers to register connectivity.
package main
@@ -78,6 +80,20 @@ func (s *State) serveStatus(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "fail")
}
// serveClientIP returns the client source IP addr.
func (s *State) serveClientIP(w http.ResponseWriter, r *http.Request) {
s.lock.Lock()
defer s.lock.Unlock()
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
go s.Logf("Warning: unable to parse remote addr: %s", err)
w.WriteHeader(http.StatusInternalServerError)
}
fmt.Fprintf(w, host)
}
// serveRead writes our json encoded state
func (s *State) serveRead(w http.ResponseWriter, r *http.Request) {
s.lock.Lock()
@@ -186,6 +202,7 @@ func main() {
http.HandleFunc("/read", state.serveRead)
http.HandleFunc("/write", state.serveWrite)
http.HandleFunc("/status", state.serveStatus)
http.HandleFunc("/client_ip", state.serveClientIP)
go log.Fatal(http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", *port), nil))
+1 -1
View File
@@ -23,4 +23,4 @@ if [ -n "$CIRCLECI" -o -n "$PARALLEL" ]; then
RUNNER_ARGS="$RUNNER_ARGS -parallel"
fi
HOSTS="$HOSTS" "${DIR}/../tools/runner/runner" -timeout=300 $RUNNER_ARGS $TESTS
HOSTS="$HOSTS" "${DIR}/../tools/runner/runner" -timeout=360 $RUNNER_ARGS $TESTS
+18 -18
View File
@@ -41,15 +41,15 @@ func main() {
log.Fatalf("The client key already exists. Please remove it or specify a different key/cert.")
}
err = cert.GenerateCert(
[]string{""},
clientCertPath,
clientKeyPath,
caCertPath,
caKeyPath,
org,
bits,
)
err = cert.GenerateCert(&cert.Options{
Hosts: []string{""},
CertFile: clientCertPath,
KeyFile: clientKeyPath,
CAFile: caCertPath,
CAKeyFile: caKeyPath,
Org: org,
Bits: bits,
})
if err != nil {
log.Fatalf("Error generating client certificate: %s", err)
}
@@ -64,15 +64,15 @@ func main() {
if _, err := os.Stat(serverCertPath); os.IsNotExist(err) {
log.Printf("Creating server certificate: %s", serverCertPath)
err = cert.GenerateCert(
[]string{ip},
serverCertPath,
serverKeyPath,
caCertPath,
caKeyPath,
org,
bits,
)
err = cert.GenerateCert(&cert.Options{
Hosts: []string{ip},
CertFile: serverCertPath,
KeyFile: serverKeyPath,
CAFile: caCertPath,
CAKeyFile: caKeyPath,
Org: org,
Bits: bits,
})
if err != nil {
log.Fatalf("error generating server cert: %s", err)
}
+30
View File
@@ -0,0 +1,30 @@
// Modified from the original at k8s.io/client-go/testing to the bare minimum
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package testing
import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
type Action interface {
GetNamespace() string
GetVerb() string
GetResource() schema.GroupVersionResource
GetSubresource() string
Matches(verb, resource string) bool
}
+393
View File
@@ -0,0 +1,393 @@
// Modified from the original at k8s.io/client-go/testing in line with
// https://github.com/kubernetes/kubernetes/pull/68460
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package testing
import (
"fmt"
"sync"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/testing"
)
// ObjectTracker keeps track of objects. It is intended to be used to
// fake calls to a server by returning objects based on their kind,
// namespace and name.
type ObjectTracker interface {
// Add adds an object to the tracker. If object being added
// is a list, its items are added separately.
Add(obj runtime.Object) error
// Get retrieves the object by its kind, namespace and name.
Get(gvr schema.GroupVersionResource, ns, name string) (runtime.Object, error)
// Create adds an object to the tracker in the specified namespace.
Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error
// Update updates an existing object in the tracker in the specified namespace.
Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error
// List retrieves all objects of a given kind in the given
// namespace. Only non-List kinds are accepted.
List(gvr schema.GroupVersionResource, gvk schema.GroupVersionKind, ns string) (runtime.Object, error)
// Delete deletes an existing object from the tracker. If object
// didn't exist in the tracker prior to deletion, Delete returns
// no error.
Delete(gvr schema.GroupVersionResource, ns, name string) error
}
// ObjectScheme abstracts the implementation of common operations on objects.
type ObjectScheme interface {
runtime.ObjectCreater
runtime.ObjectTyper
}
// ObjectReaction returns a ReactionFunc that applies core.Action to
// the given tracker.
func ObjectReaction(tracker ObjectTracker) testing.ReactionFunc {
return func(action testing.Action) (bool, runtime.Object, error) {
ns := action.GetNamespace()
gvr := action.GetResource()
// Here and below we need to switch on implementation types,
// not on interfaces, as some interfaces are identical
// (e.g. UpdateAction and CreateAction), so if we use them,
// updates and creates end up matching the same case branch.
switch action := action.(type) {
case testing.ListActionImpl:
obj, err := tracker.List(gvr, action.GetKind(), ns)
return true, obj, err
case testing.GetActionImpl:
obj, err := tracker.Get(gvr, ns, action.GetName())
return true, obj, err
case testing.CreateActionImpl:
objMeta, err := meta.Accessor(action.GetObject())
if err != nil {
return true, nil, err
}
if action.GetSubresource() == "" {
err = tracker.Create(gvr, action.GetObject(), ns)
} else {
// TODO: Currently we're handling subresource creation as an update
// on the enclosing resource. This works for some subresources but
// might not be generic enough.
err = tracker.Update(gvr, action.GetObject(), ns)
}
if err != nil {
return true, nil, err
}
obj, err := tracker.Get(gvr, ns, objMeta.GetName())
return true, obj, err
case testing.UpdateActionImpl:
objMeta, err := meta.Accessor(action.GetObject())
if err != nil {
return true, nil, err
}
err = tracker.Update(gvr, action.GetObject(), ns)
if err != nil {
if errors.IsConflict(err) { // return the currently-stored version of the object
obj, _ := tracker.Get(gvr, ns, objMeta.GetName())
return true, obj, err
}
return true, nil, err
}
obj, err := tracker.Get(gvr, ns, objMeta.GetName())
return true, obj, err
case testing.DeleteActionImpl:
err := tracker.Delete(gvr, ns, action.GetName())
if err != nil {
return true, nil, err
}
return true, nil, nil
default:
return false, nil, fmt.Errorf("no reaction implemented for %s", action)
}
}
}
type tracker struct {
scheme ObjectScheme
decoder runtime.Decoder
lock sync.RWMutex
objects map[schema.GroupVersionResource][]runtime.Object
counter int
}
var _ ObjectTracker = &tracker{}
// NewObjectTracker returns an ObjectTracker that can be used to keep track
// of objects for the fake clientset. Mostly useful for unit tests.
func NewObjectTracker(scheme ObjectScheme, decoder runtime.Decoder) ObjectTracker {
return &tracker{
scheme: scheme,
decoder: decoder,
objects: make(map[schema.GroupVersionResource][]runtime.Object),
}
}
func (t *tracker) List(gvr schema.GroupVersionResource, gvk schema.GroupVersionKind, ns string) (runtime.Object, error) {
// Heuristic for list kind: original kind + List suffix. Might
// not always be true but this tracker has a pretty limited
// understanding of the actual API model.
listGVK := gvk
listGVK.Kind = listGVK.Kind + "List"
// GVK does have the concept of "internal version". The scheme recognizes
// the runtime.APIVersionInternal, but not the empty string.
if listGVK.Version == "" {
listGVK.Version = runtime.APIVersionInternal
}
list, err := t.scheme.New(listGVK)
if err != nil {
return nil, err
}
if !meta.IsListType(list) {
return nil, fmt.Errorf("%q is not a list type", listGVK.Kind)
}
t.lock.RLock()
defer t.lock.RUnlock()
objs, ok := t.objects[gvr]
if !ok {
return list, nil
}
matchingObjs, err := filterByNamespaceAndName(objs, ns, "")
if err != nil {
return nil, err
}
if err := meta.SetList(list, matchingObjs); err != nil {
return nil, err
}
return list.DeepCopyObject(), nil
}
func (t *tracker) Get(gvr schema.GroupVersionResource, ns, name string) (runtime.Object, error) {
errNotFound := errors.NewNotFound(gvr.GroupResource(), name)
t.lock.RLock()
defer t.lock.RUnlock()
objs, ok := t.objects[gvr]
if !ok {
return nil, errNotFound
}
matchingObjs, err := filterByNamespaceAndName(objs, ns, name)
if err != nil {
return nil, err
}
if len(matchingObjs) == 0 {
return nil, errNotFound
}
if len(matchingObjs) > 1 {
return nil, fmt.Errorf("more than one object matched gvr %s, ns: %q name: %q", gvr, ns, name)
}
// Only one object should match in the tracker if it works
// correctly, as Add/Update methods enforce kind/namespace/name
// uniqueness.
obj := matchingObjs[0].DeepCopyObject()
if status, ok := obj.(*metav1.Status); ok {
if status.Status != metav1.StatusSuccess {
return nil, &errors.StatusError{ErrStatus: *status}
}
}
return obj, nil
}
func (t *tracker) Add(obj runtime.Object) error {
if meta.IsListType(obj) {
return t.addList(obj, false)
}
objMeta, err := meta.Accessor(obj)
if err != nil {
return err
}
gvks, _, err := t.scheme.ObjectKinds(obj)
if err != nil {
return err
}
if len(gvks) == 0 {
return fmt.Errorf("no registered kinds for %v", obj)
}
for _, gvk := range gvks {
// NOTE: UnsafeGuessKindToResource is a heuristic and default match. The
// actual registration in apiserver can specify arbitrary route for a
// gvk. If a test uses such objects, it cannot preset the tracker with
// objects via Add(). Instead, it should trigger the Create() function
// of the tracker, where an arbitrary gvr can be specified.
gvr, _ := meta.UnsafeGuessKindToResource(gvk)
// Resource doesn't have the concept of "__internal" version, just set it to "".
if gvr.Version == runtime.APIVersionInternal {
gvr.Version = ""
}
err := t.add(gvr, obj, objMeta.GetNamespace(), false)
if err != nil {
return err
}
}
return nil
}
func (t *tracker) Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
return t.add(gvr, obj, ns, false)
}
func (t *tracker) Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
return t.add(gvr, obj, ns, true)
}
func (t *tracker) add(gvr schema.GroupVersionResource, obj runtime.Object, ns string, replaceExisting bool) error {
t.lock.Lock()
defer t.lock.Unlock()
gr := gvr.GroupResource()
// To avoid the object from being accidentally modified by caller
// after it's been added to the tracker, we always store the deep
// copy.
obj = obj.DeepCopyObject()
newMeta, err := meta.Accessor(obj)
if err != nil {
return err
}
// Propagate namespace to the new object if hasn't already been set.
if len(newMeta.GetNamespace()) == 0 {
newMeta.SetNamespace(ns)
}
// Update version on new object
inputResourceVersion := newMeta.GetResourceVersion()
newMeta.SetResourceVersion(fmt.Sprintf("%d", t.counter))
t.counter++
if ns != newMeta.GetNamespace() {
msg := fmt.Sprintf("request namespace does not match object namespace, request: %q object: %q", ns, newMeta.GetNamespace())
return errors.NewBadRequest(msg)
}
for i, existingObj := range t.objects[gvr] {
oldMeta, err := meta.Accessor(existingObj)
if err != nil {
return err
}
if oldMeta.GetNamespace() == newMeta.GetNamespace() && oldMeta.GetName() == newMeta.GetName() {
if replaceExisting {
if oldMeta.GetResourceVersion() != inputResourceVersion {
return errors.NewConflict(gr, newMeta.GetName(), fmt.Errorf("Resource version mismatch"))
}
t.objects[gvr][i] = obj
return nil
}
return errors.NewAlreadyExists(gr, newMeta.GetName())
}
}
if replaceExisting {
// Tried to update but no matching object was found.
return errors.NewNotFound(gr, newMeta.GetName())
}
t.objects[gvr] = append(t.objects[gvr], obj)
return nil
}
func (t *tracker) addList(obj runtime.Object, replaceExisting bool) error {
list, err := meta.ExtractList(obj)
if err != nil {
return err
}
errs := runtime.DecodeList(list, t.decoder)
if len(errs) > 0 {
return errs[0]
}
for _, obj := range list {
if err := t.Add(obj); err != nil {
return err
}
}
return nil
}
func (t *tracker) Delete(gvr schema.GroupVersionResource, ns, name string) error {
t.lock.Lock()
defer t.lock.Unlock()
found := false
for i, existingObj := range t.objects[gvr] {
objMeta, err := meta.Accessor(existingObj)
if err != nil {
return err
}
if objMeta.GetNamespace() == ns && objMeta.GetName() == name {
t.objects[gvr] = append(t.objects[gvr][:i], t.objects[gvr][i+1:]...)
found = true
break
}
}
if found {
return nil
}
return errors.NewNotFound(gvr.GroupResource(), name)
}
// filterByNamespaceAndName returns all objects in the collection that
// match provided namespace and name. Empty namespace matches
// non-namespaced objects.
func filterByNamespaceAndName(objs []runtime.Object, ns, name string) ([]runtime.Object, error) {
var res []runtime.Object
for _, obj := range objs {
acc, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
if ns != "" && acc.GetNamespace() != ns {
continue
}
if name != "" && acc.GetName() != name {
continue
}
res = append(res, obj)
}
return res, nil
}
+1 -1
View File
@@ -16,7 +16,7 @@ def ansibleize(h)
end
def get_go_version_from_build_dockerfile()
go_regexp = /FROM golang:(\S*).*?/
go_regexp = /FROM golang:([\d\.]*).*?/
dockerfile_path = File.expand_path(File.join(File.dirname(__FILE__), 'build', 'Dockerfile'))
go_version = File.readlines(dockerfile_path).select { |line| line.match(go_regexp) }.first.match(go_regexp).captures.first
if go_version.nil?
-26
View File
@@ -1,26 +0,0 @@
# Managing Vendored Dependencies with `git submodule`
These operations result in staged but uncommitted changes to your
branch; you will need to commit them as normal. Execute them in the
root of your checkout.
## Adding a New Dependency
~/weave$ git submodule add https://example.com/organisation/module vendor/example.com/organisation/module
## Update All Dependencies to Upstream `master`
~/weave$ git submodule foreach git pull origin master
## Force a Specific Dependency to a Particular Commit
~/weave$ (cd vendor/example.com/organisation/module && git checkout <commit-ish>)
~/weave$ git add vendor/example.com/organisation/module
## Remove a Dependency
~/weave$ git submodule deinit vendor/example.com/organisation/module
~/weave$ git rm vendor/example.com/organisation/module
~/weave$ rm -rf .git/modules/vendor/example.con/organisation/module
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Microsoft Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+12
View File
@@ -0,0 +1,12 @@
# go-ansiterm
This is a cross platform Ansi Terminal Emulation library. It reads a stream of Ansi characters and produces the appropriate function calls. The results of the function calls are platform dependent.
For example the parser might receive "ESC, [, A" as a stream of three characters. This is the code for Cursor Up (http://www.vt100.net/docs/vt510-rm/CUU). The parser then calls the cursor up function (CUU()) on an event handler. The event handler determines what platform specific work must be done to cause the cursor to move up one position.
The parser (parser.go) is a partial implementation of this state machine (http://vt100.net/emu/vt500_parser.png). There are also two event handler implementations, one for tests (test_event_handler.go) to validate that the expected events are being produced and called, the other is a Windows implementation (winterm/win_event_handler.go).
See parser_test.go for examples exercising the state machine and generating appropriate function calls.
-----
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
+188
View File
@@ -0,0 +1,188 @@
package ansiterm
const LogEnv = "DEBUG_TERMINAL"
// ANSI constants
// References:
// -- http://www.ecma-international.org/publications/standards/Ecma-048.htm
// -- http://man7.org/linux/man-pages/man4/console_codes.4.html
// -- http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
// -- http://en.wikipedia.org/wiki/ANSI_escape_code
// -- http://vt100.net/emu/dec_ansi_parser
// -- http://vt100.net/emu/vt500_parser.svg
// -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
// -- http://www.inwap.com/pdp10/ansicode.txt
const (
// ECMA-48 Set Graphics Rendition
// Note:
// -- Constants leading with an underscore (e.g., _ANSI_xxx) are unsupported or reserved
// -- Fonts could possibly be supported via SetCurrentConsoleFontEx
// -- Windows does not expose the per-window cursor (i.e., caret) blink times
ANSI_SGR_RESET = 0
ANSI_SGR_BOLD = 1
ANSI_SGR_DIM = 2
_ANSI_SGR_ITALIC = 3
ANSI_SGR_UNDERLINE = 4
_ANSI_SGR_BLINKSLOW = 5
_ANSI_SGR_BLINKFAST = 6
ANSI_SGR_REVERSE = 7
_ANSI_SGR_INVISIBLE = 8
_ANSI_SGR_LINETHROUGH = 9
_ANSI_SGR_FONT_00 = 10
_ANSI_SGR_FONT_01 = 11
_ANSI_SGR_FONT_02 = 12
_ANSI_SGR_FONT_03 = 13
_ANSI_SGR_FONT_04 = 14
_ANSI_SGR_FONT_05 = 15
_ANSI_SGR_FONT_06 = 16
_ANSI_SGR_FONT_07 = 17
_ANSI_SGR_FONT_08 = 18
_ANSI_SGR_FONT_09 = 19
_ANSI_SGR_FONT_10 = 20
_ANSI_SGR_DOUBLEUNDERLINE = 21
ANSI_SGR_BOLD_DIM_OFF = 22
_ANSI_SGR_ITALIC_OFF = 23
ANSI_SGR_UNDERLINE_OFF = 24
_ANSI_SGR_BLINK_OFF = 25
_ANSI_SGR_RESERVED_00 = 26
ANSI_SGR_REVERSE_OFF = 27
_ANSI_SGR_INVISIBLE_OFF = 28
_ANSI_SGR_LINETHROUGH_OFF = 29
ANSI_SGR_FOREGROUND_BLACK = 30
ANSI_SGR_FOREGROUND_RED = 31
ANSI_SGR_FOREGROUND_GREEN = 32
ANSI_SGR_FOREGROUND_YELLOW = 33
ANSI_SGR_FOREGROUND_BLUE = 34
ANSI_SGR_FOREGROUND_MAGENTA = 35
ANSI_SGR_FOREGROUND_CYAN = 36
ANSI_SGR_FOREGROUND_WHITE = 37
_ANSI_SGR_RESERVED_01 = 38
ANSI_SGR_FOREGROUND_DEFAULT = 39
ANSI_SGR_BACKGROUND_BLACK = 40
ANSI_SGR_BACKGROUND_RED = 41
ANSI_SGR_BACKGROUND_GREEN = 42
ANSI_SGR_BACKGROUND_YELLOW = 43
ANSI_SGR_BACKGROUND_BLUE = 44
ANSI_SGR_BACKGROUND_MAGENTA = 45
ANSI_SGR_BACKGROUND_CYAN = 46
ANSI_SGR_BACKGROUND_WHITE = 47
_ANSI_SGR_RESERVED_02 = 48
ANSI_SGR_BACKGROUND_DEFAULT = 49
// 50 - 65: Unsupported
ANSI_MAX_CMD_LENGTH = 4096
MAX_INPUT_EVENTS = 128
DEFAULT_WIDTH = 80
DEFAULT_HEIGHT = 24
ANSI_BEL = 0x07
ANSI_BACKSPACE = 0x08
ANSI_TAB = 0x09
ANSI_LINE_FEED = 0x0A
ANSI_VERTICAL_TAB = 0x0B
ANSI_FORM_FEED = 0x0C
ANSI_CARRIAGE_RETURN = 0x0D
ANSI_ESCAPE_PRIMARY = 0x1B
ANSI_ESCAPE_SECONDARY = 0x5B
ANSI_OSC_STRING_ENTRY = 0x5D
ANSI_COMMAND_FIRST = 0x40
ANSI_COMMAND_LAST = 0x7E
DCS_ENTRY = 0x90
CSI_ENTRY = 0x9B
OSC_STRING = 0x9D
ANSI_PARAMETER_SEP = ";"
ANSI_CMD_G0 = '('
ANSI_CMD_G1 = ')'
ANSI_CMD_G2 = '*'
ANSI_CMD_G3 = '+'
ANSI_CMD_DECPNM = '>'
ANSI_CMD_DECPAM = '='
ANSI_CMD_OSC = ']'
ANSI_CMD_STR_TERM = '\\'
KEY_CONTROL_PARAM_2 = ";2"
KEY_CONTROL_PARAM_3 = ";3"
KEY_CONTROL_PARAM_4 = ";4"
KEY_CONTROL_PARAM_5 = ";5"
KEY_CONTROL_PARAM_6 = ";6"
KEY_CONTROL_PARAM_7 = ";7"
KEY_CONTROL_PARAM_8 = ";8"
KEY_ESC_CSI = "\x1B["
KEY_ESC_N = "\x1BN"
KEY_ESC_O = "\x1BO"
FILL_CHARACTER = ' '
)
func getByteRange(start byte, end byte) []byte {
bytes := make([]byte, 0, 32)
for i := start; i <= end; i++ {
bytes = append(bytes, byte(i))
}
return bytes
}
var toGroundBytes = getToGroundBytes()
var executors = getExecuteBytes()
// SPACE 20+A0 hex Always and everywhere a blank space
// Intermediate 20-2F hex !"#$%&'()*+,-./
var intermeds = getByteRange(0x20, 0x2F)
// Parameters 30-3F hex 0123456789:;<=>?
// CSI Parameters 30-39, 3B hex 0123456789;
var csiParams = getByteRange(0x30, 0x3F)
var csiCollectables = append(getByteRange(0x30, 0x39), getByteRange(0x3B, 0x3F)...)
// Uppercase 40-5F hex @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
var upperCase = getByteRange(0x40, 0x5F)
// Lowercase 60-7E hex `abcdefghijlkmnopqrstuvwxyz{|}~
var lowerCase = getByteRange(0x60, 0x7E)
// Alphabetics 40-7E hex (all of upper and lower case)
var alphabetics = append(upperCase, lowerCase...)
var printables = getByteRange(0x20, 0x7F)
var escapeIntermediateToGroundBytes = getByteRange(0x30, 0x7E)
var escapeToGroundBytes = getEscapeToGroundBytes()
// See http://www.vt100.net/emu/vt500_parser.png for description of the complex
// byte ranges below
func getEscapeToGroundBytes() []byte {
escapeToGroundBytes := getByteRange(0x30, 0x4F)
escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x51, 0x57)...)
escapeToGroundBytes = append(escapeToGroundBytes, 0x59)
escapeToGroundBytes = append(escapeToGroundBytes, 0x5A)
escapeToGroundBytes = append(escapeToGroundBytes, 0x5C)
escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x60, 0x7E)...)
return escapeToGroundBytes
}
func getExecuteBytes() []byte {
executeBytes := getByteRange(0x00, 0x17)
executeBytes = append(executeBytes, 0x19)
executeBytes = append(executeBytes, getByteRange(0x1C, 0x1F)...)
return executeBytes
}
func getToGroundBytes() []byte {
groundBytes := []byte{0x18}
groundBytes = append(groundBytes, 0x1A)
groundBytes = append(groundBytes, getByteRange(0x80, 0x8F)...)
groundBytes = append(groundBytes, getByteRange(0x91, 0x97)...)
groundBytes = append(groundBytes, 0x99)
groundBytes = append(groundBytes, 0x9A)
groundBytes = append(groundBytes, 0x9C)
return groundBytes
}
// Delete 7F hex Always and everywhere ignored
// C1 Control 80-9F hex 32 additional control characters
// G1 Displayable A1-FE hex 94 additional displayable characters
// Special A0+FF hex Same as SPACE and DELETE
+7
View File
@@ -0,0 +1,7 @@
package ansiterm
type ansiContext struct {
currentChar byte
paramBuffer []byte
interBuffer []byte
}
+49
View File
@@ -0,0 +1,49 @@
package ansiterm
type csiEntryState struct {
baseState
}
func (csiState csiEntryState) Handle(b byte) (s state, e error) {
csiState.parser.logf("CsiEntry::Handle %#x", b)
nextState, err := csiState.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case sliceContains(alphabetics, b):
return csiState.parser.ground, nil
case sliceContains(csiCollectables, b):
return csiState.parser.csiParam, nil
case sliceContains(executors, b):
return csiState, csiState.parser.execute()
}
return csiState, nil
}
func (csiState csiEntryState) Transition(s state) error {
csiState.parser.logf("CsiEntry::Transition %s --> %s", csiState.Name(), s.Name())
csiState.baseState.Transition(s)
switch s {
case csiState.parser.ground:
return csiState.parser.csiDispatch()
case csiState.parser.csiParam:
switch {
case sliceContains(csiParams, csiState.parser.context.currentChar):
csiState.parser.collectParam()
case sliceContains(intermeds, csiState.parser.context.currentChar):
csiState.parser.collectInter()
}
}
return nil
}
func (csiState csiEntryState) Enter() error {
csiState.parser.clear()
return nil
}
+38
View File
@@ -0,0 +1,38 @@
package ansiterm
type csiParamState struct {
baseState
}
func (csiState csiParamState) Handle(b byte) (s state, e error) {
csiState.parser.logf("CsiParam::Handle %#x", b)
nextState, err := csiState.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case sliceContains(alphabetics, b):
return csiState.parser.ground, nil
case sliceContains(csiCollectables, b):
csiState.parser.collectParam()
return csiState, nil
case sliceContains(executors, b):
return csiState, csiState.parser.execute()
}
return csiState, nil
}
func (csiState csiParamState) Transition(s state) error {
csiState.parser.logf("CsiParam::Transition %s --> %s", csiState.Name(), s.Name())
csiState.baseState.Transition(s)
switch s {
case csiState.parser.ground:
return csiState.parser.csiDispatch()
}
return nil
}
+36
View File
@@ -0,0 +1,36 @@
package ansiterm
type escapeIntermediateState struct {
baseState
}
func (escState escapeIntermediateState) Handle(b byte) (s state, e error) {
escState.parser.logf("escapeIntermediateState::Handle %#x", b)
nextState, err := escState.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case sliceContains(intermeds, b):
return escState, escState.parser.collectInter()
case sliceContains(executors, b):
return escState, escState.parser.execute()
case sliceContains(escapeIntermediateToGroundBytes, b):
return escState.parser.ground, nil
}
return escState, nil
}
func (escState escapeIntermediateState) Transition(s state) error {
escState.parser.logf("escapeIntermediateState::Transition %s --> %s", escState.Name(), s.Name())
escState.baseState.Transition(s)
switch s {
case escState.parser.ground:
return escState.parser.escDispatch()
}
return nil
}
+47
View File
@@ -0,0 +1,47 @@
package ansiterm
type escapeState struct {
baseState
}
func (escState escapeState) Handle(b byte) (s state, e error) {
escState.parser.logf("escapeState::Handle %#x", b)
nextState, err := escState.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case b == ANSI_ESCAPE_SECONDARY:
return escState.parser.csiEntry, nil
case b == ANSI_OSC_STRING_ENTRY:
return escState.parser.oscString, nil
case sliceContains(executors, b):
return escState, escState.parser.execute()
case sliceContains(escapeToGroundBytes, b):
return escState.parser.ground, nil
case sliceContains(intermeds, b):
return escState.parser.escapeIntermediate, nil
}
return escState, nil
}
func (escState escapeState) Transition(s state) error {
escState.parser.logf("Escape::Transition %s --> %s", escState.Name(), s.Name())
escState.baseState.Transition(s)
switch s {
case escState.parser.ground:
return escState.parser.escDispatch()
case escState.parser.escapeIntermediate:
return escState.parser.collectInter()
}
return nil
}
func (escState escapeState) Enter() error {
escState.parser.clear()
return nil
}
+90
View File
@@ -0,0 +1,90 @@
package ansiterm
type AnsiEventHandler interface {
// Print
Print(b byte) error
// Execute C0 commands
Execute(b byte) error
// CUrsor Up
CUU(int) error
// CUrsor Down
CUD(int) error
// CUrsor Forward
CUF(int) error
// CUrsor Backward
CUB(int) error
// Cursor to Next Line
CNL(int) error
// Cursor to Previous Line
CPL(int) error
// Cursor Horizontal position Absolute
CHA(int) error
// Vertical line Position Absolute
VPA(int) error
// CUrsor Position
CUP(int, int) error
// Horizontal and Vertical Position (depends on PUM)
HVP(int, int) error
// Text Cursor Enable Mode
DECTCEM(bool) error
// Origin Mode
DECOM(bool) error
// 132 Column Mode
DECCOLM(bool) error
// Erase in Display
ED(int) error
// Erase in Line
EL(int) error
// Insert Line
IL(int) error
// Delete Line
DL(int) error
// Insert Character
ICH(int) error
// Delete Character
DCH(int) error
// Set Graphics Rendition
SGR([]int) error
// Pan Down
SU(int) error
// Pan Up
SD(int) error
// Device Attributes
DA([]string) error
// Set Top and Bottom Margins
DECSTBM(int, int) error
// Index
IND() error
// Reverse Index
RI() error
// Flush updates from previous commands
Flush() error
}
+24
View File
@@ -0,0 +1,24 @@
package ansiterm
type groundState struct {
baseState
}
func (gs groundState) Handle(b byte) (s state, e error) {
gs.parser.context.currentChar = b
nextState, err := gs.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case sliceContains(printables, b):
return gs, gs.parser.print()
case sliceContains(executors, b):
return gs, gs.parser.execute()
}
return gs, nil
}
+31
View File
@@ -0,0 +1,31 @@
package ansiterm
type oscStringState struct {
baseState
}
func (oscState oscStringState) Handle(b byte) (s state, e error) {
oscState.parser.logf("OscString::Handle %#x", b)
nextState, err := oscState.baseState.Handle(b)
if nextState != nil || err != nil {
return nextState, err
}
switch {
case isOscStringTerminator(b):
return oscState.parser.ground, nil
}
return oscState, nil
}
// See below for OSC string terminators for linux
// http://man7.org/linux/man-pages/man4/console_codes.4.html
func isOscStringTerminator(b byte) bool {
if b == ANSI_BEL || b == 0x5C {
return true
}
return false
}
+151
View File
@@ -0,0 +1,151 @@
package ansiterm
import (
"errors"
"log"
"os"
)
type AnsiParser struct {
currState state
eventHandler AnsiEventHandler
context *ansiContext
csiEntry state
csiParam state
dcsEntry state
escape state
escapeIntermediate state
error state
ground state
oscString state
stateMap []state
logf func(string, ...interface{})
}
type Option func(*AnsiParser)
func WithLogf(f func(string, ...interface{})) Option {
return func(ap *AnsiParser) {
ap.logf = f
}
}
func CreateParser(initialState string, evtHandler AnsiEventHandler, opts ...Option) *AnsiParser {
ap := &AnsiParser{
eventHandler: evtHandler,
context: &ansiContext{},
}
for _, o := range opts {
o(ap)
}
if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" {
logFile, _ := os.Create("ansiParser.log")
logger := log.New(logFile, "", log.LstdFlags)
if ap.logf != nil {
l := ap.logf
ap.logf = func(s string, v ...interface{}) {
l(s, v...)
logger.Printf(s, v...)
}
} else {
ap.logf = logger.Printf
}
}
if ap.logf == nil {
ap.logf = func(string, ...interface{}) {}
}
ap.csiEntry = csiEntryState{baseState{name: "CsiEntry", parser: ap}}
ap.csiParam = csiParamState{baseState{name: "CsiParam", parser: ap}}
ap.dcsEntry = dcsEntryState{baseState{name: "DcsEntry", parser: ap}}
ap.escape = escapeState{baseState{name: "Escape", parser: ap}}
ap.escapeIntermediate = escapeIntermediateState{baseState{name: "EscapeIntermediate", parser: ap}}
ap.error = errorState{baseState{name: "Error", parser: ap}}
ap.ground = groundState{baseState{name: "Ground", parser: ap}}
ap.oscString = oscStringState{baseState{name: "OscString", parser: ap}}
ap.stateMap = []state{
ap.csiEntry,
ap.csiParam,
ap.dcsEntry,
ap.escape,
ap.escapeIntermediate,
ap.error,
ap.ground,
ap.oscString,
}
ap.currState = getState(initialState, ap.stateMap)
ap.logf("CreateParser: parser %p", ap)
return ap
}
func getState(name string, states []state) state {
for _, el := range states {
if el.Name() == name {
return el
}
}
return nil
}
func (ap *AnsiParser) Parse(bytes []byte) (int, error) {
for i, b := range bytes {
if err := ap.handle(b); err != nil {
return i, err
}
}
return len(bytes), ap.eventHandler.Flush()
}
func (ap *AnsiParser) handle(b byte) error {
ap.context.currentChar = b
newState, err := ap.currState.Handle(b)
if err != nil {
return err
}
if newState == nil {
ap.logf("WARNING: newState is nil")
return errors.New("New state of 'nil' is invalid.")
}
if newState != ap.currState {
if err := ap.changeState(newState); err != nil {
return err
}
}
return nil
}
func (ap *AnsiParser) changeState(newState state) error {
ap.logf("ChangeState %s --> %s", ap.currState.Name(), newState.Name())
// Exit old state
if err := ap.currState.Exit(); err != nil {
ap.logf("Exit state '%s' failed with : '%v'", ap.currState.Name(), err)
return err
}
// Perform transition action
if err := ap.currState.Transition(newState); err != nil {
ap.logf("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err)
return err
}
// Enter new state
if err := newState.Enter(); err != nil {
ap.logf("Enter state '%s' failed with: '%v'", newState.Name(), err)
return err
}
ap.currState = newState
return nil
}
+99
View File
@@ -0,0 +1,99 @@
package ansiterm
import (
"strconv"
)
func parseParams(bytes []byte) ([]string, error) {
paramBuff := make([]byte, 0, 0)
params := []string{}
for _, v := range bytes {
if v == ';' {
if len(paramBuff) > 0 {
// Completed parameter, append it to the list
s := string(paramBuff)
params = append(params, s)
paramBuff = make([]byte, 0, 0)
}
} else {
paramBuff = append(paramBuff, v)
}
}
// Last parameter may not be terminated with ';'
if len(paramBuff) > 0 {
s := string(paramBuff)
params = append(params, s)
}
return params, nil
}
func parseCmd(context ansiContext) (string, error) {
return string(context.currentChar), nil
}
func getInt(params []string, dflt int) int {
i := getInts(params, 1, dflt)[0]
return i
}
func getInts(params []string, minCount int, dflt int) []int {
ints := []int{}
for _, v := range params {
i, _ := strconv.Atoi(v)
// Zero is mapped to the default value in VT100.
if i == 0 {
i = dflt
}
ints = append(ints, i)
}
if len(ints) < minCount {
remaining := minCount - len(ints)
for i := 0; i < remaining; i++ {
ints = append(ints, dflt)
}
}
return ints
}
func (ap *AnsiParser) modeDispatch(param string, set bool) error {
switch param {
case "?3":
return ap.eventHandler.DECCOLM(set)
case "?6":
return ap.eventHandler.DECOM(set)
case "?25":
return ap.eventHandler.DECTCEM(set)
}
return nil
}
func (ap *AnsiParser) hDispatch(params []string) error {
if len(params) == 1 {
return ap.modeDispatch(params[0], true)
}
return nil
}
func (ap *AnsiParser) lDispatch(params []string) error {
if len(params) == 1 {
return ap.modeDispatch(params[0], false)
}
return nil
}
func getEraseParam(params []string) int {
param := getInt(params, 0)
if param < 0 || 3 < param {
param = 0
}
return param
}
Loaded 100 of 3184 files, more files were not shown because too many files have changed in this diff. Show more