Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
242b8907bf | ||
|
|
9db22c3cd6 | ||
|
|
e57d79bd70 | ||
|
|
589ab4f01d | ||
|
|
01fe09065c | ||
|
|
83958561f7 | ||
|
|
cb73cf3f51 | ||
|
|
7a3e8e64b7 | ||
|
|
f7ce16cba8 | ||
|
|
6ac1fef01d | ||
|
|
5c19efae48 | ||
|
|
f7c0da3ae1 | ||
|
|
fe0c833cdf | ||
|
|
58342f7509 | ||
|
|
fad537cad7 | ||
|
|
6d5c2ad0fa | ||
|
|
b585e1f4cb | ||
|
|
7102bf6d9d | ||
|
|
77988012ed | ||
|
|
2989e3cccd | ||
|
|
5a47d1d13f | ||
|
|
d3795d4e74 | ||
|
|
bc97daf5c0 | ||
|
|
08e4c98172 | ||
|
|
ca254e066a | ||
|
|
aea92a3563 | ||
|
|
e9fa655e0e | ||
|
|
7d178ecee9 | ||
|
|
201321f390 | ||
|
|
3afc528c88 | ||
|
|
62c9522bf6 | ||
|
|
1b45e8662e | ||
|
|
3ce8aec039 | ||
|
|
32d44e09a1 | ||
|
|
4e37f5ec42 | ||
|
|
39f471e9d3 | ||
|
|
293abd953e | ||
|
|
fde7dcdde9 | ||
|
|
7075b5d9fd | ||
|
|
50b1aa0c74 | ||
|
|
7f48cc9239 | ||
|
|
8211afe5d5 | ||
|
|
e26dbf11e9 | ||
|
|
39ac1a0037 | ||
|
|
86f716c38c | ||
|
|
d5c7c4476b | ||
|
|
2a02883f07 | ||
|
|
f592e4b70a | ||
|
|
968855e0a4 | ||
|
|
bee031ad70 | ||
|
|
3378e09b6a | ||
|
|
ab37b4a6c4 | ||
|
|
dff5b6d3f1 | ||
|
|
c026579cec | ||
|
|
ef8b576742 | ||
|
|
69398a9d7e | ||
|
|
0a2beedad3 | ||
|
|
29d50bbf09 | ||
|
|
673fc73cd5 | ||
|
|
1185bed370 | ||
|
|
1176d44044 | ||
|
|
77649ab81b | ||
|
|
a51bc6164c | ||
|
|
dd935b7e9e | ||
|
|
67dbdd377c | ||
|
|
e5185e73ce | ||
|
|
17ada094f8 | ||
|
|
3e5593dc4f | ||
|
|
e647fe6331 | ||
|
|
a2679794b3 | ||
|
|
30f59953d2 | ||
|
|
acd16881d9 | ||
|
|
2aaa400d62 | ||
|
|
487ebfb514 | ||
|
|
fde7e88e66 | ||
|
|
9e4ce8c052 | ||
|
|
defa5cc3d3 | ||
|
|
dea2755161 | ||
|
|
cc8785fdcf | ||
|
|
7265c6ea03 | ||
|
|
0792f0717d | ||
|
|
851b58cca8 | ||
|
|
cb48ebc399 | ||
|
|
5278eb7ebc | ||
|
|
418c256689 | ||
|
|
4eeb846507 | ||
|
|
7529f5f9af | ||
|
|
8506e57482 | ||
|
|
b1d09305e4 | ||
|
|
07892bff7d | ||
|
|
50985dee90 | ||
|
|
60323d0960 | ||
|
|
a9cef6327f | ||
|
|
fdeb9010ba | ||
|
|
ee659d4033 | ||
|
|
8f9b2a36c0 | ||
|
|
6a83130685 | ||
|
|
77e6c6f886 | ||
|
|
8a5a15d5e9 | ||
|
|
bcc8c73f59 | ||
|
|
e22c6375c9 | ||
|
|
22ae0d200c | ||
|
|
7231bd00f8 | ||
|
|
590a151bde | ||
|
|
f5dfefef75 | ||
|
|
8e6cb5e24d | ||
|
|
ed629d420d | ||
|
|
fd568682c3 | ||
|
|
dbb9849b8c | ||
|
|
776af44782 | ||
|
|
69dacd0594 | ||
|
|
f17a2a00b1 | ||
|
|
1a64420e01 | ||
|
|
49b9a2c8df | ||
|
|
6c251417d5 | ||
|
|
8c0c38c8b3 | ||
|
|
29eee4e858 | ||
|
|
454c6f713d | ||
|
|
dd715e18ec | ||
|
|
f067c7d44e | ||
|
|
c1023eaf18 | ||
|
|
b091f59311 | ||
|
|
f98ad822e3 | ||
|
|
917c1ddbad | ||
|
|
d81d2f691d | ||
|
|
1ad118a38f | ||
|
|
9d1648cee7 | ||
|
|
929167366a | ||
|
|
353ccf5e6c | ||
|
|
cfc1fa4fae | ||
|
|
d881f47088 | ||
|
|
84013c4452 | ||
|
|
d9dc1a988c | ||
|
|
5fd14348aa | ||
|
|
57a250a20b | ||
|
|
81add75258 | ||
|
|
0145282a10 | ||
|
|
ae0cb5d483 | ||
|
|
e7eeecd762 | ||
|
|
7d308a4ac5 | ||
|
|
ebcb7f6108 | ||
|
|
e03c7b1b7c | ||
|
|
70a20b4d26 | ||
|
|
d143470d77 | ||
|
|
30d6639a98 | ||
|
|
92cfd3b4f9 | ||
|
|
7659ea21c7 | ||
|
|
4e740736e7 | ||
|
|
4f44d9e25a | ||
|
|
2ae2d52289 | ||
|
|
635191970f | ||
|
|
e370443a3f | ||
|
|
738ff2b65f | ||
|
|
cef8c908d0 | ||
|
|
edc0cdbc10 | ||
|
|
89e116961c | ||
|
|
71180f97e7 | ||
|
|
be8d9d88d8 | ||
|
|
35de3720e9 | ||
|
|
4a7e2054db | ||
|
|
fe75047988 | ||
|
|
f73fb5307f | ||
|
|
172597a68d | ||
|
|
f05169e457 | ||
|
|
b171497466 | ||
|
|
61375fb785 | ||
|
|
04cc4edf26 | ||
|
|
0101a12fba | ||
|
|
e094fb1547 | ||
|
|
e0a44a901a | ||
|
|
d4f9abeefd | ||
|
|
0338d4595b | ||
|
|
7328c3fe86 | ||
|
|
bf288ff6f5 | ||
|
|
ef4c4024bc | ||
|
|
6f24cca4b1 | ||
|
|
bf59a003bb | ||
|
|
2298a40327 | ||
|
|
61d22bb8b4 | ||
|
|
eb3bbff883 | ||
|
|
03c1379d1b | ||
|
|
1635093341 | ||
|
|
93dcc4550a | ||
|
|
ab55da47b6 | ||
|
|
17971a5a3a | ||
|
|
e0b90e8f02 | ||
|
|
ea014d1362 | ||
|
|
6ef302b8a1 | ||
|
|
1e7afe3bec | ||
|
|
48508044d6 | ||
|
|
a4ad66f1c8 | ||
|
|
0f85a959bb | ||
|
|
d9a8adda1d | ||
|
|
ef4a8648a9 | ||
|
|
8bb72aa175 | ||
|
|
fa05dcf6ee | ||
|
|
8affa26858 | ||
|
|
856cb48170 | ||
|
|
1239fca567 | ||
|
|
6592a9afc7 | ||
|
|
7ffd96f425 | ||
|
|
906d9a47b1 | ||
|
|
0dff07dc8b | ||
|
|
9261d17dbe | ||
|
|
7d8a18bd20 | ||
|
|
29cb13cba6 | ||
|
|
52f375ac1e | ||
|
|
73f184df7e | ||
|
|
bbd631f08f | ||
|
|
cf315fb6ba | ||
|
|
c03a2caac2 | ||
|
|
9c2bc0b825 | ||
|
|
fcda8985fb | ||
|
|
14082f9332 | ||
|
|
83401eb245 | ||
|
|
022a98d4a4 | ||
|
|
bb1cca713d | ||
|
|
1d80a8d676 | ||
|
|
aa9c032176 | ||
|
|
baa8f229f7 | ||
|
|
ab1b99a4d9 | ||
|
|
11ff1880e5 | ||
|
|
71bf040715 | ||
|
|
6a04c6c7bd | ||
|
|
8530528dd5 | ||
|
|
e1d8992ec5 | ||
|
|
b03311e99d | ||
|
|
0c1138ee60 | ||
|
|
8a45292e24 | ||
|
|
d1712e9589 | ||
|
|
ec21afb426 | ||
|
|
882bff292e | ||
|
|
f3843a6b18 | ||
|
|
597c6c111d | ||
|
|
ac4957e15c | ||
|
|
f3915592b2 | ||
|
|
8a292fd9f3 | ||
|
|
f34936d9be | ||
|
|
d4367457a9 | ||
|
|
b4a43f1b85 | ||
|
|
3674808244 | ||
|
|
44cc978a94 | ||
|
|
f441ec2644 | ||
|
|
0ae13a2650 | ||
|
|
ee6596e21c | ||
|
|
a63147beda | ||
|
|
cdcf5808db | ||
|
|
d501c78795 | ||
|
|
e61ddc59c4 | ||
|
|
46e4f047de | ||
|
|
9cff5f2c5e | ||
|
|
5f031453d3 | ||
|
|
028216021f | ||
|
|
d6544302d3 | ||
|
|
bc529c5d09 | ||
|
|
354d1ddd5c | ||
|
|
28bf4e6e95 | ||
|
|
33b7faa17d | ||
|
|
ac731c0ff7 | ||
|
|
fd34dda6c1 | ||
|
|
16fca913de | ||
|
|
f989a82eb0 | ||
|
|
d410ecd34c | ||
|
|
48569265e2 | ||
|
|
ad43acf843 | ||
|
|
71782a6aa5 | ||
|
|
6df24d1a40 | ||
|
|
e283b0a1ce | ||
|
|
fa15193011 | ||
|
|
dff14c19f8 | ||
|
|
6103595b9c | ||
|
|
01c436ae15 | ||
|
|
c3a2539445 | ||
|
|
664ba8735f | ||
|
|
937036aa64 | ||
|
|
47cdf2b755 | ||
|
|
ef270764f3 | ||
|
|
a6edd40de3 | ||
|
|
4219c2c2ab | ||
|
|
4785de1a34 | ||
|
|
cbec624cd8 | ||
|
|
e7bde18f71 | ||
|
|
e9eeaee520 | ||
|
|
d5ec9fb38d | ||
|
|
233a776c41 | ||
|
|
d5f1c233bf | ||
|
|
af732d0495 | ||
|
|
04a87f951b | ||
|
|
9cc98919fa | ||
|
|
e42600632f | ||
|
|
93aa3fed16 | ||
|
|
49e76d1486 | ||
|
|
965bed7c4f | ||
|
|
317c6ba04c | ||
|
|
41d127934b | ||
|
|
915638d17a | ||
|
|
f73355abcc | ||
|
|
8f9da95e5e | ||
|
|
0935aa9221 | ||
|
|
aaa073afed | ||
|
|
9b606fb823 | ||
|
|
15d91c558b | ||
|
|
ffc5a78139 | ||
|
|
f8693074a3 | ||
|
|
1629f4f3f0 | ||
|
|
10baab38d3 | ||
|
|
e458c11e66 | ||
|
|
a07e3301b1 | ||
|
|
2b4d13c5b1 | ||
|
|
755e42836c | ||
|
|
ad743b0e96 | ||
|
|
0544901357 | ||
|
|
bc94633180 | ||
|
|
16415404c7 | ||
|
|
702d12d801 | ||
|
|
46eb01604c | ||
|
|
af36682a7e | ||
|
|
03d9e3db69 | ||
|
|
b03b3fccc7 | ||
|
|
9414e2979d | ||
|
|
865664425b | ||
|
|
b060cf30f4 | ||
|
|
5352ed837a | ||
|
|
d4d54a1ff5 | ||
|
|
ea82a6715f | ||
|
|
d356e32bdf | ||
|
|
6193d22d2e | ||
|
|
50c9568073 | ||
|
|
9d05ed41b2 | ||
|
|
e3f7e0a23a | ||
|
|
da2f0803ce | ||
|
|
6dde5e5ee9 | ||
|
|
494df21bdf | ||
|
|
e44bbbec63 | ||
|
|
c19f49a284 | ||
|
|
d3f2c110e4 | ||
|
|
09628178e4 | ||
|
|
5630a01a8c | ||
|
|
40b3a49c90 | ||
|
|
431b87b94a | ||
|
|
828c3d21d2 | ||
|
|
f0e75481d5 | ||
|
|
8bb7e1cf24 | ||
|
|
e117a6bd04 | ||
|
|
ac44fa63c8 | ||
|
|
bb639dbf32 | ||
|
|
64c890005a | ||
|
|
19088ba8bc | ||
|
|
ec5f7033a4 | ||
|
|
eb60f68df2 | ||
|
|
d72e671d8b | ||
|
|
3462357481 | ||
|
|
2b4da35b1c | ||
|
|
86d68c9875 | ||
|
|
cfff8ee8be | ||
|
|
af10cef5f0 | ||
|
|
a1a42736ed | ||
|
|
b533a11bee | ||
|
|
afed9e9646 | ||
|
|
74b477ac82 | ||
|
|
bf74c8f108 | ||
|
|
04c7f5283e | ||
|
|
d1d53ba45c | ||
|
|
dfadb8f189 | ||
|
|
5335c133a3 | ||
|
|
8fcb5daedf | ||
|
|
ede70a78d6 | ||
|
|
23056cbb45 | ||
|
|
29e9e70ac9 | ||
|
|
d0b711513e | ||
|
|
7dbee74e85 | ||
|
|
4e7a3b1805 | ||
|
|
3c7b244f18 | ||
|
|
e50f5c52c5 | ||
|
|
baf91658d6 | ||
|
|
fda035d8d6 | ||
|
|
a4a53e1a11 | ||
|
|
aad3e52d72 | ||
|
|
cb778a9af5 | ||
|
|
3af9732c84 | ||
|
|
00e754ed9f | ||
|
|
d893f44833 | ||
|
|
4839f41809 | ||
|
|
c801409c94 | ||
|
|
1c4a01d52c | ||
|
|
880b0a883a | ||
|
|
4b77662e3b | ||
|
|
a5ff7ab8df | ||
|
|
04a6e6d13b | ||
|
|
5c2e878c64 | ||
|
|
f9bada5af8 | ||
|
|
80d66f8f68 | ||
|
|
98b35b87a9 | ||
|
|
b1d6605e8f | ||
|
|
2cd3bde348 | ||
|
|
f2454775b9 | ||
|
|
a53d1e00ee | ||
|
|
523a964ac8 | ||
|
|
f8cf7a1054 | ||
|
|
4d8a40c0b5 | ||
|
|
9754da15d6 | ||
|
|
0458d32089 | ||
|
|
ee4e8eb994 | ||
|
|
b8d619dde7 | ||
|
|
00c86fed42 | ||
|
|
6d5af85eee | ||
|
|
1ae2d33714 | ||
|
|
71d550260f | ||
|
|
1092bae05c | ||
|
|
e611f04f41 | ||
|
|
3492cd93d0 | ||
|
|
947ae11fee | ||
|
|
cdd6ed548c | ||
|
|
fc940d0017 | ||
|
|
b38a55ca7c | ||
|
|
8e739d5240 | ||
|
|
71f8bb7e9a | ||
|
|
ebfaf11f4a | ||
|
|
4a6cec3ecc | ||
|
|
1abce148a3 | ||
|
|
e0cc7f17b2 | ||
|
|
264e5c9979 | ||
|
|
a6a1ce5c9e | ||
|
|
6ac7ab162f | ||
|
|
5e660311d2 | ||
|
|
4a49ff3083 | ||
|
|
7f92c8c4c6 | ||
|
|
85ac8177ad | ||
|
|
03bd1fb91d | ||
|
|
eb5b16614c | ||
|
|
a5873a3970 | ||
|
|
e48ca80dee | ||
|
|
e7c9e22350 | ||
|
|
b8e7d83f19 | ||
|
|
4c304c0013 | ||
|
|
7249350757 | ||
|
|
50a8dc1383 | ||
|
|
71491138aa | ||
|
|
ca87f8ada3 | ||
|
|
835b3f3d67 | ||
|
|
263dffeedc | ||
|
|
e1063944f6 | ||
|
|
2380018457 | ||
|
|
a9db631836 | ||
|
|
b45f138ff1 | ||
|
|
b239694d34 | ||
|
|
0c2774f367 | ||
|
|
ea4647bc74 | ||
|
|
6ec21d3a58 | ||
|
|
44a4a56024 | ||
|
|
0aac614c08 | ||
|
|
2ea027d3a8 | ||
|
|
cd4cbed5a0 | ||
|
|
69b688b357 | ||
|
|
7a81bc2ddc | ||
|
|
cdeae04238 | ||
|
|
6df2782d73 | ||
|
|
3fc7efa536 | ||
|
|
a556deec78 | ||
|
|
71e43c2023 | ||
|
|
370f258c3a | ||
|
|
9b32bc2ab3 | ||
|
|
fd5e1970e1 | ||
|
|
a1de7f3eef | ||
|
|
06a63db4cf | ||
|
|
c720ebec02 | ||
|
|
6a528409ab | ||
|
|
c69e140e9e | ||
|
|
8ed0c13645 | ||
|
|
b635f9a994 | ||
|
|
ac4246e1f5 | ||
|
|
06a325a05f | ||
|
|
68e9bbe350 | ||
|
|
3bd9e369cd | ||
|
|
3d12e8088e | ||
|
|
aee35b34fe | ||
|
|
c06ccb3b0d | ||
|
|
268bb656fc | ||
|
|
57357a728c | ||
|
|
a335c419ec | ||
|
|
e263295720 | ||
|
|
0c854f2144 | ||
|
|
bdf4debdfd | ||
|
|
f557835ad0 | ||
|
|
17a7391086 | ||
|
|
e65293eb50 | ||
|
|
48c9547943 | ||
|
|
caa6c6c931 | ||
|
|
11f56031f1 | ||
|
|
2e825242cd | ||
|
|
ebe4c5b8b7 | ||
|
|
027064cbdb | ||
|
|
9fddb827f9 | ||
|
|
3162009325 | ||
|
|
060f6392c7 | ||
|
|
8c8c6db7dd | ||
|
|
cd94dd0088 | ||
|
|
afc6f49e80 | ||
|
|
c509b6e5bb | ||
|
|
a466f55d59 | ||
|
|
c79b6a8bd7 | ||
|
|
c2083cbce0 | ||
|
|
4157d9298d | ||
|
|
f686408808 | ||
|
|
432e10e19f | ||
|
|
b8c57ef10f | ||
|
|
601bda3176 | ||
|
|
d9bde790c6 | ||
|
|
5cc0826ca0 | ||
|
|
96f2eb3ecf | ||
|
|
e703f2f3f2 | ||
|
|
1c0ffbc177 | ||
|
|
f544da9e19 | ||
|
|
c0592ba1b3 | ||
|
|
0cbf5ca130 | ||
|
|
472eab2acd | ||
|
|
7a5d7fca6c | ||
|
|
081b95dfd0 | ||
|
|
faf5b14d8b | ||
|
|
38958049f4 | ||
|
|
589b45dace | ||
|
|
7ebf3d7a88 | ||
|
|
3273d13666 | ||
|
|
5fdbbf3722 | ||
|
|
14350ff463 | ||
|
|
0b49c961a7 | ||
|
|
39b80f26ed | ||
|
|
74b20265e4 | ||
|
|
358fddb958 | ||
|
|
5c8f6175b5 | ||
|
|
e8d823d66b | ||
|
|
83ce5875ee | ||
|
|
ed83efbceb | ||
|
|
223116c4bf | ||
|
|
8056a098a8 | ||
|
|
15a2cba7f8 | ||
|
|
b37bb02a8b | ||
|
|
0c14e5135a | ||
|
|
f5a30c790e | ||
|
|
b06ddeb962 | ||
|
|
34415c3c17 | ||
|
|
6387fa8bc9 | ||
|
|
f6e6926171 | ||
|
|
25e7868cd5 | ||
|
|
385e6ff9b8 | ||
|
|
3755906689 | ||
|
|
63cbb34dec | ||
|
|
8ec6b699b1 | ||
|
|
369852a3a7 | ||
|
|
c82be57639 | ||
|
|
092c3a17d1 | ||
|
|
fd0a7efa23 | ||
|
|
551f164ec3 | ||
|
|
acdd739deb | ||
|
|
45e3016c6a | ||
|
|
88bf64722f | ||
|
|
c1483dbf1f | ||
|
|
d9c998b5c4 | ||
|
|
170e19c283 | ||
|
|
bce07b7491 | ||
|
|
8b5741506d | ||
|
|
7c47a8ffb7 | ||
|
|
791ab55875 | ||
|
|
e642f366f5 | ||
|
|
bd1a9d020c | ||
|
|
d794b203f7 | ||
|
|
7ec68119a6 | ||
|
|
71fd3b6b2b | ||
|
|
93c60395f6 | ||
|
|
af27aa04f2 | ||
|
|
0861d05522 | ||
|
|
89221f117b | ||
|
|
7f5c37ef1e | ||
|
|
05baa566c6 | ||
|
|
f8ac27a6cc | ||
|
|
e5c23bf9f5 | ||
|
|
954cd0b86a | ||
|
|
0635ed1854 | ||
|
|
9dedeadf15 | ||
|
|
4424e877f0 | ||
|
|
6561272f8d | ||
|
|
aa54d332c2 | ||
|
|
881e795db5 | ||
|
|
4216231ec7 | ||
|
|
e5aeb4bd05 | ||
|
|
333d5f7511 | ||
|
|
7be7945b29 | ||
|
|
566ed64607 | ||
|
|
83c358c8d2 | ||
|
|
afe882cd94 | ||
|
|
5b8a359475 | ||
|
|
ae95248499 | ||
|
|
df0af203f8 | ||
|
|
53b8c804f2 | ||
|
|
7d0aaea6e8 | ||
|
|
45a66ac517 | ||
|
|
087d948941 | ||
|
|
5b2a4ff614 | ||
|
|
72e92db96e | ||
|
|
768414534f | ||
|
|
ceb5f8c714 | ||
|
|
cc3359dfe5 | ||
|
|
f1ca4854b8 | ||
|
|
6319860230 | ||
|
|
58caf38f07 | ||
|
|
cb4b5fd143 | ||
|
|
cda6785f1d | ||
|
|
cb030be4ad | ||
|
|
7165414fc3 | ||
|
|
b464ce86f7 | ||
|
|
d72d3af36e | ||
|
|
80b3514e24 | ||
|
|
f07ed7153b | ||
|
|
e33f56f9b0 | ||
|
|
6c46af25cc | ||
|
|
5ad227a8b3 | ||
|
|
67fe1e5daf | ||
|
|
99b475056a | ||
|
|
804f4ad971 | ||
|
|
24d9a00f47 | ||
|
|
b00e7d088e | ||
|
|
c12e909882 | ||
|
|
6356fbc882 | ||
|
|
ca7220b775 | ||
|
|
619a02eefb | ||
|
|
85c1a31e5c | ||
|
|
f386548890 | ||
|
|
f86ade6568 | ||
|
|
81fa4059df | ||
|
|
c80d87554a | ||
|
|
e77bf60d34 | ||
|
|
61cb5535db | ||
|
|
63314beb2a | ||
|
|
2d0be34a93 | ||
|
|
0abfdf4efb | ||
|
|
b014de3f08 | ||
|
|
56c275a9bf | ||
|
|
ddd039761d | ||
|
|
f4798fadf8 | ||
|
|
adda1c459c | ||
|
|
979df93bff | ||
|
|
27e7ec08d4 | ||
|
|
c6a777c4b0 | ||
|
|
7182ec0463 | ||
|
|
425f17aab1 | ||
|
|
3494cc39c1 | ||
|
|
2face0f318 | ||
|
|
bb203a2d18 | ||
|
|
95b42a532a | ||
|
|
08e2c5c14e | ||
|
|
a4f3e763bb | ||
|
|
06feefe80f | ||
|
|
3dd7e13cc0 | ||
|
|
fdf3afa935 | ||
|
|
8bb8f70bb6 | ||
|
|
c71c630cf6 | ||
|
|
179b6f7cf9 | ||
|
|
1fe5c65fd4 | ||
|
|
e1edce84d9 | ||
|
|
73a393823a | ||
|
|
b32da8d69f | ||
|
|
b993fd67ab | ||
|
|
e9770740d8 | ||
|
|
7abed164eb | ||
|
|
7a1bd27a58 | ||
|
|
aca34191da | ||
|
|
df5508c0f3 | ||
|
|
e63fd0b38f | ||
|
|
47878cf5b6 | ||
|
|
f0ea18171b | ||
|
|
aa3f40ec8d | ||
|
|
bf8c8833e5 | ||
|
|
8569cbd4a3 | ||
|
|
adcb1018df | ||
|
|
f4b9a9b66c | ||
|
|
52d911c8c1 | ||
|
|
d1314dd248 | ||
|
|
5db7a2bae1 | ||
|
|
71540e6c6d | ||
|
|
3c45ba09f9 | ||
|
|
cf87a84bd9 | ||
|
|
c77b2e8fb3 | ||
|
|
37fd54f20b | ||
|
|
fb9b4ddec0 | ||
|
|
7670fed72c | ||
|
|
de2a70861c | ||
|
|
9fc7be83c8 | ||
|
|
c260536429 | ||
|
|
59b3a3c8c5 | ||
|
|
49f2e208c3 | ||
|
|
2266868648 | ||
|
|
d1bba977f3 | ||
|
|
e2bee6e8c6 | ||
|
|
6062627582 | ||
|
|
efee8e73c6 | ||
|
|
37f820a168 | ||
|
|
c41ca708f3 | ||
|
|
73d7812dc5 | ||
|
|
6a410a7749 | ||
|
|
843a993600 | ||
|
|
0fc59a831c | ||
|
|
7ffbeead8d | ||
|
|
9a17439e2f | ||
|
|
ab74d7b438 | ||
|
|
d4de48aeee | ||
|
|
eaa4bd1a6c | ||
|
|
c380a6f4ee | ||
|
|
faef371a8c | ||
|
|
39ba706a9b | ||
|
|
aa4980bbda | ||
|
|
7f5bf67d33 | ||
|
|
e7e27796e4 | ||
|
|
2491f49243 | ||
|
|
c25281e827 | ||
|
|
a4cd4d5a2f | ||
|
|
c6d1adf220 | ||
|
|
5dd7590e5c | ||
|
|
e092a517aa | ||
|
|
9b6154cf63 | ||
|
|
1e4d86775f | ||
|
|
2728bc0f9d | ||
|
|
dac2124cd1 | ||
|
|
37f5a17953 | ||
|
|
f9dd763774 | ||
|
|
4b758dd130 | ||
|
|
5e03c0fe25 | ||
|
|
166d4afad8 | ||
|
|
153ed601be | ||
|
|
cbf4a0facd | ||
|
|
a1c3cba363 | ||
|
|
1d91c1a23b | ||
|
|
f50fbc002e | ||
|
|
dbc8b4fe1a | ||
|
|
b1fa417381 | ||
|
|
912e8fe119 | ||
|
|
e2294482a6 | ||
|
|
b4fda4c98e | ||
|
|
d9da08792f | ||
|
|
bf040105da | ||
|
|
e8ca085758 | ||
|
|
7300c33793 | ||
|
|
f5cd7b5073 | ||
|
|
f02b78ed73 | ||
|
|
b3379df53d | ||
|
|
c9525532ea | ||
|
|
654a811ef0 | ||
|
|
cda1084777 | ||
|
|
fd257f43d1 | ||
|
|
8ff814d976 | ||
|
|
7c819e7f5c | ||
|
|
b417696c66 | ||
|
|
f0109c1920 | ||
|
|
6dd109a5bb | ||
|
|
2702b85ee5 | ||
|
|
8717fd15ed | ||
|
|
161446d55d | ||
|
|
ac14b19db8 | ||
|
|
e3ef473cd3 | ||
|
|
4fc2835d92 | ||
|
|
85023e0a76 | ||
|
|
0257274aa8 | ||
|
|
9aaa7f19fd | ||
|
|
9f8b94645d | ||
|
|
c458deaf80 | ||
|
|
d59c170cd5 | ||
|
|
f11d91badf | ||
|
|
c63619bf7d | ||
|
|
d9d1956e0c | ||
|
|
f695b3fd17 | ||
|
|
ede00047db | ||
|
|
52377df340 | ||
|
|
424d331a32 | ||
|
|
fbbbb924a3 | ||
|
|
caeff127c9 | ||
|
|
56145ba518 | ||
|
|
885b6195f6 | ||
|
|
df3ccd362a | ||
|
|
5ed18994cd | ||
|
|
4dca07c9ff | ||
|
|
b16de0c71e | ||
|
|
a131703b2d | ||
|
|
78672db440 | ||
|
|
5b4906f822 | ||
|
|
ee9f8eec1c | ||
|
|
dc0e25a420 | ||
|
|
85cea672d3 | ||
|
|
ff81ecfd41 | ||
|
|
ef5f80a05a | ||
|
|
d551b6b0a8 | ||
|
|
1ec2c0fb3f | ||
|
|
d1e2aa6d36 | ||
|
|
64dc7a1f69 | ||
|
|
bc79a9187f | ||
|
|
02d9c090ed | ||
|
|
671ed724d5 | ||
|
|
f37e742536 | ||
|
|
6c7f860418 | ||
|
|
79d78ae97b | ||
|
|
4ff574744e | ||
|
|
7c3e937ae8 | ||
|
|
178438c470 | ||
|
|
632694472c | ||
|
|
14e1145972 | ||
|
|
8df1aa48a5 | ||
|
|
16b75f899e | ||
|
|
c3abe4f7b2 | ||
|
|
f800813031 | ||
|
|
f7bddab4cb | ||
|
|
c434345960 | ||
|
|
f9157acced | ||
|
|
e49bbe176d | ||
|
|
0e694ed6b4 | ||
|
|
9dfd7004cc | ||
|
|
cf6d0c8126 | ||
|
|
3963d4453a | ||
|
|
99b0b949e8 | ||
|
|
ce5d2a6dd0 | ||
|
|
801283267a | ||
|
|
a2e301c648 | ||
|
|
2249c7c648 | ||
|
|
0425d5bf6a | ||
|
|
de9597b6fa | ||
|
|
2fa576f3c0 | ||
|
|
fa29a4b4af | ||
|
|
d698aefbfc | ||
|
|
db1ac2a343 | ||
|
|
a3b6380296 | ||
|
|
469d456c8b | ||
|
|
438efcccda | ||
|
|
daa369b86c | ||
|
|
c3a2ea3dd8 | ||
|
|
c54f2959ad | ||
|
|
ac0f284c5f | ||
|
|
bd06553ce0 | ||
|
|
158fa9d8e5 | ||
|
|
d234ca6ab8 | ||
|
|
8d71467802 | ||
|
|
8c5cbc738a | ||
|
|
01e3d1d81b | ||
|
|
5e297c366e | ||
|
|
39b0d2a0cb | ||
|
|
260b416cd7 | ||
|
|
ad5845b67f | ||
|
|
b426973679 | ||
|
|
7fcd837277 | ||
|
|
5993b3e846 | ||
|
|
cf7ea357de | ||
|
|
4e48b956d9 | ||
|
|
5b5d9d833a | ||
|
|
e16a4aa97a | ||
|
|
12fa345d10 | ||
|
|
d2ea7e046d | ||
|
|
1b50285609 | ||
|
|
658c910862 | ||
|
|
e1ab6a9035 | ||
|
|
8c8709e527 | ||
|
|
d1e004dedc | ||
|
|
8a827bebc8 | ||
|
|
4fc232be61 | ||
|
|
df2868fcdd | ||
|
|
32ba82efaf | ||
|
|
b1aa52b3ed | ||
|
|
11c368531a | ||
|
|
e389c8bb06 | ||
|
|
608da90dc7 | ||
|
|
d3ddb71557 | ||
|
|
fbb6e58b4f | ||
|
|
34b95ee36f | ||
|
|
1283ba4f61 | ||
|
|
a0982cb5e5 | ||
|
|
0314a86e03 | ||
|
|
d9d038ae35 | ||
|
|
87b61419f3 | ||
|
|
5fb6df5219 | ||
|
|
b6647741b5 | ||
|
|
0b4f9dbcf9 | ||
|
|
9644ed3f29 | ||
|
|
1d52615452 | ||
|
|
327a6f6d61 | ||
|
|
7aee9ebf84 | ||
|
|
8f6087cfec | ||
|
|
6022ad467a | ||
|
|
05652c0a3c | ||
|
|
503546d2a9 | ||
|
|
3252a25e00 | ||
|
|
6847a3551d | ||
|
|
70bd0ade45 | ||
|
|
9dd1fbba39 | ||
|
|
8405e0759a | ||
|
|
bcc9a85e29 | ||
|
|
be3b0aa976 | ||
|
|
5ac8cc74d3 | ||
|
|
68e5ed575f | ||
|
|
f6966548eb | ||
|
|
fb3ed9c1a6 | ||
|
|
61fc5d56fb | ||
|
|
e96b68f0aa | ||
|
|
bd23c79823 | ||
|
|
393fa3a333 | ||
|
|
27bdd3b4ad | ||
|
|
14bc1c987e | ||
|
|
fd7fcc762e | ||
|
|
03afb54cdd | ||
|
|
50e114863d | ||
|
|
9ce229ba3a | ||
|
|
e6f031a3c9 | ||
|
|
8b432a4d3e | ||
|
|
818f3e284b | ||
|
|
560f38330e | ||
|
|
5771ba15dd | ||
|
|
9243bc2b0a | ||
|
|
df5d6972e1 | ||
|
|
380cb9b0fc | ||
|
|
bd3b3791c6 | ||
|
|
77ced2ac55 | ||
|
|
7decce690a | ||
|
|
65ecac6f1a | ||
|
|
14a3898371 | ||
|
|
c012650476 | ||
|
|
d5fd62393f | ||
|
|
789a6192f2 | ||
|
|
298fdc3d58 | ||
|
|
019a065d03 | ||
|
|
de1304d424 | ||
|
|
ef915ca58f | ||
|
|
72e134605c | ||
|
|
7cc2a1fe38 | ||
|
|
5de090999c | ||
|
|
8ea5d999c2 | ||
|
|
671dddf761 | ||
|
|
f4a117a8fc | ||
|
|
8fdbaf5745 | ||
|
|
0e655a2bbd | ||
|
|
15054393fb | ||
|
|
f56d001703 | ||
|
|
6ac248bfbf | ||
|
|
a5739e8753 | ||
|
|
79f2324d2b | ||
|
|
c1a3ea925d | ||
|
|
062c7a6fdc | ||
|
|
6cc7f37dbb | ||
|
|
76601fed09 | ||
|
|
88a044f7f9 | ||
|
|
03fcbbceb9 | ||
|
|
cec88f154e | ||
|
|
849edb1768 | ||
|
|
981b78f680 | ||
|
|
ee75d91756 | ||
|
|
d2450aae1c | ||
|
|
b4f35f1d38 | ||
|
|
9326775718 | ||
|
|
76f9afb59c | ||
|
|
192fbe3a53 | ||
|
|
427b6d2902 | ||
|
|
2f5a3f4513 | ||
|
|
c984ec1e86 | ||
|
|
061d2b123f | ||
|
|
c1d98ba59c | ||
|
|
a1823af745 | ||
|
|
4a01d23025 | ||
|
|
27632b118f |
No files matched your search
+9
-2
@@ -43,12 +43,16 @@ prog/weavewait/weavewait
|
||||
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/weave-npc/weave-npc
|
||||
prog/plugin/plugin
|
||||
testing/cover/cover
|
||||
testing/runner/runner
|
||||
releases
|
||||
.*.uptodate
|
||||
weave.tar.gz
|
||||
weave*.tar.gz
|
||||
prog/weaveexec/weave
|
||||
prog/weaveexec/weavehosts
|
||||
prog/weaveexec/weaveproxy
|
||||
@@ -57,9 +61,12 @@ prog/weaveexec/weavewait_noop
|
||||
prog/weaveexec/weavewait_nomcast
|
||||
prog/weaveexec/sigproxy
|
||||
prog/weaveexec/weaveutil
|
||||
prog/weaveexec/docker*.tgz
|
||||
prog/weaveexec/docker*
|
||||
Vagrantfile.local
|
||||
test/tls/tls
|
||||
test/tls/*.pem
|
||||
test/coverage
|
||||
test/coverage.*
|
||||
*qemu-*-static
|
||||
terraform.tfstate
|
||||
terraform.tfstate.backup
|
||||
+57
-3
@@ -4,9 +4,6 @@
|
||||
[submodule "vendor/github.com/vishvananda/netlink"]
|
||||
path = vendor/github.com/vishvananda/netlink
|
||||
url = https://github.com/vishvananda/netlink.git
|
||||
[submodule "vendor/github.com/davecheney/profile"]
|
||||
path = vendor/github.com/davecheney/profile
|
||||
url = https://github.com/davecheney/profile.git
|
||||
[submodule "vendor/github.com/gorilla/mux"]
|
||||
path = vendor/github.com/gorilla/mux
|
||||
url = https://github.com/gorilla/mux.git
|
||||
@@ -70,3 +67,60 @@
|
||||
[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
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# IMPORTANT: Please only use GLOBs to ignore files and directories.
|
||||
|
||||
# Do not lint the below bash scripts in Weave Net, in order to
|
||||
# avoid breaking the build on make lint:
|
||||
bin/circle-deploy-issues
|
||||
bin/circle-deploy-master
|
||||
bin/circle-teardown-pre
|
||||
bin/circle-test-teardown
|
||||
bin/circle-test-unit
|
||||
bin/install-wordepress
|
||||
bin/multiweave
|
||||
bin/publish-site
|
||||
bin/release
|
||||
build/build.sh
|
||||
prog/weave-kube/launch.sh
|
||||
prog/weaveexec/symlink
|
||||
test/*_test.sh
|
||||
test/600_proxy_docker_py.sh
|
||||
test/assert.sh
|
||||
test/config.sh
|
||||
test/gce.sh
|
||||
test/gen_coverage_reports.sh
|
||||
test/run_all.sh
|
||||
test/sanity_check.sh
|
||||
test/setup.sh
|
||||
vendor/*.sh
|
||||
weave
|
||||
+214
@@ -1,3 +1,217 @@
|
||||
## Release 1.9.1
|
||||
|
||||
# Bug fixes and minor improvements
|
||||
* Fix a race condition when the Weave Net container is restarted
|
||||
which could allow a new container to be allocated the same IP
|
||||
address as an existing one #2784,#2787
|
||||
* Handle the message type received when a pod has been deleted during
|
||||
Kubernetes api-server fail-over #2772,#2773
|
||||
* Make weave-kube work with `dockerd --iptables=false` #2726
|
||||
* Ensure we have the right kernel modules loaded for Network Policy in weave-kube #2819
|
||||
* Reference-count addresses in Network Policy Controller, to avoid
|
||||
errors when updates come in an unexpected order #2792,#2795
|
||||
* Allow the soft connection limit to be raised in weave-kube, so
|
||||
larger clusters can be created #2781
|
||||
* WeaveDNS was incorrectly case-sensitive for reverse DNS lookups #2817,#2818
|
||||
|
||||
# Build and Testing
|
||||
* Scripts to create VMs to run automated tests were rewritten to use
|
||||
Terraform and Ansible, to make it much easier to test with different
|
||||
versions of components such as Docker and Kubernetes #2647,#2694,#2775,#2796
|
||||
* Upgrade to latest Weaveworks common build-tools #2780
|
||||
* Improve encryption tests #2793
|
||||
* Update vishvananda/netlink library to bring in changes we had previously forked #2790
|
||||
* Slight change to the build container to avoid permission errors and slow builds #2761,#2802
|
||||
|
||||
## Release 1.9.0
|
||||
|
||||
Highlights:
|
||||
|
||||
* Encryption is now available for Fast Datapath connections, which
|
||||
greatly improves the performance. #1644,#2687
|
||||
* We now build images for Intel/AMD 64-bit, ARM and ARM 64-bit. #2713
|
||||
* Weave Net Docker images are now labelled with description, vendor, etc. #2712
|
||||
* `weave status connections` now shows the MTU #2389,#2663
|
||||
* CNI plugin is now a stand-alone binary that does not depend on Docker #2594,#2662
|
||||
* Embedded docker client updated to version 1.10.3 #2395
|
||||
|
||||
**NOTE:** The move to multi-architecture required that we update the
|
||||
embedded Docker client, and this has the effect that this release of
|
||||
Weave Net will not work with Docker installations older than
|
||||
1.10.
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/issues?q=milestone%3A1.9.0).
|
||||
|
||||
## Release 1.8.2
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fixed a bug where looping flows were installed which caused high CPU
|
||||
usage #2650, #2674
|
||||
* Fixed a bug where Kubernetes master could not contact pods #2673, #2683
|
||||
* Fixed a bug where weave-kube was crashing in a loop due to invalid
|
||||
Weave bridge state #2657
|
||||
* Fixed a bug where iptables NAT rules were not appended due to
|
||||
"temporary unavailable" iptables error #2679
|
||||
* Added a detection of enabling the hairpin mode on the Weave bridge port
|
||||
which caused installation of looping flows #2674
|
||||
* Added a detection of overlaps between Weave and the host IP address
|
||||
ranges when launching weave-kube #2669, #2672
|
||||
* Added logging of connections blocked by weave-npc #2546, #2573
|
||||
|
||||
More details in the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.8.2).
|
||||
|
||||
## Release 1.8.1
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fixed weave-npc crash from Succeeded/Failed pods #2632,#2658
|
||||
* Fixed occasional failure to create Weave bridge on node reboot
|
||||
#2617,#2637
|
||||
* Fixed a bug where weave-kube would fail to install when run with
|
||||
unrelease snapshot builds #2642
|
||||
* Improved conformance to CNI spec by not releasing IP addresses when
|
||||
a container dies #2643
|
||||
* Improved troubleshooting of install failure by creating CNI config
|
||||
after Weave Net is up #2570
|
||||
* "up to date" shown even when the version check was blocked by
|
||||
firewall #2537,#2565,#2645
|
||||
* "Unable to claim" message on re-launching Weave after using CNI
|
||||
#2548,#2577
|
||||
* Eliminated spurious IP reclaim operations when IPAM was disabled
|
||||
#2567,#2644
|
||||
* Include `jq` tool in our build VM configuration #2656
|
||||
|
||||
## Release 1.8.0
|
||||
|
||||
Highlights:
|
||||
|
||||
* Exposed network policy controller Prometheus metrics
|
||||
weaveworks/weave-npc#23, #2595, #2549
|
||||
* Exposed router Prometheus metrics #2535, #2547, #2523, #2579, #2578,
|
||||
#2568, #2560, #2561
|
||||
|
||||
More details in the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.8.0).
|
||||
|
||||
## Release 1.7.2
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fixed an error where the Docker plugin could fail to attach a
|
||||
container with a `bridge "weave" not present` error #2540/#2541
|
||||
* Fixed `cannot connect to itself` panic on weave launch #2527/#2543
|
||||
* Fixed inferred initial peer count when target peers includes self
|
||||
#2481/#2543
|
||||
* Fixed compilation on Raspberry Pi #2506/#2538
|
||||
|
||||
## Release 1.7.1
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Added utility to recover container addresses without asking Docker #2531
|
||||
* Improved CI #2274
|
||||
|
||||
## Release 1.7.0
|
||||
|
||||
Highlights:
|
||||
|
||||
* weave-kube - Deploy Weave Net to Kubernetes with a single command
|
||||
* weave-npc - Kubernetes network policy controller implementation
|
||||
|
||||
More details in the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.7.0).
|
||||
|
||||
## Release 1.6.2
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fixed hang after stopping and restarting on Docker 1.12 #2469/#2502
|
||||
* Avoid an error on Google container images by checking for tx offload support #2504
|
||||
* Fixed an issue where the supplied peer list could be ignored when restarting after failure #2503/#2509
|
||||
* Check for empty peer name on launch #2495/#2501
|
||||
|
||||
More details in the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.6.2).
|
||||
|
||||
## Release 1.6.1
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* `weave ps` was occasionally failing to list allocated addresses of containers #2388/#2418/#2445
|
||||
* `weave launch[-router]` on 4.2 kernel would appear to succeed even if the fast datapath VXLAN UDP port was in use by a different process #2375/#2474
|
||||
* Launching the proxy would fail when the Docker daemon could not be detected #2457/#2424
|
||||
* The CNI plugin did not work with Apache Mesos #2394/#2442
|
||||
* Router stopped working after a restart in the AWSVPC mode #2381/#2409
|
||||
* Router crashed when the Docker API endpoint parameter was explicitly set to empty #2421/#2467
|
||||
* The CNI plugin did not work on recent versions of Docker for Mac #2434/#2442
|
||||
* The CNI plugin assigns an IP to the bridge if necessary, which avoids failures if `weave expose` has not run yet #2471
|
||||
* Distinguish peer name collisions from attempts to connect to self in logs #2460
|
||||
* Improve host clock skew detection message #2174
|
||||
* Improve the error message returned when executing `weave launch-plugin` without the router running #2293/#2416
|
||||
* The `create-bridge` subcommand was not enabled in the fast datapath mode #2464/2466
|
||||
* Allow users to omit `weave setup[-cni]` by initializing the CNI plugin on the `launch[-router]` subcommand #2435/#2442
|
||||
* Reduce verbosity of fast datapath miss event logs #1852/#2417
|
||||
* Include the `ipam` option in the help output of the `status` subcommand #2425/#2426
|
||||
* Remove a harmless duplication of the `--no-dns` parameter #2430
|
||||
* Internal refactoring
|
||||
* Improvements to testing and building
|
||||
* Improvements to the documentation
|
||||
|
||||
More details in the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.6.1).
|
||||
|
||||
## Release 1.6.0
|
||||
|
||||
## Highlights
|
||||
|
||||
* A new [AWS VPC](https://weave.works/docs/net/latest/using-weave/awsvpc/) mode
|
||||
that leverages Amazon Virtual Private Cloud for near-native network
|
||||
performance, as an alternative to the Sleeve and Fast Datapath
|
||||
overlays
|
||||
* Docker 1.12 introduced some internal changes that made it
|
||||
incompatible with previous version of Weave Net - this version
|
||||
restores compatibility
|
||||
* An [operational guide](https://weave.works/docs/net/latest/operational-guide)
|
||||
detailing best practices for deploying and operating Weave Net
|
||||
* Changes to the target peer list are remembered across restarts,
|
||||
making it much easier to deploy resilient networks
|
||||
* The version checkpoint now transmits network mode (e.g. 'awsvpc')
|
||||
and kernel/docker versions to us to inform and guide our development
|
||||
efforts. See the [installation documentation](https://weave.works/docs/net/latest/installing-weave/)
|
||||
for instructions on disabling the checkpoint feature.
|
||||
|
||||
More details in the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.6.0).
|
||||
|
||||
## Release 1.5.2
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Weave Proxy did not flush the initial http header in the Docker event stream, which could cause Docker Swarm to show all nodes pending. #2306/#2311
|
||||
* When using the CNI plugin, if a container was removed and quickly replaced by another using the same IP address, other containers might be unable to contact it. Send an address resolution protocol message to update them. #2313
|
||||
* Avoid Docker hanging for 1 minute in `weave launch` if the plugin had not shut down cleanly #2286/#2292
|
||||
* Print an error message when Weave bridge mode is changed without `weave reset` #2304
|
||||
* Eliminate spurious warning message from IP allocator on plugin shutdown #2300/#2319
|
||||
* Display error message when address requested in a subnet that is too small (/31 or /32) #2282/#2321
|
||||
* Add short wait after `weave reset` to allow updates to reach peers #2280
|
||||
* Weave was occasionally unable to claim existing IP address immediately after launch #2275/#2281
|
||||
* Refactor some integration tests to run faster and more reliably #2291
|
||||
|
||||
## Release 1.5.1
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Persisted data that was rendered invalid by changing peer name or
|
||||
allocation range is detected and removed automatically, preventing
|
||||
crashes and hangs #2246/#2209/#2249
|
||||
* `weave rmpeer` persists the range takeover in case the peer on which
|
||||
it was executed dies subsequently #2238
|
||||
* Launching a container with an explicit `WEAVE_CIDR` in the
|
||||
allocation range now waits instead of erroring if the allocator
|
||||
hasn't finished initialising #2232/#2265
|
||||
* Weave DNS now responds to AAAA queries with an empty answer section,
|
||||
instead of NXDOMAIN which could be cached and block subsequent
|
||||
resolution of A records #2244/#2252
|
||||
* Many improvements to the documentation.
|
||||
|
||||
More details in the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.5.1).
|
||||
|
||||
## Release 1.5.0
|
||||
|
||||
## Highlights
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
Thanks for your interest in contributing to this project and for
|
||||
taking the time to read this guide.
|
||||
|
||||
# Code of conduct
|
||||
|
||||
**Be respectful:** disagreements between people are to be expected and are usually the sign of healthy debate and engagement. Disagreements can lead to frustration and even anger for some members. Turning to personal insults, intimidation or threatening behaviour does not improve the situation. Participants should thus take care to ensure all communications / interactions stay professional at all times.
|
||||
|
||||
**Be considerate:** remember that the community has members with a diverse background, many of whom have English as a second language. What might appear impolite, may simply be a result of a lack of knowledge of the English language. Bear in mind that actions will have an impact on other community members and the project as a whole, so take potential consequences into account before pursuing a course of action.
|
||||
|
||||
**Be forgiving:** humans are fallible and as such prone to make mistakes and inexplicably change their positions at times. Don't assume that other members are acting with malicious intent. Be prepared to forgive people who make mistakes and assist each other in learning from them. Playing a blame game doesn't help anyone.
|
||||
|
||||
# Issues
|
||||
* Before reporting an issue make sure you search first if anybody has already reported a similar issue and whether or not it has been fixed.
|
||||
* Make sure your issue report sufficiently details the problem:
|
||||
* If you set up weave in some way other than that described by our [main instructions](https://github.com/zettio/weave#installation), please mention that.
|
||||
* Include step-by-step instructions to reproduce the issue.
|
||||
* State the OS platform you are using, i.e. `uname -a`
|
||||
* `iptables` rules and firewalls can have a great impact on any Linux network; please note whether you have set up any rules and/or are running a firewall on your machine(s).
|
||||
* If you want to tackle any open issue, make sure you let people know you are working on it.
|
||||
|
||||
## Pull Requests
|
||||
* Please be generous describing your changes.
|
||||
* Although it is highly suggested to include tests, they are not a hard requirement in order to get your contributions accepted.
|
||||
* Keep pull requests small so core developers can review them quickly.
|
||||
* Keep each pull request focused on a specific topic. If you have two things to change, create two pull requests. This helps reviewers to understand the meat of your contribution.
|
||||
|
||||
## Workflow
|
||||
See [WorkingOnWeave](https://github.com/weaveworks/weave/wiki/WorkingOnWeave)
|
||||
|
||||
# Acknowledgements
|
||||
|
||||
Much of this taken from https://github.com/cloudescape/gowsdl/blob/master/CONTRIBUTING.md
|
||||
@@ -0,0 +1,3 @@
|
||||
DOCKER_VERSION=1.11.2
|
||||
KUBERNETES_VERSION=1.5.2
|
||||
KUBERNETES_CNI_VERSION=0.3.0.1
|
||||
@@ -1,52 +1,137 @@
|
||||
PUBLISH=publish_weave publish_weaveexec publish_plugin
|
||||
|
||||
.DEFAULT: all
|
||||
.PHONY: all exes testrunner update tests lint publish $(PUBLISH) clean clean-bin prerequisites build run-smoketests
|
||||
.PHONY: all exes testrunner update tests lint publish-one-arch $(PUBLISH) clean clean-bin prerequisites build run-smoketests
|
||||
|
||||
# If you can use docker without being root, you can do "make SUDO="
|
||||
SUDO=sudo -E
|
||||
SUDO=$(shell docker info >/dev/null 2>&1 || echo "sudo -E")
|
||||
BUILD_IN_CONTAINER=true
|
||||
RM=--rm
|
||||
RUN_FLAGS=-ti
|
||||
COVERAGE=
|
||||
|
||||
DOCKERHUB_USER=weaveworks
|
||||
WEAVE_VERSION=git-$(shell git rev-parse --short=12 HEAD)
|
||||
# This specifies the architecture we're building for
|
||||
ARCH?=amd64
|
||||
|
||||
# We're using QEMU to be able to build Docker images for other architectures
|
||||
# from amd64 machines.
|
||||
QEMU_VERSION=v2.7.0
|
||||
|
||||
# A list of all supported architectures here. Should be named as Go is naming platforms
|
||||
# All supported architectures must have an "ifeq" block below that customizes the parameters
|
||||
ALL_ARCHITECTURES=amd64 arm arm64
|
||||
ML_PLATFORMS=linux/amd64,linux/arm,linux/arm64
|
||||
|
||||
ifeq ($(ARCH),amd64)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=x86_64
|
||||
|
||||
# The name of the alpine baseimage to use as the base for weave images
|
||||
ALPINE_BASEIMAGE?=alpine:3.4
|
||||
|
||||
# The extension for the made images
|
||||
# Specifying none means for example weaveworks/weave:latest
|
||||
ARCH_EXT?=
|
||||
|
||||
# The name of the gcc we're using for compiling C code
|
||||
CC=gcc
|
||||
|
||||
# Optional parameters that can be passed when linking C code into the Go binary
|
||||
CGO_LDFLAGS=
|
||||
endif
|
||||
ifeq ($(ARCH),arm)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=armel
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=armhf/alpine:3.4
|
||||
|
||||
# arm images have the -arm suffix, for instance weaveworks/weave-arm:latest
|
||||
ARCH_EXT?=-arm
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=arm-linux-gnueabihf-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=arm
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for arm are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
ifeq ($(ARCH),arm64)
|
||||
# Use the arm 32-bit docker client variant
|
||||
WEAVEEXEC_DOCKER_ARCH?=armel
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=aarch64/alpine:3.5
|
||||
|
||||
# arm64 images have the -arm64 suffix, for instance weaveworks/weave-arm64:latest
|
||||
ARCH_EXT?=-arm64
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=aarch64-linux-gnu-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=aarch64
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for arm64 are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
|
||||
# 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
|
||||
WEAVE_VERSION?=git-$(shell git rev-parse --short=12 HEAD)
|
||||
|
||||
# Paths to all relevant binaries that should be compiled
|
||||
WEAVER_EXE=prog/weaver/weaver
|
||||
WEAVEPROXY_EXE=prog/weaveproxy/weaveproxy
|
||||
SIGPROXY_EXE=prog/sigproxy/sigproxy
|
||||
KUBEPEERS_EXE=prog/kube-peers/kube-peers
|
||||
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
|
||||
PLUGIN_EXE=prog/plugin/plugin
|
||||
RUNNER_EXE=tools/runner/runner
|
||||
MANIFEST_TOOL_DIR=vendor/github.com/estesp/manifest-tool
|
||||
MANIFEST_TOOL_EXE=$(MANIFEST_TOOL_DIR)/manifest-tool
|
||||
TEST_TLS_EXE=test/tls/tls
|
||||
|
||||
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE) $(PLUGIN_EXE) $(TEST_TLS_EXE)
|
||||
# All binaries together in a list
|
||||
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(KUBEPEERS_EXE) $(WEAVENPC_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE) $(PLUGIN_EXE) $(RUNNER_EXE) $(TEST_TLS_EXE) $(MANIFEST_TOOL_EXE)
|
||||
|
||||
# These stamp files are used to mark the current state of the build; whether an image has been built or not
|
||||
BUILD_UPTODATE=.build.uptodate
|
||||
WEAVER_UPTODATE=.weaver.uptodate
|
||||
WEAVEEXEC_UPTODATE=.weaveexec.uptodate
|
||||
PLUGIN_UPTODATE=.plugin.uptodate
|
||||
WEAVER_UPTODATE=.weaver$(ARCH_EXT).uptodate
|
||||
WEAVEEXEC_UPTODATE=.weaveexec$(ARCH_EXT).uptodate
|
||||
PLUGIN_UPTODATE=.plugin$(ARCH_EXT).uptodate
|
||||
WEAVEKUBE_UPTODATE=.weavekube$(ARCH_EXT).uptodate
|
||||
WEAVENPC_UPTODATE=.weavenpc$(ARCH_EXT).uptodate
|
||||
WEAVEDB_UPTODATE=.weavedb.uptodate
|
||||
|
||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) $(PLUGIN_UPTODATE) $(BUILD_UPTODATE) $(WEAVEDB_UPTODATE)
|
||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) $(PLUGIN_UPTODATE) $(WEAVEKUBE_UPTODATE) $(WEAVENPC_UPTODATE)
|
||||
|
||||
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave
|
||||
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec
|
||||
PLUGIN_IMAGE=$(DOCKERHUB_USER)/plugin
|
||||
BUILD_IMAGE=$(DOCKERHUB_USER)/weavebuild
|
||||
# The names of the images. Note that the images for other architectures than amd64 have a suffix in the image name.
|
||||
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave$(ARCH_EXT)
|
||||
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec$(ARCH_EXT)
|
||||
PLUGIN_IMAGE=$(DOCKERHUB_USER)/plugin$(ARCH_EXT)
|
||||
WEAVEKUBE_IMAGE=$(DOCKERHUB_USER)/weave-kube$(ARCH_EXT)
|
||||
WEAVENPC_IMAGE=$(DOCKERHUB_USER)/weave-npc$(ARCH_EXT)
|
||||
BUILD_IMAGE=weaveworks/weavebuild
|
||||
WEAVEDB_IMAGE=$(DOCKERHUB_USER)/weavedb
|
||||
|
||||
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) $(PLUGIN_IMAGE) $(WEAVEDB_IMAGE)
|
||||
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) $(PLUGIN_IMAGE) $(WEAVEKUBE_IMAGE) $(WEAVENPC_IMAGE) $(WEAVEDB_IMAGE)
|
||||
|
||||
WEAVE_EXPORT=weave.tar.gz
|
||||
PUBLISH=publish_weave publish_weaveexec publish_plugin publish_weave-kube publish_weave-npc
|
||||
PUSH_ML=push_ml_weave push_ml_weaveexec push_ml_plugin push_ml_weave-kube push_ml_weave-npc
|
||||
|
||||
WEAVEEXEC_DOCKER_VERSION=1.6.2
|
||||
DOCKER_DISTRIB=prog/weaveexec/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
|
||||
DOCKER_DISTRIB_URL=https://get.docker.com/builds/Linux/x86_64/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
|
||||
WEAVE_EXPORT=weave$(ARCH_EXT).tar.gz
|
||||
|
||||
DOCKER_VERSION=1.10.3
|
||||
DOCKER_DISTRIB=prog/weaveexec/docker-$(DOCKER_VERSION).tgz
|
||||
DOCKER_DISTRIB_URL=https://get.docker.com/builds/Linux/$(WEAVEEXEC_DOCKER_ARCH)/docker-$(DOCKER_VERSION).tgz
|
||||
NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
|
||||
rm $@; \
|
||||
echo "\nYour go standard library was built without the 'netgo' build tag."; \
|
||||
@@ -55,44 +140,52 @@ NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
|
||||
echo " sudo go install -tags netgo std"; \
|
||||
false; \
|
||||
}
|
||||
BUILD_FLAGS=-i -ldflags "-extldflags \"-static\" -X main.version=$(WEAVE_VERSION)" -tags netgo
|
||||
# The flags we are passing to go build. -extldflags -static for making a static binary,
|
||||
# -linkmode external for linking external C libraries into the binary, -X main.version for telling the
|
||||
# Go binary which version it is, -tags netgo for enforcing the native Go DNS resolver
|
||||
BUILD_FLAGS=-i -ldflags "-linkmode external -extldflags -static -X main.version=$(WEAVE_VERSION)" -tags netgo
|
||||
|
||||
PACKAGE_BASE=$(shell go list -e ./)
|
||||
|
||||
all: $(WEAVE_EXPORT)
|
||||
exes: $(EXES)
|
||||
testrunner: $(RUNNER_EXE) $(TEST_TLS_EXE)
|
||||
|
||||
$(EXES): $(BUILD_UPTODATE)
|
||||
$(WEAVER_EXE) $(WEAVEPROXY_EXE) $(WEAVEUTIL_EXE): common/*.go common/*/*.go net/*.go net/*/*.go
|
||||
$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go db/*.go nameserver/*.go prog/weaver/*.go
|
||||
$(WEAVEPROXY_EXE): proxy/*.go prog/weaveproxy/*.go
|
||||
$(WEAVEUTIL_EXE): prog/weaveutil/*.go
|
||||
$(WEAVEUTIL_EXE): prog/weaveutil/*.go net/*.go plugin/net/*.go plugin/ipam/*.go db/*.go
|
||||
$(SIGPROXY_EXE): prog/sigproxy/*.go
|
||||
$(PLUGIN_EXE): prog/plugin/*.go plugin/*/*.go api/*.go common/*.go common/docker/*.go
|
||||
$(KUBEPEERS_EXE): prog/kube-peers/*.go
|
||||
$(WEAVENPC_EXE): prog/weave-npc/*.go npc/*.go npc/*/*.go
|
||||
$(PLUGIN_EXE): prog/plugin/*.go plugin/*/*.go api/*.go common/*.go common/docker/*.go net/*.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
|
||||
tests: $(BUILD_UPTODATE) tools/.git
|
||||
lint: $(BUILD_UPTODATE) tools/.git
|
||||
tests: tools/.git
|
||||
lint: tools/.git
|
||||
|
||||
ifeq ($(BUILD_IN_CONTAINER),true)
|
||||
|
||||
$(EXES) tests lint:
|
||||
# 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
|
||||
@mkdir -p $(shell pwd)/.pkg
|
||||
$(SUDO) docker run $(RM) $(RUN_FLAGS) \
|
||||
-v $(shell pwd):/go/src/github.com/weaveworks/weave \
|
||||
-v $(shell pwd)/.pkg:/go/pkg \
|
||||
-e GOARCH -e GOOS -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX -e COVERDIR -e SLOW \
|
||||
$(BUILD_IMAGE) COVERAGE=$(COVERAGE) WEAVE_VERSION=$(WEAVE_VERSION) $@
|
||||
-e GOARCH=$(ARCH) -e CGO_ENABLED=1 -e GOOS=linux -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX -e COVERDIR -e SLOW -e DEBUG \
|
||||
$(BUILD_IMAGE) COVERAGE=$(COVERAGE) WEAVE_VERSION=$(WEAVE_VERSION) CC=$(CC) QEMUARCH=$(QEMUARCH) CGO_LDFLAGS=$(CGO_LDFLAGS) $@
|
||||
|
||||
else
|
||||
|
||||
exes: $(EXES)
|
||||
|
||||
$(WEAVER_EXE) $(WEAVEPROXY_EXE) $(PLUGIN_EXE):
|
||||
ifeq ($(COVERAGE),true)
|
||||
go build -v $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
$(eval COVERAGE_MODULES := $(shell (go list ./$(@D); go list -f '{{join .Deps "\n"}}' ./$(@D) | grep "^$(PACKAGE_BASE)/") | grep -v "^$(PACKAGE_BASE)/vendor/" | paste -s -d,))
|
||||
go test -c -o ./$@ $(BUILD_FLAGS) -v -covermode=atomic -coverpkg $(COVERAGE_MODULES) ./$(@D)/
|
||||
else
|
||||
@@ -100,7 +193,7 @@ else
|
||||
endif
|
||||
$(NETGO_CHECK)
|
||||
|
||||
$(WEAVEUTIL_EXE):
|
||||
$(WEAVEUTIL_EXE) $(KUBEPEERS_EXE) $(WEAVENPC_EXE):
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
$(NETGO_CHECK)
|
||||
|
||||
@@ -114,26 +207,48 @@ $(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):
|
||||
$(SIGPROXY_EXE) $(TEST_TLS_EXE) $(WEAVEWAIT_NOOP_EXE) $(RUNNER_EXE) $(MANIFEST_TOOL_EXE):
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
|
||||
tests:
|
||||
./tools/test -no-go-get
|
||||
./tools/test -no-go-get -netgo -timeout 8m
|
||||
|
||||
lint:
|
||||
./tools/lint -nocomment -notestpackage .
|
||||
./tools/lint -nocomment -notestpackage
|
||||
|
||||
endif
|
||||
|
||||
# This rule makes sure the build image is up-to-date.
|
||||
# It also makes sure the multiarch hooks are reqistered in the kernel so the QEMU emulation works
|
||||
$(BUILD_UPTODATE): build/*
|
||||
$(SUDO) docker build -t $(BUILD_IMAGE) build/
|
||||
$(SUDO) docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
touch $@
|
||||
|
||||
$(WEAVER_UPTODATE): prog/weaver/Dockerfile $(WEAVER_EXE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -t $(WEAVER_IMAGE) prog/weaver
|
||||
# Creates the Dockerfile.your-user-here file from the template
|
||||
# Also replaces all placeholders with real values
|
||||
# If the architecture is amd64, it deletes all CROSS_BUILD lines
|
||||
# but otherwise, it only removes the "CROSS_BUILD_" placeholder and downloads QEMU
|
||||
%/Dockerfile.$(DOCKERHUB_USER): %/Dockerfile.template
|
||||
echo "DOCKERHUB_USER|$(DOCKERHUB_USER)|g;s|ARCH_EXT|$(ARCH_EXT)|g;s|ALPINE_BASEIMAGE|$(ALPINE_BASEIMAGE)|g;s|QEMUARCH|$(QEMUARCH)"
|
||||
sed -e "s|DOCKERHUB_USER|$(DOCKERHUB_USER)|g;s|ARCH_EXT|$(ARCH_EXT)|g;s|ALPINE_BASEIMAGE|$(ALPINE_BASEIMAGE)|g;s|QEMUARCH|$(QEMUARCH)|g" $^ > $@
|
||||
ifeq ($(ARCH),amd64)
|
||||
# When building "normally" for amd64, remove the whole line, it has no part in the amd64 image
|
||||
sed -i "/CROSS_BUILD_/d" $@
|
||||
else
|
||||
# When cross-building, only the placeholder "CROSS_BUILD_" should be removed
|
||||
# Register /usr/bin/qemu-ARCH-static as the handler for ARM binaries in the kernel
|
||||
curl -sSL https://github.com/multiarch/qemu-user-static/releases/download/$(QEMU_VERSION)/x86_64_qemu-$(QEMUARCH)-static.tar.gz | tar -xz -C $(shell dirname $@)
|
||||
cd $(shell dirname $@) && sha256sum -c $(shell pwd)/build/shasums/qemu-$(QEMUARCH)-static.sha256sum
|
||||
sed -i "s/CROSS_BUILD_//g" $@
|
||||
endif
|
||||
|
||||
# The targets below builds the weave images
|
||||
$(WEAVER_UPTODATE): prog/weaver/Dockerfile.$(DOCKERHUB_USER) $(WEAVER_EXE) $(WEAVEEXEC_UPTODATE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -f prog/weaver/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVER_IMAGE) prog/weaver
|
||||
touch $@
|
||||
|
||||
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile prog/weaveexec/symlink $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE)
|
||||
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) prog/weaveexec/symlink $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE)
|
||||
cp weave prog/weaveexec/weave
|
||||
cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy
|
||||
cp $(WEAVEPROXY_EXE) prog/weaveexec/weaveproxy
|
||||
@@ -141,45 +256,101 @@ $(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile prog/weaveexec/symlink $(DOCKER
|
||||
cp $(WEAVEWAIT_NOOP_EXE) prog/weaveexec/weavewait_noop
|
||||
cp $(WEAVEWAIT_NOMCAST_EXE) prog/weaveexec/weavewait_nomcast
|
||||
cp $(WEAVEUTIL_EXE) prog/weaveexec/weaveutil
|
||||
cp $(DOCKER_DISTRIB) prog/weaveexec/docker.tgz
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec
|
||||
tar -xf $(DOCKER_DISTRIB) usr/local/bin/docker -O > prog/weaveexec/docker
|
||||
chmod +x prog/weaveexec/docker
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -f prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEEXEC_IMAGE) prog/weaveexec
|
||||
touch $@
|
||||
|
||||
$(PLUGIN_UPTODATE): prog/plugin/Dockerfile $(PLUGIN_EXE) $(WEAVEEXEC_UPTODATE)
|
||||
$(SUDO) docker build -t $(PLUGIN_IMAGE) prog/plugin
|
||||
$(PLUGIN_UPTODATE): prog/plugin/Dockerfile.$(DOCKERHUB_USER) $(PLUGIN_EXE) $(WEAVER_UPTODATE)
|
||||
$(SUDO) docker build -f prog/plugin/Dockerfile.$(DOCKERHUB_USER) -t $(PLUGIN_IMAGE) prog/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
|
||||
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
|
||||
touch $@
|
||||
|
||||
$(WEAVEDB_UPTODATE): prog/weavedb/Dockerfile
|
||||
$(SUDO) docker build -t $(WEAVEDB_IMAGE) prog/weavedb
|
||||
touch $@
|
||||
|
||||
$(WEAVE_EXPORT): $(IMAGES_UPTODATE)
|
||||
$(WEAVE_EXPORT): $(IMAGES_UPTODATE) $(WEAVEDB_UPTODATE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@
|
||||
|
||||
$(DOCKER_DISTRIB):
|
||||
curl -o $(DOCKER_DISTRIB) $(DOCKER_DISTRIB_URL)
|
||||
cd $(shell dirname $@) && sha256sum -c $(shell pwd)/build/shasums/docker-tgz-$(WEAVEEXEC_DOCKER_ARCH).sha256sum
|
||||
|
||||
tools/.git:
|
||||
tools/.git $(MANIFEST_TOOL_DIR)/.git:
|
||||
git submodule update --init
|
||||
|
||||
$(RUNNER_EXE): tools/.git
|
||||
make -C tools/runner
|
||||
# CODE FOR PUBLISHING THE IMAGES
|
||||
|
||||
$(PUBLISH): publish_%: $(IMAGES_UPTODATE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag -f $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:latest
|
||||
# This target first runs "make publish" for each architecture
|
||||
# Then it pushes the manifest lists
|
||||
publish: $(addprefix sub-publish-,$(ALL_ARCHITECTURES)) $(WEAVEDB_UPTODATE)
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) $(PUSH_ML)
|
||||
ifeq ($(PUBLISH_WEAVEDB),true)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/weavedb:latest
|
||||
endif
|
||||
|
||||
publish: $(PUBLISH)
|
||||
sub-publish-%:
|
||||
$(MAKE) ARCH=$* DOCKER_HOST=$(DOCKER_HOST) clean-bin
|
||||
$(MAKE) ARCH=$* DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) $(IMAGES_UPTODATE)
|
||||
$(MAKE) ARCH=$* DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) publish-one-arch
|
||||
|
||||
publish-one-arch: $(PUBLISH)
|
||||
|
||||
# This rule handles the pushing of the built images
|
||||
$(PUBLISH): publish_%: $(IMAGES_UPTODATE)
|
||||
# Tag :latest with the real version
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*$(ARCH_EXT) $(DOCKERHUB_USER)/$*$(ARCH_EXT):$(WEAVE_VERSION)
|
||||
# Push the image with the arch suffix for arm and arm64, and without suffix for amd64
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) push_$*$(ARCH_EXT)
|
||||
|
||||
ifeq ($(ARCH),amd64)
|
||||
# If the architecture is amd64, add the -amd64 suffix.
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION) $(DOCKERHUB_USER)/$*-amd64:$(WEAVE_VERSION)
|
||||
# If the version is latest, tag the -amd64 with latest as well
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*-amd64:$(WEAVE_VERSION) $(DOCKERHUB_USER)/$*-amd64:latest
|
||||
endif
|
||||
# Push the image with the -amd64-suffix so BINARY-ARCH-named images exist for all arches, so manifest lists may be made and ARCH is replaceable in scripts
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) push_$*-amd64
|
||||
endif
|
||||
|
||||
# This target pushes an image, and if UPDATE_LATEST is anything but false, also updates the latest tag
|
||||
# It takes one parameter, the image name
|
||||
push_%:
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:latest
|
||||
endif
|
||||
|
||||
# This target pushes a manifest list; it takes one parameter, the image name.
|
||||
# The variable UPDATE_LATEST controls whether it updates the versioned tag and/or the latest tag
|
||||
$(PUSH_ML): push_ml_%: $(MANIFEST_TOOL_EXE)
|
||||
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)
|
||||
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
|
||||
endif
|
||||
|
||||
clean-bin:
|
||||
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(IMAGES) $(BUILD_IMAGE)
|
||||
rm -rf $(EXES) $(IMAGES_UPTODATE) $(WEAVE_EXPORT) .pkg
|
||||
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(IMAGES)
|
||||
find prog -type f -name "Dockerfile.*" -not -name "Dockerfile.template" -print | xargs rm -f
|
||||
find prog -type f -name "*qemu-*" -print | xargs rm -f
|
||||
rm -rf $(EXES) $(IMAGES_UPTODATE) $(WEAVEDB_UPTODATE) weave*.tar.gz $(DOCKER_DISTRIB) prog/weaveexec/docker .pkg
|
||||
|
||||
clean: clean-bin
|
||||
rm -rf test/tls/*.pem test/coverage.* test/coverage
|
||||
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(BUILD_IMAGE)
|
||||
rm -rf test/tls/*.pem test/coverage.* test/coverage $(BUILD_UPTODATE) $(MANIFEST_TOOL_EXE)
|
||||
|
||||
build:
|
||||
$(SUDO) go clean -i net
|
||||
@@ -188,3 +359,20 @@ build:
|
||||
|
||||
run-smoketests: all testrunner
|
||||
cd test && ./setup.sh && ./run_all.sh
|
||||
|
||||
integration-tests: all testrunner
|
||||
# Usage:
|
||||
# $ make \
|
||||
# NAME="<prefix used to name VMs and other resources>" \
|
||||
# PROVIDER="<provider among {vagrant|gcp|aws|do}>" \
|
||||
# NUM_HOSTS="<# test machines>" \
|
||||
# PLAYBOOK="<filename>" \
|
||||
# RUNNER_ARGS="<...>" \
|
||||
# TESTS="<...>" \ # Can be set to only run one or a few tests instead of the full test suite.
|
||||
# DOCKER_VERSION=<...> \
|
||||
# KUBERNETES_VERSION=<...> \
|
||||
# KUBERNETES_CNI_VERSION=<...> \
|
||||
# <...> # See also run-integration-test.sh for all variables and individual functions.
|
||||
# integration-tests
|
||||
#
|
||||
RUNNER_ARGS="-parallel" ./test/run-integration-tests.sh
|
||||
@@ -1,181 +1,42 @@
|
||||
# Weave Net - Weaving Containers into Applications
|
||||
|
||||
[](https://travis-ci.org/weaveworks/weave) [](https://circleci.com/gh/weaveworks/weave) [](https://coveralls.io/r/weaveworks/weave)
|
||||
[](https://circleci.com/gh/weaveworks/weave)
|
||||
[](https://coveralls.io/r/weaveworks/weave)
|
||||
[](https://goreportcard.com/report/github.com/weaveworks/weave)
|
||||
[](https://slack.weave.works)
|
||||
[](https://hub.docker.com/r/weaveworks/weave/)
|
||||
|
||||
# About Weaveworks
|
||||
|
||||
[Weaveworks](http://weave.works) is the company that delivers the most productive way for developers to connect, observe and control
|
||||
Docker containers. The first product developed by Weaveworks, with nearly 5 million downloads to date, is Weave Net, enabling users to get started with Docker clusters and portable apps in a fraction of the time compared to other solutions. In addition, Weaveworks offers Weave Scope, a powerful container monitoring tool that automatically maps Docker containers and their interactions, as well as Weave Flux, a microservice router that automates the access of containers as services.
|
||||
|
||||
[Weaveworks](https://www.weave.works) is the company that delivers the most productive way for developers to connect, observe and control
|
||||
Docker containers.
|
||||
|
||||
# Introduction
|
||||
This document is intended to cover some of the more technical aspects of Weave Net. To learn about our products, including getting
|
||||
started tutorials, visit our [website](http://weave.works) and
|
||||
[documentation](http://docs.weave.works).
|
||||
This repository contains [Weave Net](https://www.weave.works/products/weave-net/), the first product developed by Weaveworks, and with over 8 million downloads to date, it enables you to get started with Docker clusters and portable apps in a fraction of the time compared with other solutions.
|
||||
|
||||
Weaveworks' product [Weave Cloud](https://www.weave.works/solution/cloud/) takes the heavy lifting out of building, integrating and operating open source components, so you don’t have to manage databases, storage and availability.
|
||||
|
||||
Weave Cloud is built using these Open Source projects: [Weave Scope](https://www.weave.works/products/weave-scope/), a powerful container monitoring tool that automatically maps Docker containers and their interactions, [Weave Cortex](https://github.com/weaveworks/cortex), a horizontally-scalable version of Prometheus, and [Weave Flux](https://www.weave.works/products/weave-flux/), a continuous deployment tool that works with Kubernetes.
|
||||
|
||||
# Weave Net
|
||||
|
||||
Weave Net creates a virtual network that connects Docker containers
|
||||
deployed across multiple hosts and enables their automatic discovery.
|
||||
Weave Net creates a virtual network that connects Docker containers across multiple hosts and enables their automatic discovery. With Weave Net, portable microservices-based applications consisting of multiple containers can run anywhere: on one host, multiple hosts or even across cloud providers and data centers. Applications use the network just as if the containers were all plugged into the same network switch, without having to configure port mappings, ambassadors or links.
|
||||
|
||||

|
||||
Services provided by application containers on the Weave network can be exposed to the outside world, regardless of where they are running. Similarly, existing internal systems can be opened to accept connections from application containers irrespective of their location.
|
||||
|
||||
Applications use the network just as if the containers were all
|
||||
plugged into the same network switch, with no need to configure port
|
||||
mappings, links, etc. Services provided by application containers on
|
||||
the weave network can be made accessible to the outside world,
|
||||
regardless of where those containers are running. Similarly, existing
|
||||
internal systems can be exposed to application containers irrespective
|
||||
of their location.
|
||||
## <a name="help"></a>Getting help
|
||||
|
||||

|
||||
If you have any questions about, feedback for or a problem with Weave Net:
|
||||
|
||||
Weave Net can traverse firewalls and operate in partially connected
|
||||
networks. Traffic can be encrypted, allowing hosts to be connected
|
||||
across an untrusted network.
|
||||
- Read [the Weave Net docs](https://www.weave.works/docs/net/latest/introducing-weave/).
|
||||
- Invite yourself to the <a href="https://weaveworks.github.io/community-slack/" target="_blank"> #weave-community </a> slack channel.
|
||||
- Ask a question on the <a href="https://weave-community.slack.com/messages/general/"> #weave-community</a> slack channel.
|
||||
- Join the <a href="https://www.meetup.com/pro/Weave/"> Weave User Group </a> and get invited to online talks, hands-on training and meetups in your area.
|
||||
- Send an email to <a href="mailto:weave-users@weave.works">weave-users@weave.works</a>
|
||||
- <a href="https://github.com/weaveworks/weave/issues/new">File an issue.</a>
|
||||
|
||||
With Weave Net you can easily construct applications consisting of
|
||||
multiple containers, running anywhere.
|
||||
Your feedback is always welcome!
|
||||
|
||||
Weave Net works alongside Docker's existing (single host) networking
|
||||
capabilities, so these can continue to be used by containers.
|
||||
##Further Information
|
||||
|
||||
## Installation
|
||||
|
||||
To get started, you should be running Linux (kernel 3.8 or later) and have Docker
|
||||
(version 1.6.0 or later) installed. Then install Weave Net with
|
||||
|
||||
sudo curl -L git.io/weave -o /usr/local/bin/weave
|
||||
sudo chmod a+x /usr/local/bin/weave
|
||||
|
||||
For usage on OSX (with Docker Machine) you first need to
|
||||
[make sure that a VM is running and configured](https://docs.docker.com/installation/mac/#from-your-shell).
|
||||
Then you can launch Weave Net directly from the OSX host.
|
||||
|
||||
For installing Weave Net on other platforms, follow the [integration guides](http://weave.works/product/integrations/).
|
||||
|
||||
Weave Net respects the environment variable `DOCKER_HOST`, so you can run
|
||||
it locally to control a weave network on a remote host.
|
||||
|
||||
Weave Net
|
||||
[periodically contacts Weaveworks servers for available versions](https://github.com/weaveworks/go-checkpoint).
|
||||
New versions are announced in the log and in
|
||||
[the status summary](http://docs.weave.works/weave/latest_release/troubleshooting.html#weave-status).
|
||||
To disable this check, run:
|
||||
|
||||
```
|
||||
export CHECKPOINT_DISABLE=1
|
||||
```
|
||||
|
||||
before launching Weave Net.
|
||||
|
||||
## Quick Start Screencast
|
||||
|
||||
<a href="https://youtu.be/kihQCCT1ykE" alt="Click to watch the screencast" target="_blank">
|
||||
<img src="/docs/hello-screencast.png" />
|
||||
</a>
|
||||
|
||||
## Example
|
||||
|
||||
Say you have docker running on two hosts, accessible to each other as
|
||||
`$HOST1` and `$HOST2`, and want to deploy an application consisting of
|
||||
two containers, one on each host.
|
||||
|
||||
On $HOST1 you would run:
|
||||
|
||||
host1$ weave launch
|
||||
host1$ eval $(weave env)
|
||||
host1$ docker run --name a1 -ti ubuntu
|
||||
|
||||
> NB: If the first command results in an error like
|
||||
> `http:///var/run/docker.sock/v1.19/containers/create: dial unix
|
||||
> /var/run/docker.sock: permission denied. Are you trying to connect
|
||||
> to a TLS-enabled daemon without TLS?` then you likely need to be
|
||||
> 'root' in order to connect to the Docker daemon. If so, run the
|
||||
> above and all subsequent commands in a *single* root shell (e.g. one
|
||||
> created with `sudo -s`). Do *not* prefix individual commands with
|
||||
> `sudo`, since some commands modify environment entries and hence
|
||||
> they all need to be executed from the same shell.
|
||||
|
||||
The first line runs Weave Net. The second line configures your environment
|
||||
so that containers launched via the docker command line are
|
||||
automatically attached to the weave network. Finally, you run your
|
||||
application container.
|
||||
|
||||
That's it! If your application consists of more than one container on
|
||||
this host, you can simply launch them with `docker run` as appropriate.
|
||||
|
||||
Next you'll repeat similar steps on `$HOST2`...
|
||||
|
||||
host2$ weave launch $HOST1
|
||||
host2$ eval $(weave env)
|
||||
host2$ docker run --name a2 -ti ubuntu
|
||||
|
||||
The only difference, apart from the name of the application container,
|
||||
is that weave is being told that it should peer with the weave on
|
||||
`$HOST1` (specified as the IP address or hostname, and optional
|
||||
`:port`, by which `$HOST2` can reach it). NB: if there is a firewall
|
||||
between `$HOST1` and `$HOST2`, you must permit traffic to the weave
|
||||
control port (TCP 6783) and data ports (UDP 6783/6784).
|
||||
|
||||
Note that you could instead have told the Weave Net instance on `$HOST1` to connect to
|
||||
`$HOST2`, or told both about each other. Order does not matter here;
|
||||
Weave Net automatically (re)connects to peers when they become
|
||||
available. You can instruct Weave Net to connect to multiple peers by
|
||||
supplying multiple addresses, separated by spaces; you can also
|
||||
[add peers dynamically](http://docs.weave.works/weave/latest_release/features.html#dynamic-topologies).
|
||||
|
||||
Weave Net must be started once per host. The relevant container images are
|
||||
pulled down on demand, but if you wish you can preload them by running
|
||||
`weave setup` - this is particularly useful for automated deployments,
|
||||
and ensures that there are no delays during later operations.
|
||||
|
||||
Now that you've got everything set up, let's confirm that your containers
|
||||
can talk to each other...
|
||||
|
||||
In the container started on `$HOST1`...
|
||||
|
||||
root@a1:/# ping -c 1 -q a2
|
||||
PING a2.weave.local (10.40.0.2) 56(84) bytes of data.
|
||||
--- a2.weave.local ping statistics ---
|
||||
1 packets transmitted, 1 received, 0% packet loss, time 0ms
|
||||
rtt min/avg/max/mdev = 0.341/0.341/0.341/0.000 ms
|
||||
|
||||
Similarly, in the container started on `$HOST2`...
|
||||
|
||||
root@a2:/# ping -c 1 -q a1
|
||||
PING a1.weave.local (10.32.0.2) 56(84) bytes of data.
|
||||
--- a1.weave.local ping statistics ---
|
||||
1 packets transmitted, 1 received, 0% packet loss, time 0ms
|
||||
rtt min/avg/max/mdev = 0.366/0.366/0.366/0.000 ms
|
||||
|
||||
So there you have it, two containers on separate hosts happily talking
|
||||
to each other.
|
||||
|
||||
## Find out more
|
||||
|
||||
* [Documentation homepage](http://docs.weave.works/weave/latest_release/)
|
||||
* [Getting Started Guides](http://weave.works/guides/)
|
||||
* [Features](http://docs.weave.works/weave/latest_release/features.html)
|
||||
* [Troubleshooting](http://docs.weave.works/weave/latest_release/troubleshooting.html)
|
||||
* [Building](http://docs.weave.works/weave/latest_release/building.html)
|
||||
* [How it works](http://docs.weave.works/weave/latest_release/how-it-works.html)
|
||||
|
||||
## Contact Us
|
||||
|
||||
Found a bug, want to suggest a feature, or have a question? Please
|
||||
[file an issue](https://github.com/weaveworks/weave/issues), or post
|
||||
to the
|
||||
[Weave Users Google Group](https://groups.google.com/a/weave.works/forum/#!forum/weave-users),
|
||||
which you can email at weave-users@weave.works. When reporting a bug, please
|
||||
include which version of Weave Net you are running, as shown by `weave
|
||||
version`.
|
||||
|
||||
Follow us on Twitter:
|
||||
[@weaveworks](https://twitter.com/weaveworks).
|
||||
|
||||
Check out our blog for the latest product and ecosystem news:
|
||||
[Weaveworks Blog](https://www.weave.works/blog/).
|
||||
|
||||
IRC:
|
||||
[#weavenetwork](https://botbot.me/freenode/weavenetwork/)
|
||||
* [Installing Weave Net](https://www.weave.works/docs/net/latest/installing-weave/)
|
||||
* [Weave Net Product Overview](https://www.weave.works/products/weave-net/)
|
||||
Vendored
+18
-22
@@ -1,40 +1,36 @@
|
||||
VAGRANTFILE_API_VERSION = "2"
|
||||
|
||||
require './vagrant-common.rb'
|
||||
require File.expand_path(File.join(File.dirname(__FILE__), 'vagrant-common.rb'))
|
||||
|
||||
vm_ip = "172.16.0.3" # arbitrary private IP
|
||||
|
||||
pkgs = %w(
|
||||
docker-engine=1.10.2-0~wily
|
||||
aufs-tools
|
||||
build-essential
|
||||
ethtool
|
||||
iputils-arping
|
||||
libpcap-dev
|
||||
git
|
||||
mercurial
|
||||
bc
|
||||
)
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
|
||||
config.vm.box = "ubuntu/wily64"
|
||||
config.vm.box = VAGRANT_IMAGE
|
||||
|
||||
config.vm.network "private_network", ip: vm_ip
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "off"]
|
||||
vb.customize ["modifyvm", :id, "--natdnsproxy1", "off"]
|
||||
vb.memory = 2048
|
||||
configure_nat_dns(vb)
|
||||
end
|
||||
|
||||
# Disable default Vagrant shared folder, which we don't need:
|
||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
# Keep Weave Net sources' in sync:
|
||||
config.vm.synced_folder ".", "/home/vagrant/src/github.com/weaveworks/weave"
|
||||
# Create a convenience symlink to $HOME/src/github.com/weaveworks/weave
|
||||
config.vm.provision :shell, :inline => 'ln -sf ~vagrant/src/github.com/weaveworks/weave ~vagrant/'
|
||||
|
||||
install_build_deps config.vm, pkgs
|
||||
install_go_toochain config.vm
|
||||
tweak_user_env config.vm
|
||||
tweak_docker_daemon config.vm
|
||||
# Set SSH keys up to be able to run smoke tests straightaway:
|
||||
config.vm.provision "file", source: "~/.vagrant.d/insecure_private_key", destination: "/home/vagrant/src/github.com/weaveworks/weave/test/insecure_private_key"
|
||||
# Grant permissions on sources:
|
||||
config.vm.provision :shell, :inline => 'sudo chown -R vagrant:vagrant ~vagrant/src', :privileged => false
|
||||
cleanup config.vm
|
||||
|
||||
config.vm.provision 'ansible' do |ansible|
|
||||
ansible.playbook = 'tools/config_management/setup_weave-net_dev.yml'
|
||||
ansible.extra_vars = {
|
||||
go_version: GO_VERSION
|
||||
}.merge(ansibleize(get_dependencies_version_from_file_and_env()))
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
|
||||
@@ -5,6 +5,15 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// Special token used in place of a container identifier when:
|
||||
// - the caller does not know the container ID, or
|
||||
// - the caller is unsure whether IPAM was previously told the container ID.
|
||||
//
|
||||
// While addresses are typically stored under their container IDs, when this
|
||||
// token is used, the address will be stored under its own string, something
|
||||
// which should be kept in mind if the entry needs to be removed at any point.
|
||||
const NoContainerID string = "_"
|
||||
|
||||
func (client *Client) ipamOp(ID string, op string) (*net.IPNet, error) {
|
||||
ip, err := client.httpVerb(op, fmt.Sprintf("/ip/%s", ID), nil)
|
||||
if err != nil {
|
||||
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ]; then
|
||||
cd "$SRCDIR"
|
||||
git submodule update --init
|
||||
cd "$SRCDIR/build"
|
||||
../tools/rebuild-image weaveworks/weavebuild . Dockerfile build.sh
|
||||
touch "$SRCDIR/.build.uptodate"
|
||||
sudo chown -R ubuntu /usr/local/go
|
||||
cd "$SRCDIR"
|
||||
make testrunner
|
||||
make COVERAGE=true RM= exes all
|
||||
fi
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "TEST_AND_PUBLISH=1" >"$STATE"
|
||||
|
||||
# Avoid doing a full build if we can. Note if the PR doesn't exist
|
||||
# when the build is kicked off, we can't run this check so we do a
|
||||
# full build. Subsequent pushes to the same branch will have the desired behaviour
|
||||
if [ -n "$CI_PULL_REQUEST" -a "$CI_PULL_REQUEST" = "$CI_PULL_REQUESTS" ]; then
|
||||
if PR=$(echo "$CI_PULL_REQUEST" | grep -oP '(?<=^https://github.com/weaveworks/weave/pull/)[0-9]+$'); then
|
||||
echo "GH_PR=$PR" >>"$STATE"
|
||||
if FILES=$(curl -s "https://api.github.com/repos/weaveworks/weave/pulls/$PR/files" | jq -r '.[]|.filename'); then
|
||||
echo "PR touches [$FILES]"
|
||||
if ! echo "$FILES" | grep -v ^site/; then
|
||||
echo "PR contains site changes only - skipping tests"
|
||||
echo "TEST_AND_PUBLISH=" >>"$STATE"
|
||||
else
|
||||
echo "PR contains non-site changes - running tests"
|
||||
fi
|
||||
else
|
||||
echo "Unable to get files for PR $PR" >&2
|
||||
fi
|
||||
else
|
||||
echo "Unable to extract PR number from $CI_PULL_REQUEST" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
source "$STATE"
|
||||
|
||||
mkdir -p "$(dirname "$SRCDIR")" && cp -r "$(pwd)/" "$SRCDIR"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ]; then
|
||||
cd "$SRCDIR"
|
||||
git submodule update --init
|
||||
fi
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
source "$STATE"
|
||||
|
||||
site_changes() {
|
||||
curl -s https://api.github.com/repos/weaveworks/weave/pulls/$1/files |
|
||||
jq -r '.[]|.status + " " + .filename' |
|
||||
grep -oP '^(added|modified|removed) site/.*(?=\.md$)'
|
||||
}
|
||||
|
||||
format_comment() {
|
||||
if [ ! -d .git ] || ! BRANCH=$(git symbolic-ref --short HEAD) || [ -z "$BRANCH" ] ; then
|
||||
echo "Could not determine branch" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "#### Documentation changes:"
|
||||
while read ACTION PAGE; do
|
||||
echo "$ACTION [${PAGE#site/}]($WP_DEV_URL/docs/net/${BRANCH#issues/}/${PAGE#site/}/)"
|
||||
done | sort
|
||||
}
|
||||
|
||||
post_comment() {
|
||||
jq -s -R '. | tostring | { body: . }' |
|
||||
curl -s -d @- \
|
||||
-H "Authorization: Bearer $WP_GH_COMMENT_TOKEN" \
|
||||
-H 'Content-Type: application/json' \
|
||||
https://api.github.com/repos/weaveworks/weave/issues/$GH_PR/comments > /dev/null
|
||||
}
|
||||
|
||||
cd $SRCDIR
|
||||
bin/install-wordepress v1.0.0
|
||||
bin/publish-site "$WP_DEV_URL" "$WP_DEV_USER" "$WP_DEV_PASSWORD"
|
||||
|
||||
if [ -n "$WP_GH_COMMENT_TOKEN" -a -n "$GH_PR" ] ; then
|
||||
echo ">>> Adding doc changelog to GitHub PR $GH_PR"
|
||||
if CHANGES=$(site_changes $GH_PR) && [ -n "$CHANGES" ] ; then
|
||||
echo "$CHANGES" | format_comment | post_comment
|
||||
fi
|
||||
fi
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
||||
cd $SRCDIR
|
||||
make clean-bin
|
||||
# CircleCI doesn't let us install the qemu module to do ARM docker builds
|
||||
make publish ALL_ARCHITECTURES=amd64 UPDATE_LATEST=latest-only
|
||||
fi
|
||||
|
||||
cd $SRCDIR
|
||||
bin/install-wordepress v1.0.0
|
||||
bin/publish-site "$WP_DEV_URL" "$WP_DEV_USER" "$WP_DEV_PASSWORD"
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cd $SRCDIR
|
||||
bin/install-wordepress v1.0.0
|
||||
bin/publish-site "$WP_LIVE_URL" "$WP_LIVE_USER" "$WP_LIVE_PASSWORD"
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||
if [ "$CIRCLE_NODE_INDEX" = "0" ] ; then
|
||||
go get github.com/mattn/goveralls
|
||||
goveralls -repotoken $COVERALLS_REPO_TOKEN -coverprofile=$SRCDIR/test/profile.cov -service=circleci
|
||||
cd $SRCDIR/test; cp coverage.* $CIRCLE_ARTIFACTS
|
||||
fi
|
||||
fi
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Signal failures in lock file, in order to fail fast:
|
||||
function signal_failure() {
|
||||
echo "KO" >"$TEST_VMS_READY_LOCK_FILE"
|
||||
exit 1
|
||||
}
|
||||
trap signal_failure ERR
|
||||
|
||||
source "$STATE"
|
||||
source "$(dirname "$0")/wait_for.sh"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ]; then
|
||||
[ -n "$SECRET_KEY" ] || {
|
||||
echo "Cannot run smoke tests: no secret key"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Provisioning from image typically take 90 seconds,
|
||||
# but if we are creating a GCP image, it can take more than 10 minutes:
|
||||
wait_for 900 "$TEST_VMS_PROV_AND_CONF_LOCK_FILE"
|
||||
|
||||
# Upload built binaries to testing VMs:
|
||||
cd "$SRCDIR/test" # Ensures we generate Terraform state files in the right folder, for later use by integration tests.
|
||||
./run-integration-tests.sh setup >>"$TEST_VMS_SETUP_OUTPUT_FILE" 2>&1
|
||||
echo "OK" >"$TEST_VMS_READY_LOCK_FILE"
|
||||
echo "Test VMs now ready. $(date)." >>"$TEST_VMS_SETUP_OUTPUT_FILE"
|
||||
fi
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
source "$(dirname "$0")/wait_for.sh"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ]; then
|
||||
[ -n "$SECRET_KEY" ] || {
|
||||
echo "Cannot run smoke tests: no secret key"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Provisioning from image typically take 90 seconds,
|
||||
# but if we are creating a GCP image, it can take more than 10 minutes:
|
||||
wait_for 900 "$TEST_VMS_READY_LOCK_FILE"
|
||||
|
||||
# Run integration tests:
|
||||
export COVERAGE=true
|
||||
export WEAVE_NET_SANITY_CHECKS_FILES="$CIRCLE_ARTIFACTS/weave_net_sanity_check_*.log"
|
||||
cd "$SRCDIR/test" # Ensures we generate code coverage files in the right folder, and use the Terraform state of previously provisionned VMs.
|
||||
./run-integration-tests.sh test
|
||||
fi
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||
cd $SRCDIR/test
|
||||
test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; ./gen_coverage_reports.sh)
|
||||
fi
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||
cd $SRCDIR
|
||||
make DEBUG=1 lint
|
||||
COVERDIR=test/coverage make RM= tests
|
||||
fi
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ $# -ne 1 ] ; then
|
||||
echo USAGE: install-wordepress [COMMIT-ISH] >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
go get github.com/weaveworks/wordepress
|
||||
cd /home/ubuntu/src/github.com/weaveworks/wordepress
|
||||
git checkout $1
|
||||
cd cmd/wordepress
|
||||
go get
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Signal failures in lock file, in order to fail fast:
|
||||
function signal_failure() {
|
||||
echo "KO" >"$TEST_VMS_PROV_AND_CONF_LOCK_FILE"
|
||||
exit 1
|
||||
}
|
||||
trap signal_failure ERR
|
||||
|
||||
source "$STATE"
|
||||
|
||||
function install_terraform() {
|
||||
curl -fsS https://releases.hashicorp.com/terraform/0.8.5/terraform_0.8.5_linux_amd64.zip | gunzip >terraform && chmod +x terraform && sudo mv terraform /usr/bin
|
||||
}
|
||||
|
||||
function install_ansible() {
|
||||
sudo apt-get update || true
|
||||
sudo apt-get install -qq -y python-pip python-dev libffi-dev libssl-dev && pip install --user -U cffi && pip install --user ansible
|
||||
export PATH="$PATH:$HOME/.local/bin"
|
||||
}
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ]; then
|
||||
[ -n "$SECRET_KEY" ] || {
|
||||
echo "Cannot run smoke tests: no secret key"
|
||||
exit 1
|
||||
}
|
||||
|
||||
install_terraform >>"$TEST_VMS_SETUP_OUTPUT_FILE" 2>&1
|
||||
install_ansible >>"$TEST_VMS_SETUP_OUTPUT_FILE" 2>&1
|
||||
|
||||
# Only attempt to create GCP image in first container, wait for it to be created otherwise:
|
||||
[ "$CIRCLE_NODE_INDEX" != "0" ] && export CREATE_IMAGE=0
|
||||
|
||||
# Provision and configure testing VMs:
|
||||
cd "$SRCDIR/test" # Ensures we generate Terraform state files in the right folder, for later use by integration tests.
|
||||
./run-integration-tests.sh configure >>"$TEST_VMS_SETUP_OUTPUT_FILE" 2>&1
|
||||
echo "OK" >"$TEST_VMS_PROV_AND_CONF_LOCK_FILE"
|
||||
echo "Test VMs now provisioned and configured. $(date)." >>"$TEST_VMS_SETUP_OUTPUT_FILE"
|
||||
fi
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
PRODUCT=net
|
||||
|
||||
fatal() {
|
||||
echo "$@" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ ! -d .git ] ; then
|
||||
fatal "Current directory is not a git clone"
|
||||
fi
|
||||
|
||||
if ! BRANCH=$(git symbolic-ref --short HEAD) || [ -z "$BRANCH" ] ; then
|
||||
fatal "Could not determine branch"
|
||||
fi
|
||||
|
||||
case "$BRANCH" in
|
||||
issues/*)
|
||||
VERSION="${BRANCH#issues/}"
|
||||
TAGS="$VERSION"
|
||||
;;
|
||||
*)
|
||||
if echo "$BRANCH" | grep -qE '^[0-9]+\.[0-9]+' ; then
|
||||
DESCRIBE=$(git describe --match 'v*')
|
||||
if ! VERSION=$(echo "$DESCRIBE" | grep -oP '(?<=^v)[0-9]+\.[0-9]+\.[0-9]+') ; then
|
||||
fatal "Could not infer latest $BRANCH version from $DESCRIBE"
|
||||
fi
|
||||
TAGS="latest"
|
||||
else
|
||||
VERSION="$BRANCH"
|
||||
TAGS="$VERSION"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
for TAG in $TAGS ; do
|
||||
echo ">>> Publishing $PRODUCT $VERSION to $1/docs/$PRODUCT/$TAG"
|
||||
wordepress \
|
||||
--url "$1" --user "$2" --password "$3" \
|
||||
--product "$PRODUCT" --version "$VERSION" --tag "$TAG" \
|
||||
publish site
|
||||
done
|
||||
+19
-4
@@ -89,6 +89,7 @@ build() {
|
||||
## Inject the version numbers and build the distributables
|
||||
## (library versions?)
|
||||
sed -i "/SCRIPT_VERSION=/ c\SCRIPT_VERSION=\"$VERSION\"" ./weave
|
||||
sed -i -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset.yaml
|
||||
make SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER
|
||||
|
||||
if make tests; then
|
||||
@@ -157,6 +158,13 @@ draft() {
|
||||
--name "weave" \
|
||||
--file "./weave"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG \
|
||||
--name "weave-daemonset.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset.yaml"
|
||||
|
||||
echo "** Draft $TYPE $RELEASE_NAME $VERSION created at"
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/$LATEST_TAG"
|
||||
}
|
||||
@@ -165,14 +173,14 @@ publish() {
|
||||
setup
|
||||
cd $PWD/$RELEASE_DIR
|
||||
|
||||
UPDATE_LATEST=false
|
||||
IS_MAINLINE=false
|
||||
if [ "$RELEASE_TYPE" = 'MAINLINE' ] ; then
|
||||
UPDATE_LATEST=true
|
||||
IS_MAINLINE=true
|
||||
fi
|
||||
|
||||
if [ "$RELEASE_TYPE" = 'PRERELEASE' ] ; then
|
||||
echo "== Tagging and pushing images on docker hub as user $DOCKERHUB_USER"
|
||||
make UPDATE_LATEST=$UPDATE_LATEST SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER publish
|
||||
make UPDATE_LATEST=$IS_MAINLINE SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER publish
|
||||
echo "** Docker images tagged and pushed"
|
||||
|
||||
echo "== Publishing pre-release on GitHub"
|
||||
@@ -204,7 +212,7 @@ publish() {
|
||||
echo '** Sanity checks OK for publishing tag' $LATEST_TAG as $DOCKERHUB_USER/weave:$VERSION
|
||||
|
||||
echo "== Tagging and pushing images on docker hub as user $DOCKERHUB_USER"
|
||||
make UPDATE_LATEST=$UPDATE_LATEST SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER publish
|
||||
make PUBLISH_WEAVEDB=$IS_MAINLINE UPDATE_LATEST=$IS_MAINLINE SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER publish
|
||||
echo "** Docker images tagged and pushed"
|
||||
|
||||
echo "== Publishing release on GitHub"
|
||||
@@ -236,6 +244,13 @@ publish() {
|
||||
--name "weave" \
|
||||
--file "./weave"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave-daemonset.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset.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,57 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
## This is the shape of unencrypted `do-setup-ci-secrets`
|
||||
##
|
||||
## cat > /tmp/boto_gsutil_cert.pem << EOF
|
||||
## -----BEGIN PRIVATE KEY-----
|
||||
## <...>
|
||||
## -----END PRIVATE KEY-----
|
||||
## EOF
|
||||
##
|
||||
## cat > ~/.boto << EOF
|
||||
## [Credentials]
|
||||
## gs_service_client_id = <...>
|
||||
## gs_service_key_file = /tmp/boto_gsutil_cert.pem
|
||||
##
|
||||
## [Boto]
|
||||
## https_validate_certificates = True
|
||||
##
|
||||
## [GSUtil]
|
||||
## content_language = en
|
||||
## default_api_version = 2
|
||||
## default_project_id = <...>
|
||||
## EOF
|
||||
|
||||
openssl base64 -d << EOF \
|
||||
| openssl aes-256-cbc -d -K $1 -iv $2 \
|
||||
-out bin/do-setup-ci-secrets
|
||||
mOYNzNsAdwva0LbB2YFMY9VgL0SITFu1bPrbOujyLtOrBNjmOYbzR5d/sG+ARMFr
|
||||
7NZ5OKcvMbItllz0n7TtNkzkCOMztMC870Wcx3nGJzbBephdcYwos4tBlp5bihTU
|
||||
AXgxLUiDT1broYAFKnbE1aZC/XyNxuLTN+nXJWIWeijaXTgvhkEEm3tab+z7ioCb
|
||||
tlg71W1BrUuocctAJAuVwH/uTZAaXaJGJj1s2S7F2DrFnG6sFlrp8rnCucPLWKnC
|
||||
4IwJFC5saR5gxK6wECffwmwbpeXVOoQ58OgzxoZTEyAr9nhVbaBt5UZz3tfHlx8W
|
||||
Rn5jHehACw5kg5eMtGPqMEksJ63LX4h3mfSTLJXJHEmOrzCRpFrigVrsbA408U2u
|
||||
8jJTHJmKrGfwBh5ZFzBg6NuV4VplOp+ZquJLgegv2WH/iu/ciopXqB2YDCdx5ZBi
|
||||
nHV2Puth7kjS4EMCKUGfeqpesjL9Djb8cygzv/J3ZBmLjNH9dqlSXWy2DcFZA1Iy
|
||||
pFTTHkVU3w5sNz3nYOuT2UPQvULBW0FUndDKX6olyVa0mlxPwGBP9N3yGg918/rw
|
||||
7rNV/r54lA334F5QMhDrVxar6sl9wfLA+Z+8qemBbNFlLog1uQGhuwWK2nFu1tRM
|
||||
9uyvQIq0oq5NHKpogWc7NEuk7CPe1JyfRIcbMVj8+ilmKjUPB2HPC43IDmKt9/56
|
||||
rY3IXAcxZ/ClGfGpeNHXqop9T/WhfaowponOZFcdGSUddG0OswqkTE4KIv71mgTF
|
||||
jXooMkTQBOG+gBnCe+8bCRdRCG3anxHX74za4dGD58cOJtl6DYWnjWuR91NWnxdT
|
||||
1Zr3iUe41txg9hCNY9LHo2QHGLPxe6pmNnSClQ1mcFhuY6NF8RmCX3HAGKJQkxJb
|
||||
vpLGbXTXjAIFgIYgLNgKP2RLvCif/LmSWgkvE3ZU9GXtCUbm6xgJodwOqXiI9uuO
|
||||
4H4+/vpGHnSFENiclkgMFrLG4yiMu8ArgmnCBzg9E2AoOX5zCrh/2ZRTU8Dk4mt7
|
||||
jUd5SKQLsjB9QtB+y3tRO7zY9RDvR6iBOPRsjx6t8EJMCfPb06KchfMJw+VGxvMM
|
||||
48jtmepREFnhd/8XDRGnw5bznjRuwohGik8BToltjIviMkkpifKWaLwlaOiDXaHh
|
||||
VBFNXnUKvOBwRMU+PZqv4+DP2qff1gSXnGKqA5GIURYrK9P7TqHHNTnmjBdfJLzc
|
||||
0x+W1DTiYhLT2vrLrkEpZ99o6qrg+lwvjat6IsJF8xoQEo4pLD/ZRPJ0WzoDyctT
|
||||
oWZ5rMsN1TzTvJrBrTAWIJyhIzZcuNwChMrU6qogMr0fd6TSxWhWn0sOJ5y8AaTR
|
||||
ej4JHx0qroS/9JPmgFYU61pX3c0ypdef/sSHWG23mcFzLTgwe2d4ty9VakypZy3T
|
||||
sXCl6EHVVXlUFWhY4zQCjdZpYJv/rtoKL1BL4fPE2NV5WTjCL2GNvaO5SfHR7pA0
|
||||
yQJR/iU+cS2n8UXsWNjyV+/qEn6+QXuyVHGLbxyH8EeqlliAbyHsc2rzVI8Xctzo
|
||||
W5DuIqxwHAkNNurhmx27ktIIQ6HhaCp028SWwvLttkbBBgF4v1mmd4u/2bKbKjUd
|
||||
dOaetaIUx1hU9OAeaePvVkeTDcc3mlFtPARCS9Fqj16hgJQ43onZyC0H1K2WAxmU
|
||||
GsmzjCHlTNquzFTuuQihYGZKfsWIh9z6HEOkvNJ3VEQ=
|
||||
EOF
|
||||
|
||||
exec sh bin/do-setup-ci-secrets
|
||||
@@ -1,115 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
# openssl enc -in do-setup-circleci-secrets.orig -out setup-circleci-secrets.orig -e -aes256 -pass stdin
|
||||
# openssl base64 < setup-circleci-secrets.orig
|
||||
|
||||
openssl base64 -d << EOF \
|
||||
| openssl enc \
|
||||
-out bin/do-setup-circleci-secrets \
|
||||
-d -aes256 -pass pass:$1
|
||||
U2FsdGVkX1/3QHnDgpZn0srp+GiEQBTlag0CBdsw6Bp6iaIKy6Jy8gFshrsmvl3+
|
||||
c+7hn1nqsBVpzVH0S5QA3Ex5nx9rl7/dcGoogsySFcwMmBFcauT0UTMK1bvX0Hqx
|
||||
QrshYSndUkww72KA7EsSnjR1BBJWm3AhCDOTIcnO6nQhig1Fc+SBEj5FGS5xTQ5Z
|
||||
/8hDsKf54Ms49vOO6QeE5w5ndZ+k7IvVJfq9Evhb7htD20RntDhR86foAwMnLV7R
|
||||
Wh9qAqI3gnRq0GmbeRKaHmB7pcLq9f0OdScqM/Tn1D+q0556fqJjBziXpZiGbxVs
|
||||
DxEp8zDJ8yNDAi0Wb2NXjoA79SXJ8Q7iTieaST4y2OmXQpY+Vw8RVDXxi6ERyVnH
|
||||
41OeMzyLm0qvCoMl32pe1sSG5DRCUiAm7wOtDTVvNDRqmGJYUgwwexyayecJI1wQ
|
||||
LlhYLJavUvw3UGDZOld8BaMQhfOKBVCdFrjmA4HuVxmOf3029qkgzOCSZiQVNqs+
|
||||
fvM6J05zBuk+GPy8YInvRkzfxum/x00vmUyGjMs/lHDFfrmYDFK3ux41EoVqvWLG
|
||||
S5uYxdZJFdR3Xsrs+sMr51XNBQGMmz6KoEEzFYyHs5fZgtdpRdPAuQS1m6FqgZyi
|
||||
wSz6+EKsALtmGorEp+CF8JN5YzKJCrKzNkg0fILBqsqmJZGP64lmX69950lRLNGM
|
||||
/l3KVR2zH31b4AnnzTcvldVSCKDXjaMw3JA2Ce/MK6XhOWOcpEv4oDt/62lbhUKP
|
||||
QUQTPbfpQRvVfBcoIrfAm3ghi/CZc2TDVEuD7DhasLTZD3r7LoK261BWC9O5Wubi
|
||||
vlXuFIF+uyrpTDf6EJKE/+XywtArppffMP6RkHu1Iqnd8JtsqPaIZsmMmZbjgb6j
|
||||
BX4ZLb5U9R7jYoSgIke4+u6geFcrMQ+Lny4r7MxXaS0Y1UJ6Jo1bwpYw1TBIobZc
|
||||
eQ30+xhbSeRC9vEw1NXI6qM2Ai2gIHtAZvEiBkBBXTG5Ld7N/mBAmDIJEzVRxh6F
|
||||
jRkB3dzESF6eeTBm61OQepwV3aW6lslMaf26drPGv2W2q+K/WOpQOejfggtHUgVs
|
||||
9SWqtn7hHYSpIASe1Ihi14HAWv3kPhQien0zY1DLKJWf4ilbRaICn1QSJjNuKwtl
|
||||
A2ZwEw7FFddFcsF4okHPkxEoCre3shq0DEw9UA1gnwEaEF1Q+i9cvLtQg56zK3+R
|
||||
MfTzFUv2K1z3j1s5tC6Z6LzL8j682EFOll7MkihqiIWh+po1zcNs6Ln0SkjB3v9T
|
||||
OFRYVJis9GqXYO9PUW1mI69udR4nXbKlBl5tWNyAxn9hufJHLSAmbGMwActKLRM5
|
||||
kROapKrN1Ra/sHUmhoSSDUK9AXeYKkqPXVMbhly9xC1Kya4hFaFokjRQIMLPmXdQ
|
||||
lP46RL3HOq0v1BkXg05poSwDm+BtitZkcIhOzcmeucUqn1whsz39y3VoOhJqQu2j
|
||||
0dUcj19Fue1CDZfwzACb9mLZr643h0yCIHQaVT7is/yiN2vEt0S0zynvOA1D+Xgy
|
||||
4bFdjqed9DgBShLwF/lh4/hur5iZPpVr9A8rt3Vq0k5A1tdjsZpXP3qv5jLc7lJv
|
||||
cAQxdVLEuv8WPwwTV/EQsoH7lpoEdjktw/gu0v5PkUgR/WBxEqJq4liTekViQ+8X
|
||||
BAmdm/FGWeIxSSEm0a8I+muiBpIuKjuQoJit1f0k+lLUohoVDqpDDfeYHh1TxXHE
|
||||
xSKAKNmFSc2XOr5Zg2mOInxeH7WoxH6iylXKADBgEgAQ4C55DoAPRjFNtGGz2iJl
|
||||
WkzhQOfgsDss1VxpPZ4SWDQO+xxuuEnYcCYdJ6Tyi5hAwZ0+RZmXx0g9BCK+5qzc
|
||||
Ffei066kCItBcayekwCs0i86r8JvDtsMUUkzEKDQTSWHpP8ypIZoz3P+Skdqcs6p
|
||||
IcZHy2fODIsClctRAsHOjZ9BnWSY2rqf1lysqvRfOkNTViTOUVx2hf+LuL+8rT6g
|
||||
K4GvS39Q+c9FYSMjCNxv1m37NfHJv2W7AlqVV95gMBEFB97M3388T7PfvmSLsyPv
|
||||
qAKDE7genTOycQvEX9oeEACxPo08ai2Mgs/YXCig0g0btZoScoKPelQKtD0HLpZ+
|
||||
j7ruaKFy4/vySDMostB3AKSkCE/SkGu/W9oI37n1guSYC/9yJjtTcwZULXAClfwt
|
||||
LyHvLAw4+7BdGJqDe9Tf8z4zqVLax+BcBHkW7pbP+pvxC6fhIA4PO8k5HwwAVlRw
|
||||
68lG6e3z9+Vst4zrFLewHJvkDRSOTSecs9h9SPOjReTK0ovlDzMHVakfUvqpk9mv
|
||||
nCj8SWaMPF70z138AuRuKN7X4exNXPBFnOcseKp/NpT1DyvgoHm/oIO65mq9vvo+
|
||||
42uSbyyegYiTdVK4SaXBc2hixkhaTteqdEy6GxfWqKCvHtYuRD2LyUSjZn/0Y6CG
|
||||
3wZapylKL1sxfH+b1gogv2wAA8KtZAfiTlMISuymwQr5RbOFma7UVMVecCa7XrNG
|
||||
fZwO8R+6++ru0efDzeC/gtLOItjn+yVfsbk2gfu0FikHIV1sogOjnsv3dl6IpH/s
|
||||
w6WbuK/yZhyrrmhIIueHF30ES512Peogf/Gxp8U/ZAmqBa7p4qsv93XWDR52IPYz
|
||||
yU1MtboLD02J39daBNrYZuO4Yfo/xAvU7Ijg2KIR9HS0bSWNuSFAZaZXlJ2bSag3
|
||||
8l0dZMx2fZa0FALxWvY2+nd1tWcH+Wo99RSxX+4TH3guCpe1x/58yKOKL4d1xRpu
|
||||
02nGaqerV9bHU6WjCUYTrrZurTp6yqivpSFCALDMhAU5Ihd/Qjz69zg4uZ7iqbNd
|
||||
WqIYJ3SAOei2tSfoOJbdh8mohotnMFFbupck3Fv+lXAXYDp0V/sSuK32MSeJxan1
|
||||
q3t0eenAh6zoBP0Tj53N8B9YmrgZ1i1AljQqYsAdb8N5Z3vecuzQa946U8H4axEc
|
||||
BL5fgmqSfsbj1EjJVUrw3cE9hgqeqAYBBvZsyW5zSAsLq/RJKYtBxDGADqr0TOOu
|
||||
TjVqfPObb1u9EAQj15ALBhhPORa2gBAK5gnhJWW5deNgA0UOYoWARFYfATuQvr1T
|
||||
9xTVbqzZ0VJR4n7X5JyxJo2tZ44QYVzA0fqX0lt+uRkarrkbZMgplKp4MJTFCp2N
|
||||
tLciWS0YSHPbKTm7svw5mROazUvEoj1N3SYrJT+Ai9UgSL1cA4FmQ5Jsi+GOeQbH
|
||||
7E4pzjeST4b1aATY9ZZSy4TzK+UNYBcCVqY1dN5Xnk0e5yPMnrkkcYI41s/6oOaW
|
||||
lQkp/yrHkvpzQWZa7vF2yWg54j3eX8YWx/TsLvmuJmSQqiIHfu9TXcAIPDispCpF
|
||||
gp4ZbYPZ8RDQLw3ROc+bZ9E0J8e+FHwgXEELsMLW9n4d3PTj9pLFej1vmBKa03o3
|
||||
g+JViNxb1gUPNmC6ADk4o7TQzBrjV2pmmuCza16kOmq6oAbJPOeCLtVWA5HJTX8d
|
||||
AuOzBLO37tWP457dNqoNDaCMik3LCy8cslZjAz02D6VG/QGOAXP9x8bJ3zg9N7vW
|
||||
EOn1BMtIeMI4yIxpC6HgotNWDNbUBWremAFWYQRgYQqo9L1SdW4hz6X8FqdlBFvK
|
||||
ml7sdUwjEy2tNW+aiZI4cgAlI/VvMWYr5kPfhvaUu1wGYeS1TD1B+DPcjcXpOGWo
|
||||
vB1f/uvGc61zZWD0ph9f3S9OnvEko5qV3VhxOnxYUZTrqlM7LVgy/sHrSaPdR7Eo
|
||||
nb4QGdi5wEGMxVgQlDGwOmEmS5nCaA0RcfHbkQV4SebRleocLB9qcSwj5JzPICAB
|
||||
sn1qeSv45LnGdTzUnCQErw1EzgrK1FXOknujjZ3GX5dM7a6Gag0E6cAxoQeC8Mh0
|
||||
aUWolyBo9aSXltn7Vnywoe4X3v96eVH6tUQtN3wt51LBlfSDTWOQDeXLFyW57oYL
|
||||
/ErbmJLaSHpANBcC9InI+rxNYePTTba7K8rdPDppmMEmdOXjaL+5aPay2Bay9K8T
|
||||
FUyZdoyrbzxFZYT9/YyVJV/ukVcT9ru3HpzLTg9Vgc/CyRfIvCZY/Gzh+pPUbRsR
|
||||
pHYDT09BFS+mjszTkcdbceweYXJvknXpajMiISrPUUkb9rwCTmLS4QR4y7I9zAgJ
|
||||
9U72mCZB9yhhzS17kyKAANihXvOFrMXCO7QTWZbtSkobMBS2SybDxYP3rM5b6824
|
||||
HQaY4MZW5qCndydfz4fa908XbPTDka9Nu3CZfI3fhGiiGDFWViotGEYE4i9BuGZJ
|
||||
vmtzII7zeIoHwM4t5fb5mYDd9H+Rkct1yATGEIFEx7cu1fj4Ht2177EzhdkF3Q6I
|
||||
Fccn43yGDW06nFvEjnjb+Lz4EvtGp6dDefUhNBohdzd4Rm2o+etKobm0lZ1xOX74
|
||||
2gOGBLfXS6PF+msNkWP76mqKE6hG0+R1iPrN06Arti+ZRBhNGJDp1pnOERl7IyB0
|
||||
hiLUqae7xVSMVRDeq4O04EUpMXh5ixs3RJXjthYILt7vX+6qskJC3rMuyThrsvMi
|
||||
Kk0wErDpzR5U7KFnM2fk4jY5by/0b/jHZ/PWFGnaFrXNOiwK/mV1RIJtg5FCrvMv
|
||||
xJN5Qo7o2ZYzUL+IBf5mP5O49FCnz3e++9r4ySY/K57ybhQft1l+cQPWS7xuvVAg
|
||||
wZGM9DPl9AhBus0I3V3XzfkcWWHkMynMRcfTnkBHrmKjMopIOggsq05JPalM9b0V
|
||||
NfwZngVvTFbQejbGjgQQQ+G7spav/icCIA8Iu8JPHaKZt2IX77L1xMcQOouqMrx8
|
||||
JCXnJ7zsuU+8W3GQGm70H2kcen4bf4beA8TSkFmbVsaqDNtGaG+ZopWO8ctZX3CP
|
||||
jFgIYgEGi137IWvs1KD5QNFHZI6l0JMzglmvcfnViRPDHLi+EQXAoL/98j/YzA9t
|
||||
a9L4xMO0oucovKkqYOS2cdFaQKVIUM9L5ibcbW7Tol4ugMo5ibIsTgjZoyFohG6t
|
||||
acg+h2wTTyxtrIapvOOBeAo9V/iQowTAPQf38LKQZ5tApksiCG1qvJerKALTMp1J
|
||||
yUHjTCPCPsaiXVVO0mfa9OPtzVvi/icLmlz7umk82mew13djedgMS3/ghq5aEHbg
|
||||
e7rtI5snFlm/GQnV6IaCDytb+jYxqQ0DZ9Mb0CHcMpnLyiBO6nzHfadpZYlrxrpx
|
||||
dPx9MVqfLavj1UVjKuVj4Mgec9J38IBBRGMJZ4MGhde2hrw/s2jVzG1BzaIzcvFa
|
||||
bWiJOkKKRC4BY9DBDYfae5c/obwzkxLmvkbWAVzoFXc+46K1IK6bbC2a4EerOAxc
|
||||
C7B3X8BucD8OtiPORwXgjctd9AJ7cXq0EEVGoZZ8jkILnklsK6RXsxrT4gSOGFpx
|
||||
cBU7TF4kHpH+o4KGQmjlFA2VYP1tXirjEep57LOHfL+zy+HiSXdsbexf6odkqLYA
|
||||
1RR0TzSkmqJYvP0GfHPMvp2xredx1kP934fhTIUcWvErrG3NPd/Dod4BzQye1nNK
|
||||
8Y8aFYF6fACcD4SFZ11ecK9DJmB+WXGlsxtO3w1LeEysny2lVkkH6HuMRIzPyXAM
|
||||
/sEpwh3hwPJosAh6vGM6JxuL7CXP8hC1oUvK91VR0hxeDcO/LgdcHtDnlNMqt1wC
|
||||
jvYt9Hg+3aY5GehBnQPRppnIMaGhiBrRC6HjBg8Umt6UTUr5PXNY2aia5wRcx4M6
|
||||
Tj0O0dAt8WWZ9ySgzOoq4SRoaAp6m4E7CpRNMgXH031OWEeHqtt8rsYqAcvcQLuW
|
||||
vIwts5hiXZOfgXyR6SBY3ANE1SjDY46na2G4oiG8FI7eAqYgehjHrXqCnGrzjefE
|
||||
V1eVcIr7DBYuM8wq7C9x+qdMir6RNV4MrVD+7rO/drivnd3MaqDHlG0LfMZo6GIK
|
||||
QZGIy0z1W5lOXm9x3RAz7/Ea11ynkZyPDDxBE5NRcV6JfukdjDrvsaQA/Qk2Wo7h
|
||||
PdUZjv17H4syMdap6tUDTc8Cohut6n1UxnDnhaAYEDGR3TgPqI5vEaJ+yoaM9itv
|
||||
b2I/K+0uK6GfWwca7PeTtYn92/5etO4czG99xEBFzw7tiEmBTA8KfseZIUe/i3fr
|
||||
9LO+rKEGalFB4mVTYyCKPYYx4hOIvjdCox/jEBTFyYUEhgGnZvArgw9XabvII7eM
|
||||
c1FmwnN2CM/MHUzM0Kw7cS/oaoRFcG/PqioO/krL2Bd+xThwSH4Zn+Y1UVO7aXNy
|
||||
90MOehHSc2nhN984Y2re3ffVsjPhC3KdWxr8jbho/Az65rUkYg64roiO2ZqdqRvf
|
||||
aYtX9fCe8N8SzgLTlWZt+l8sjY8HP2k9NoajphV1nG4iC/pX0ZJiVQ+VBRNaGhSl
|
||||
6IhM9gM1ZsI86cULJpOTAhNu1fZBv+Mq8IXYD8SqlNVwVtjvshtlzBN4InSGEKiO
|
||||
HM7coo7aDuu+Go5AEiIVMbR09jAa0VINsCVScfSbTFhlGRTrZr2DSO/s8WxbV+Mb
|
||||
EQAfZmyrLF71Z3MfIukg+ps5iuqFhZ+gJSXXSFwumpg=
|
||||
EOF
|
||||
|
||||
exec sh bin/do-setup-circleci-secrets
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
# shellcheck disable=SC2155
|
||||
wait_for() {
|
||||
local timeout=$1
|
||||
local lock_file=$2
|
||||
for i in $(seq 1 "$timeout"); do
|
||||
if [ -f "$lock_file" ]; then
|
||||
local status="$(cat "$lock_file")"
|
||||
if [ "$status" == "OK" ]; then
|
||||
echo "[$i seconds]: $lock_file found and status: $status." && return
|
||||
else
|
||||
echo "[$i seconds]: $lock_file found and status: $status." && return 1
|
||||
fi
|
||||
fi
|
||||
if ! ((i % 10)); then echo "[$i seconds]: Waiting for $lock_file to be created..."; fi
|
||||
sleep 1
|
||||
done
|
||||
echo "Timed out waiting for test VMs to be ready. See details in: $TEST_VMS_SETUP_OUTPUT_FILE" >&2
|
||||
return 1
|
||||
}
|
||||
+82
-6
@@ -1,8 +1,84 @@
|
||||
FROM golang:1.5.2
|
||||
ENV GO15VENDOREXPERIMENT 1
|
||||
RUN apt-get update && apt-get install -y libpcap-dev python-requests time file
|
||||
RUN go get github.com/golang/lint/golint github.com/fzipp/gocyclo github.com/client9/misspell/cmd/misspell
|
||||
RUN go clean -i net && go install -tags netgo std
|
||||
RUN go install -race -tags netgo std
|
||||
FROM golang:1.7.4
|
||||
|
||||
# Support Raspberry Pi 2 and newer
|
||||
ENV GOARM 7
|
||||
|
||||
# The names of the architectures that we're fetching cross-compilers for from the Ubuntu packages
|
||||
ENV DEB_CROSSPLATFORMS \
|
||||
armhf \
|
||||
arm64
|
||||
|
||||
# The go platforms we are supporting to cross-compile
|
||||
ENV GO_CROSSPLATFORMS \
|
||||
linux/arm \
|
||||
linux/arm64
|
||||
|
||||
# The names of the gcc cross-compilers we have installed
|
||||
ENV GCC_CROSSCOMPILERS \
|
||||
arm-linux-gnueabihf \
|
||||
aarch64-linux-gnu
|
||||
|
||||
# Temporarily add the Ubuntu repositories, because we're going to install gcc cross-compilers from there
|
||||
# Install the build-essential and crossbuild-essential-ARCH packages
|
||||
RUN echo "deb http://archive.ubuntu.com/ubuntu xenial main universe" > /etc/apt/sources.list.d/cgocrosscompiling.list \
|
||||
&& apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5 3B4FE6ACC0B21F32 \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y build-essential \
|
||||
&& for platform in ${DEB_CROSSPLATFORMS}; do apt-get install -y crossbuild-essential-${platform}; done \
|
||||
&& rm /etc/apt/sources.list.d/cgocrosscompiling.list \
|
||||
&& apt-get update \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install some required packages
|
||||
# libpcap is required because we're linking against its C libraries from the prog/weaver binary
|
||||
# flex and bison are required packages for compiling libpcap manually later
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
libpcap-dev \
|
||||
python-requests \
|
||||
time \
|
||||
file \
|
||||
flex \
|
||||
bison
|
||||
|
||||
# Install common Go tools
|
||||
RUN go get \
|
||||
github.com/golang/lint/golint \
|
||||
github.com/fzipp/gocyclo \
|
||||
gopkg.in/mvdan/sh.v1/cmd/shfmt \
|
||||
github.com/fatih/hclfmt \
|
||||
github.com/client9/misspell/cmd/misspell
|
||||
|
||||
# First clean, because we're gonna rebuild the std both with -race enabled and disabled
|
||||
# Running go install std twice is intentional and required, as content is written in two different directories
|
||||
# (/usr/local/go/pkg/linux_amd64 vs /usr/local/go/pkg/linux_amd64_race)
|
||||
# The other architectures do not have support for -race
|
||||
RUN go clean -i net \
|
||||
&& go install -tags netgo std \
|
||||
&& go install -race -tags netgo std
|
||||
|
||||
# Prebuild std for the other architectures
|
||||
RUN for platform in ${GO_CROSSPLATFORMS}; do \
|
||||
GOOS=${platform%/*} GOARCH=${platform##*/} \
|
||||
go install -tags netgo std; done
|
||||
|
||||
# Allow full write access to the Go folders for anyone
|
||||
RUN chmod -R a+w /usr/local/go
|
||||
|
||||
# The libpcap version from Debian packages is 1.6.2, matching that version here although newer versions of libpcap have been released
|
||||
# We have to cross-compile the libpcap library for the new architectures, that's what we're doing here
|
||||
ENV LIBPCAP_CROSS_VERSION=1.6.2
|
||||
RUN curl -sSL http://www.tcpdump.org/release/libpcap-${LIBPCAP_CROSS_VERSION}.tar.gz | tar -xz \
|
||||
&& cd libpcap-${LIBPCAP_CROSS_VERSION} \
|
||||
&& for crosscompiler in ${GCC_CROSSCOMPILERS}; do \
|
||||
CC=${crosscompiler}-gcc ac_cv_linux_vers=2 ./configure --host=${crosscompiler} --with-pcap=linux \
|
||||
&& make \
|
||||
&& export LIB_DIR="/usr/local/lib/${crosscompiler}-gcc" VER="$(cat ./VERSION)" MAJOR_VER="$(sed 's/\([0-9][0-9]*\)\..*/\1/' ./VERSION)" \
|
||||
&& mkdir -p ${LIB_DIR} \
|
||||
&& cp -f libpcap.a libpcap.so.${VER} ${LIB_DIR} \
|
||||
&& ln -sf libpcap.so.${VER} /usr/local/lib/libpcap.so.${MAJOR_VER} \
|
||||
&& ln -sf libpcap.so.${MAJOR_VER} /usr/local/lib/libpcap.so \
|
||||
&& make clean; done
|
||||
|
||||
COPY build.sh /
|
||||
ENTRYPOINT ["sh", "/build.sh"]
|
||||
@@ -0,0 +1 @@
|
||||
8f313a9df867c207ae9ceac6e0935d1d2a0360aafce780dd5d1e0687ee98e0c3 docker-1.10.3.tgz
|
||||
@@ -0,0 +1 @@
|
||||
7541b5490cee1183d911f909c0e91d3d9d57b2b775e40f14a1a7a6de216ca4a8 docker-1.10.3.tgz
|
||||
@@ -0,0 +1 @@
|
||||
c02d861383d035ba020b361ef93bf14beb3964a42a6ae645b505b6148493e7ec qemu-aarch64-static
|
||||
@@ -0,0 +1 @@
|
||||
0ff63682b8868620d771aa812d3053dc3c579dc8633ce62d3d7ae7eee83beb06 qemu-arm-static
|
||||
+41
-29
@@ -2,63 +2,75 @@ general:
|
||||
branches:
|
||||
ignore:
|
||||
- gh\-pages
|
||||
- /pull\/.*/
|
||||
|
||||
machine:
|
||||
pre:
|
||||
- curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0
|
||||
services:
|
||||
- docker
|
||||
environment:
|
||||
GOPATH: /home/ubuntu:$GOPATH
|
||||
SRCDIR: /home/ubuntu/src/github.com/weaveworks/weave
|
||||
PATH: $PATH:$HOME/.local/bin:$HOME/google-cloud-sdk/bin/
|
||||
PATH: $PATH:$HOME/.local/bin
|
||||
CLOUDSDK_CORE_DISABLE_PROMPTS: 1
|
||||
STATE: /home/ubuntu/state.env
|
||||
# Base name of VMs for integration tests:
|
||||
NAME: test-$CIRCLE_BUILD_NUM-$CIRCLE_NODE_INDEX
|
||||
TEST_VMS_SETUP_OUTPUT_FILE: $CIRCLE_ARTIFACTS/test_vms_setup_output.txt
|
||||
TEST_VMS_PROV_AND_CONF_LOCK_FILE: $CIRCLE_ARTIFACTS/test_vms_provisioned_and_configured.txt
|
||||
TEST_VMS_READY_LOCK_FILE: $CIRCLE_ARTIFACTS/test_vms_ready.txt
|
||||
# Google Cloud Platorm images for integration tests:
|
||||
USE_IMAGE: 1
|
||||
DISK_NAME_PREFIX: test-$CIRCLE_BUILD_NUM-0
|
||||
|
||||
dependencies:
|
||||
cache_directories:
|
||||
- "~/docker"
|
||||
post:
|
||||
- "sudo apt-get update && sudo apt-get install bc jq"
|
||||
- curl https://sdk.cloud.google.com | bash
|
||||
- bin/setup-circleci-secrets "$SECRET_PASSWORD"
|
||||
- "mkdir -p $(dirname $SRCDIR) && cp -r $(pwd)/ $SRCDIR"
|
||||
- "cd $SRCDIR; git submodule update --init"
|
||||
- cd $SRCDIR/test; ./gce.sh make_template:
|
||||
parallel: false
|
||||
- "cd $SRCDIR/build; ../tools/rebuild-image weaveworks/weavebuild . Dockerfile build.sh && touch $SRCDIR/.build.uptodate"
|
||||
- bin/circle-dependencies-post-sources
|
||||
# Provision & configure testing VMs:
|
||||
- "while [ ! -d $SRCDIR ]; do sleep 1; done; $SRCDIR/bin/provision_test_vms.sh":
|
||||
parallel: true
|
||||
background: true
|
||||
- bin/circle-dependencies-post
|
||||
|
||||
test:
|
||||
pre:
|
||||
- "$SRCDIR/bin/circle-test-pre":
|
||||
parallel: true
|
||||
background: true
|
||||
override:
|
||||
- cd $SRCDIR; make lint:
|
||||
- "$SRCDIR/bin/circle-test-unit":
|
||||
parallel: true
|
||||
- cd $SRCDIR; COVERDIR=test/coverage make RM= tests:
|
||||
parallel: true
|
||||
- cd $SRCDIR; make COVERAGE=true RM= all:
|
||||
parallel: true
|
||||
- cd $SRCDIR; make testrunner:
|
||||
parallel: true
|
||||
- cd $SRCDIR/test; ./gce.sh setup && eval $(./gce.sh hosts) && ./setup.sh:
|
||||
parallel: true
|
||||
- cd $SRCDIR/test; eval $(./gce.sh hosts); export COVERAGE=true; ./run_all.sh:
|
||||
- "$SRCDIR/bin/circle-test-smoke":
|
||||
parallel: true
|
||||
timeout: 300
|
||||
post:
|
||||
- cd $SRCDIR/test; ./gce.sh destroy:
|
||||
parallel: true
|
||||
- test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; ./gen_coverage_reports.sh):
|
||||
- "$SRCDIR/bin/circle-test-teardown":
|
||||
parallel: true
|
||||
|
||||
teardown:
|
||||
pre:
|
||||
- test "$CIRCLE_NODE_INDEX" != "0" || (go get github.com/mattn/goveralls && goveralls -repotoken $COVERALLS_REPO_TOKEN -coverprofile=$SRCDIR/test/profile.cov -service=circleci || true):
|
||||
# Destroy testing VMs:
|
||||
- "cd $SRCDIR/test && ./run-integration-tests.sh destroy":
|
||||
parallel: true
|
||||
- test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; cp coverage.* $CIRCLE_ARTIFACTS):
|
||||
background: true
|
||||
- "$SRCDIR/bin/circle-teardown-pre":
|
||||
parallel: true
|
||||
|
||||
deployment:
|
||||
hub:
|
||||
release:
|
||||
branch: /[0-9]+\.[0-9]+/
|
||||
owner: weaveworks
|
||||
commands:
|
||||
- "$SRCDIR/bin/circle-deploy-release"
|
||||
master:
|
||||
branch: master
|
||||
owner: weaveworks
|
||||
commands:
|
||||
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
||||
- cd $SRCDIR; make clean-bin
|
||||
- cd $SRCDIR; make publish
|
||||
- "$SRCDIR/bin/circle-deploy-master"
|
||||
issues:
|
||||
branch: /.*/
|
||||
owner: weaveworks
|
||||
commands:
|
||||
- "$SRCDIR/bin/circle-deploy-issues"
|
||||
@@ -84,6 +84,15 @@ func (c *Client) Info() string {
|
||||
return fmt.Sprintf("Docker API on %s: %v", c.Endpoint(), env)
|
||||
}
|
||||
|
||||
func (c *Client) DockerVersion() string {
|
||||
if env, err := c.Version(); err == nil {
|
||||
if v, found := env.Map()["Version"]; found {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// AddObserver adds an observer for docker events
|
||||
func (c *Client) AddObserver(ob ContainerObserver) error {
|
||||
go func() {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type loggingHandler struct {
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
func (l *loggingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
Log.Debugf("[http] %s %s", r.Method, r.URL.RequestURI())
|
||||
l.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func LoggingHTTPHandler(h http.Handler) http.Handler {
|
||||
return &loggingHandler{next: h}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package mflagext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
type listOpts struct {
|
||||
value *[]string
|
||||
hasBeenSet bool
|
||||
}
|
||||
|
||||
func ListVar(p *[]string, names []string, value []string, usage string) {
|
||||
*p = value
|
||||
mflag.Var(&listOpts{p, false}, names, usage)
|
||||
}
|
||||
|
||||
func (opts *listOpts) Set(value string) error {
|
||||
if opts.hasBeenSet {
|
||||
(*opts.value) = append((*opts.value), value)
|
||||
} else {
|
||||
(*opts.value) = []string{value}
|
||||
opts.hasBeenSet = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opts *listOpts) String() string {
|
||||
return fmt.Sprintf("%v", []string(*opts.value))
|
||||
}
|
||||
+7
-2
@@ -21,8 +21,13 @@ func CreateDatapath(dpname string) (supported bool, err error) {
|
||||
defer dpif.Close()
|
||||
|
||||
dp, err := dpif.CreateDatapath(dpname)
|
||||
if err != nil && !odp.IsDatapathNameAlreadyExistsError(err) {
|
||||
return true, err
|
||||
if err != nil {
|
||||
if !odp.IsDatapathNameAlreadyExistsError(err) {
|
||||
return true, err
|
||||
}
|
||||
if dp, err = dpif.LookupDatapath(dpname); err != nil {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
|
||||
// Pick an ephemeral port number to use in probing for vxlan
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func AllPids(procRoot string) ([]int, error) {
|
||||
fh, err := os.Open(procRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fh.Close()
|
||||
dirNames, err := fh.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pids := make([]int, len(dirNames))
|
||||
for _, dirName := range dirNames {
|
||||
pid, err := strconv.Atoi(dirName)
|
||||
if err != nil { // Only interested in numeric entries - skip /proc/stat, etc.
|
||||
continue
|
||||
}
|
||||
pids = append(pids, pid)
|
||||
}
|
||||
return pids, err
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// A subsystem/server/... that can be stopped or queried about the status with a signal
|
||||
type SignalReceiver interface {
|
||||
Stop() error
|
||||
}
|
||||
|
||||
func SignalHandlerLoop(ss ...SignalReceiver) {
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
|
||||
buf := make([]byte, 1<<20)
|
||||
for {
|
||||
switch <-sigs {
|
||||
case syscall.SIGINT, syscall.SIGTERM:
|
||||
Log.Infof("=== received SIGINT/SIGTERM ===\n*** exiting")
|
||||
for _, subsystem := range ss {
|
||||
subsystem.Stop()
|
||||
}
|
||||
return
|
||||
case syscall.SIGQUIT:
|
||||
stacklen := runtime.Stack(buf, true)
|
||||
Log.Infof("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end", buf[:stacklen])
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-138
@@ -1,14 +1,6 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netns"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
import "strings"
|
||||
|
||||
// Assert test is true, panic otherwise
|
||||
func Assert(test bool) {
|
||||
@@ -24,132 +16,3 @@ func ErrorMessages(errors []error) string {
|
||||
}
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
|
||||
func WithNetNS(ns netns.NsHandle, work func() error) error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
oldNs, err := netns.Get()
|
||||
if err == nil {
|
||||
defer oldNs.Close()
|
||||
|
||||
err = netns.Set(ns)
|
||||
if err == nil {
|
||||
defer netns.Set(oldNs)
|
||||
|
||||
err = work()
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type NetDev struct {
|
||||
MAC net.HardwareAddr
|
||||
CIDRs []*net.IPNet
|
||||
}
|
||||
|
||||
// Search the network namespace of a process for interfaces matching a predicate
|
||||
func FindNetDevs(processID int, match func(link netlink.Link) bool) ([]NetDev, error) {
|
||||
var netDevs []NetDev
|
||||
|
||||
ns, err := netns.GetFromPid(processID)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer ns.Close()
|
||||
|
||||
err = WithNetNS(ns, func() error {
|
||||
return forEachLink(func(link netlink.Link) error {
|
||||
if match(link) {
|
||||
netDev, err := linkToNetDev(link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
netDevs = append(netDevs, *netDev)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
return netDevs, err
|
||||
}
|
||||
|
||||
func forEachLink(f func(netlink.Link) error) error {
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, link := range links {
|
||||
if err := f(link); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func linkToNetDev(link netlink.Link) (*NetDev, error) {
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
netDev := &NetDev{MAC: link.Attrs().HardwareAddr}
|
||||
for _, addr := range addrs {
|
||||
netDev.CIDRs = append(netDev.CIDRs, addr.IPNet)
|
||||
}
|
||||
return netDev, nil
|
||||
}
|
||||
|
||||
// Lookup the weave interface of a container
|
||||
func GetWeaveNetDevs(processID int) ([]NetDev, error) {
|
||||
// Bail out if this container is running in the root namespace
|
||||
nsToplevel, err := netns.GetFromPid(1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open root namespace: %s", err)
|
||||
}
|
||||
nsContainr, err := netns.GetFromPid(processID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open process %d namespace: %s", processID, err)
|
||||
}
|
||||
if nsToplevel.Equal(nsContainr) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
weaveBridge, err := netlink.LinkByName("weave")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot find weave bridge: %s", err)
|
||||
}
|
||||
// Scan devices in root namespace to find those attached to weave bridge
|
||||
indexes := make(map[int]struct{})
|
||||
err = forEachLink(func(link netlink.Link) error {
|
||||
if link.Attrs().MasterIndex == weaveBridge.Attrs().Index {
|
||||
peerIndex := link.Attrs().ParentIndex
|
||||
if peerIndex == 0 {
|
||||
// perhaps running on an older kernel where ParentIndex doesn't work.
|
||||
// as fall-back, assume the indexes are consecutive
|
||||
peerIndex = link.Attrs().Index - 1
|
||||
}
|
||||
indexes[peerIndex] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FindNetDevs(processID, func(link netlink.Link) bool {
|
||||
_, isveth := link.(*netlink.Veth)
|
||||
_, found := indexes[link.Attrs().Index]
|
||||
return isveth && found
|
||||
})
|
||||
}
|
||||
|
||||
// Get the weave bridge interface
|
||||
func GetBridgeNetDev(bridgeName string) ([]NetDev, error) {
|
||||
return FindNetDevs(1, func(link netlink.Link) bool {
|
||||
return link.Attrs().Name == bridgeName
|
||||
})
|
||||
}
|
||||
+30
-5
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
@@ -23,14 +24,39 @@ var (
|
||||
topBucket = []byte("top")
|
||||
)
|
||||
|
||||
const (
|
||||
NameIdent = "peername"
|
||||
FileName = "data.db"
|
||||
)
|
||||
|
||||
func NewBoltDB(dbPathname string) (*BoltDB, error) {
|
||||
db, err := bolt.Open(dbPathname, 0660, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[boltDB] Unable to open %s: %s", dbPathname, err)
|
||||
}
|
||||
// check the persistence version
|
||||
err = db.Update(func(tx *bolt.Tx) error {
|
||||
err = db.Update(checkVersion(false))
|
||||
return &BoltDB{db: db}, err
|
||||
}
|
||||
|
||||
func NewBoltDBReadOnly(dbPathname string) (*BoltDB, error) {
|
||||
options := bolt.Options{Timeout: time.Millisecond * 50, ReadOnly: true}
|
||||
db, err := bolt.Open(dbPathname, 0660, &options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[boltDB] Unable to open %s: %s", dbPathname, err)
|
||||
}
|
||||
err = db.View(checkVersion(true))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[boltDB] Cannot use persistence file %s: %s", dbPathname, err)
|
||||
}
|
||||
return &BoltDB{db: db}, nil
|
||||
}
|
||||
|
||||
func checkVersion(readOnly bool) func(tx *bolt.Tx) error {
|
||||
return func(tx *bolt.Tx) error {
|
||||
if top := tx.Bucket(topBucket); top == nil {
|
||||
if readOnly {
|
||||
return fmt.Errorf("no top bucket")
|
||||
}
|
||||
top, err := tx.CreateBucket(topBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -41,13 +67,12 @@ func NewBoltDB(dbPathname string) (*BoltDB, error) {
|
||||
} else {
|
||||
if checkVersion := top.Get(versionIdent); checkVersion != nil {
|
||||
if checkVersion[0] != persistenceVersion[0] {
|
||||
return fmt.Errorf("[boltDB] Cannot use persistence file %s - version %x", dbPathname, checkVersion)
|
||||
return fmt.Errorf("mismatched version %x", checkVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return &BoltDB{db: db}, err
|
||||
}
|
||||
}
|
||||
|
||||
func (d *BoltDB) Load(ident string, data interface{}) (bool, error) {
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
This document describes implementation details of the fast datapath encryption.
|
||||
|
||||
# Overview
|
||||
|
||||
At the high level, we use the ESP protocol ([RFC 2406][esp]) in the Transport
|
||||
mode. Each packet is encrypted with AES in GCM mode ([RFC 4106][aesgcm]), with
|
||||
32 byte key and 4 byte salt. This combo provides the following security
|
||||
properties:
|
||||
|
||||
* Data confidentiality.
|
||||
* Data origin authentication.
|
||||
* Integrity.
|
||||
* Anti-replay.
|
||||
* Limited traffic flow confidentiality as fast datapath VXLAN packets are fully
|
||||
encrypted.
|
||||
|
||||
## Notation
|
||||
|
||||
* `SAin`: IPsec security association for inbound connections.
|
||||
* `SAout`: IPsec security association for outbound connections.
|
||||
* `SPout`: IPsec security policy for outbound connections. Used to match
|
||||
outbound flows to SAout.
|
||||
|
||||
## SA Key Derivation
|
||||
|
||||
For each connection direction, a different AES-GCM key and salt is used.
|
||||
The pairs are derived with HKDF ([RFC 5869][hkdf]):
|
||||
|
||||
```
|
||||
SAin[KeyAndSalt] = HKDF(sha256, ikm=sessionKey, salt=nonceIn, info=localPeerName)
|
||||
SAout[KeyAndSalt] = HKDF(sha256, ikm=sessionKey, salt=nonceOut, info=remotePeerName)
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
* the mutual `sessionKey` is derived by the [Mesh][mesh] library during
|
||||
the control plane connection establishment between the local peer and
|
||||
the remote peer.
|
||||
* `nonceIn` and `nonceOut` are randomly generated 32byte nonces which
|
||||
are exchanged over the encrypted control plane channel.
|
||||
|
||||
## SPI
|
||||
|
||||
A directional secure connection between two peers is identified with SPI.
|
||||
|
||||
The kernel requires the pair of SPI and dst IP to be unique among security
|
||||
associations. Thus, to avoid collisions, we generate outbound SPI on a remote
|
||||
peer.
|
||||
|
||||
## Connection Establishment
|
||||
|
||||
```
|
||||
Peer A Peer B
|
||||
-----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
fastdp.fwd.Confirm():
|
||||
install iptables blocking rules, fastdp.fwd.Confirm():
|
||||
nonce_BA = rand(), install iptables blocking rules,
|
||||
{key,salt}_BA = hkdf(sessionKey, nonce_BA, A), nonce_AB = rand(),
|
||||
spi_BA = allocspi(), {key,salt}_AB = hkdf(sessionKey, nonce_AB, B),
|
||||
create SA_BA(B<-A, spi_BA, key_BA, salt_BA), spi_AB = allocspi(),
|
||||
send InitSARemote(spi_BA, nonce_BA). --> create SA_AB(A<-B, spi_AB, key_AB, salt_AB),
|
||||
<-- send InitSARemote(spi_AB, nonce_AB).
|
||||
|
||||
|
||||
--> recv InitSARemote(spi_BA, nonce_BA):
|
||||
recv InitSARemote(spi_AB, nonce_AB): <-- {key,salt}_BA = hkdf(sessionKey, nonce_BA, A),
|
||||
{key,salt}_AB = hkdf(sessionKey, nonce_AB, B), create SA_BA(B<-A, spi_BA, key_BA, salt_BA),
|
||||
create SA_AB(A<-B, spi_AB, key_AB, salt_AB), create SP_BA(B<-A, spi_BA),
|
||||
create SP_AB(A<-B, spi_AB), install marking rule.
|
||||
install marking rule.
|
||||
```
|
||||
|
||||
# Implementation Details
|
||||
|
||||
## XFRM
|
||||
|
||||
The implementation is based on the kernel IP packet transformation framework
|
||||
called XFRM. Unfortunately, docs are barely existing and the complexity of
|
||||
the framework is high. The best resource I found is Chapter 10 in
|
||||
"Linux Kernel Networking: Implementation and Theory" by Rami Rosen.
|
||||
|
||||
## iptables Rules
|
||||
|
||||
The kernel VXLAN driver does not set a dst port of a tunnel in the ip flow
|
||||
descriptor, thus XFRM policy lookup cannot match a policy (SPout) which includes
|
||||
the port. This makes impossible to encrypt only tunneled traffic between
|
||||
peers. To work around, we mark such outgoing packets with iptables and set
|
||||
the same mark in the policy selector (funnily enough, `iptables_mangle` module
|
||||
eventually sets the missing dst port in the flow descriptor). The challenge
|
||||
here is to pick a mark that it would not interfere with other networking
|
||||
applications before OUTPUT'ing a packet. For example, Kubernetes by default
|
||||
uses 1<<14 and 1<<15 marks and we choose 1<<17 (0x20000). Additionally,
|
||||
such workaround brings the requirement for at least the 4.2 kernel.
|
||||
|
||||
The marking rules are the following:
|
||||
|
||||
```
|
||||
iptables -t mangle -A OUTPUT -j WEAVE-IPSEC-OUT
|
||||
iptables -t mangle -A WEAVE-IPSEC-OUT -s ${LOCAL_PEER_IP} -d ${REMOTE_PEER_IP} \
|
||||
-p udp --dport ${TUNNEL_PORT} -j WEAVE-IPSEC-OUT-MARK
|
||||
iptables -t mangle -A WEAVE-IPSEC-OUT-MARK --set-xmark ${MARK} -j MARK
|
||||
```
|
||||
|
||||
As Linux does not implement [IP Security Levels][ipseclevel], we install
|
||||
additional iptables rules to prevent from accidentally sending unencrypted
|
||||
traffic between peers which have previously established the secure connection.
|
||||
|
||||
For inbound traffic, we mark each ESP packet with the mark and drop non-marked
|
||||
tunnel traffic:
|
||||
|
||||
```
|
||||
iptables -t mangle -A INPUT -j WEAVE-IPSEC-IN
|
||||
iptables -t mangle -A WEAVE-IPSEC-IN -s ${REMOTE_PEER_IP} -d ${LOCAL_PEER_IP} \
|
||||
-m esp --espspi ${SPIin} -j WEAVE-IPSEC-IN-MARK
|
||||
iptables -t mangle -A WEAVE-IPSEC-IN-MARK --set-xmark ${MARK} -j MARK
|
||||
|
||||
iptables -t filter -A INPUT -j WEAVE-IPSEC-IN
|
||||
iptables -t filter -A WEAVE-IPSEC-IN -s ${REMOTE_PEER_IP} -d ${LOCAL_PEER_IP} \
|
||||
-p udp --dport ${TUNNEL_PORT} -m mark ! --mark ${MARK} -j DROP
|
||||
```
|
||||
|
||||
For outbound traffic, we drop marked traffic which does not match any SPout:
|
||||
|
||||
```
|
||||
iptables -t filter -A OUTPUT ! -p esp -m policy --dir out --pol none \
|
||||
-m mark --mark ${MARK} -j DROP
|
||||
```
|
||||
|
||||
## ESN
|
||||
|
||||
To prevent from cycling SeqNo which makes replay attacks possible, we use
|
||||
64-bit extended sequence numbers known as [ESN](esn).
|
||||
|
||||
## MTU
|
||||
|
||||
In addition to the VXLAN overhead, the MTU calculation should take into account
|
||||
the ESP overhead which is 34-37 bytes (encrypted Payload is 4 bytes aligned) and
|
||||
consists of:
|
||||
|
||||
* 4 bytes (SPI).
|
||||
* 4 bytes (Sequence Number).
|
||||
* 8 bytes (ESP IV).
|
||||
* 1 byte (Pad Length).
|
||||
* 1 byte (NextHeader).
|
||||
* 16 bytes (ICV).
|
||||
* 0-3 bytes (Padding).
|
||||
|
||||
|
||||
[esp]: https://tools.ietf.org/html/rfc2406
|
||||
[aesgcm]: https://tools.ietf.org/html/rfc4106
|
||||
[hkdf]: https://tools.ietf.org/html/rfc5869
|
||||
[esn]: https://tools.ietf.org/html/rfc4304
|
||||
[ipseclevel]: https://tools.ietf.org/html/draft-mcdonald-simple-ipsec-api-01)
|
||||
[mesh]: https://github.com/weaveworks/mesh
|
||||
+2
-2
@@ -52,9 +52,9 @@ overlay, which has a more sophisticated dynamic mechanism for coping
|
||||
with low path MTUs.
|
||||
|
||||
To avoid triggering this fallback in typical deployments, the datapath
|
||||
interface is statically configured with an MTU of 1410 bytes allowing
|
||||
interface is statically configured with an MTU of 1376 bytes allowing
|
||||
it to work with most underlay network provider MTUs, including GCE at
|
||||
1460 bytes (the fifty byte difference accommodates the vxlan overhead).
|
||||
1460 bytes (the eighty four byte difference accommodates the encrypted vxlan overhead).
|
||||
This value can be overridden by setting `WEAVE_MTU` at launch if
|
||||
necessary.
|
||||
|
||||
|
||||
+22
-6
@@ -65,14 +65,30 @@ peer for space:
|
||||
- it will continue to ask for space until it receives some, or its
|
||||
copy of the ring tells it all peers are full in that subnet.
|
||||
|
||||
### Data persistence
|
||||
|
||||
Key IPAM data is saved to disk, in a [BoltDB](https://github.com/boltdb/bolt)
|
||||
file, stored within a [data volume container](https://docs.docker.com/engine/userguide/containers/dockervolumes/#creating-and-mounting-a-data-volume-container)
|
||||
named `weavedb`.
|
||||
|
||||
This file is used to persist data for various Weave Net components; for IPAM it contains:
|
||||
|
||||
* the division of the IP allocation range amongst peers, and
|
||||
* allocation of addresses to containers on the local peer,
|
||||
|
||||
so that it is immediately available when the peer restarts.
|
||||
|
||||
### Claiming an address
|
||||
|
||||
If a Weave process is restarted, in most cases it will hear from
|
||||
another peer which ranges it used to own, but it needs to know which
|
||||
individual IP addresses are assigned to containers in order to avoid
|
||||
giving the same address out in subsequent allocation requests. The
|
||||
weave script invokes the `claim` command in `populate_ipam` to do
|
||||
this.
|
||||
If a Weave process is restarted,
|
||||
|
||||
- if `weavedb` is present, then it loads persisted IPAM data from there, as
|
||||
described, in the previous section;
|
||||
- else, it learns from other peers which ranges it used to own.
|
||||
|
||||
Weave Net then consults the allocator to know which individual IP addresses
|
||||
are assigned to containers, and therefore avoid giving the same address out in
|
||||
subsequent allocation requests.
|
||||
|
||||
When the Allocator is told to claim an address, there are four
|
||||
possibilities:
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
* Create a [github token for
|
||||
github-release](https://help.github.com/articles/creating-an-access-token-for-command-line-use/);
|
||||
set and export `$GITHUB_TOKEN` with this value
|
||||
* Update all dependencies with `make update`
|
||||
* Ensure you are still on the right branch, since Go may switch to `master` as part of `make update`
|
||||
|
||||
## Release Types
|
||||
|
||||
@@ -133,9 +131,13 @@ Finally, for **Mainline** releases only:
|
||||
### Finish up
|
||||
|
||||
* If not on master, merge branch into master and push to GitHub.
|
||||
* Merge the branch you are on into `latest_release_doc_updates` and push to GitHub.
|
||||
* Close the [milestone](https://github.com/weaveworks/weave/milestones) in GitHub and create the next milestone
|
||||
* Update the `#weavenetwork` topic heading on freenode (requires 'chanops' permission)
|
||||
* For a mainline release vX.Y.0, create a release branch X.Y from the
|
||||
tag and push to GitHub - this will result in X.Y.0 site docs being
|
||||
published to https://www.weave.works
|
||||
* Add the new version of `weave-net` to the checkpoint system at
|
||||
https://checkpoint-api.weave.works/admin
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
# Overview
|
||||
|
||||
# ipsets
|
||||
|
||||
The policy controller maintains a number of ipsets which are
|
||||
subsequently referred to by the iptables rules used to effect network
|
||||
policy specifications. These ipsets are created, modified and
|
||||
destroyed automatically in response to Pod, Namespace and
|
||||
NetworkPolicy object updates from the k8s API server:
|
||||
|
||||
* A `hash:ip` set per namespace, containing the IP addresses of all
|
||||
pods in that namespace
|
||||
* A `list:set` per distinct (across all network policies in all
|
||||
namespaces) namespace selector mentioned in a network policy,
|
||||
containing the names of any of the above hash:ip sets whose
|
||||
corresponding namespace labels match the selector
|
||||
* A `hash:ip` set for each distinct (within the scope of the
|
||||
containing network policy's namespace) pod selector mentioned in a
|
||||
network policy, containing the IP addresses of all pods in the
|
||||
namespace whose labels match that selector
|
||||
|
||||
IPsets are implemented by the kernel module `xt_set`, without which
|
||||
weave-npc will not work.
|
||||
|
||||
ipset names are generated deterministically from a string
|
||||
representation of the corresponding label selector. Because ipset
|
||||
names are limited to 31 characters in length, this is done by taking a
|
||||
SHA hash of the selector string and then printing that out as a base
|
||||
85 string with a "weave-" prefix e.g.:
|
||||
|
||||
weave-k?Z;25^M}|1s7P3|H9i;*;MhG
|
||||
|
||||
Because pod selectors are scoped to a namespace, we need to make sure
|
||||
that if the same selector definition is used in different namespaces
|
||||
that we maintain distinct ipsets. Consequently, for such selectors the
|
||||
namespace name is prepended to the label selector string before
|
||||
hashing to avoid clashes.
|
||||
|
||||
# iptables chains
|
||||
|
||||
The policy controller maintains two iptables chains in response to
|
||||
changes to pods, namespaces and network policies. One chain contains
|
||||
the ingress rules that implement the network policy specifications,
|
||||
and the other is used to bypass the ingress rules for namespaces which
|
||||
have an ingress isolation policy of `DefaultAllow`.
|
||||
|
||||
## Dynamically maintained `WEAVE-NPC-DEFAULT` chain
|
||||
|
||||
The policy controller maintains a rule in this chain for every
|
||||
namespace whose ingress isolation policy is `DefaultAllow`. The
|
||||
purpose of this rule is simply to ACCEPT any traffic destined for such
|
||||
namespaces before it reaches the ingress chain.
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC-DEFAULT -m set --match-set $NSIPSET dst -j ACCEPT
|
||||
```
|
||||
|
||||
## Dynamically maintained `WEAVE-NPC-INGRESS` chain
|
||||
|
||||
For each namespace network policy ingress rule peer/port combination:
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC-INGRESS -p $PROTO [-m set --match-set $SRCSET] -m set --match-set $DSTSET --dport $DPORT -j ACCEPT
|
||||
```
|
||||
|
||||
## Static `WEAVE-NPC` chain
|
||||
|
||||
Static configuration:
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC -m state --state RELATED,ESTABLISHED -j ACCEPT
|
||||
iptables -A WEAVE-NPC -m state --state NEW -j WEAVE-NPC-DEFAULT
|
||||
iptables -A WEAVE-NPC -m state --state NEW -j WEAVE-NPC-INGRESS
|
||||
```
|
||||
|
||||
# Steering traffic into the policy engine
|
||||
|
||||
To direct traffic into the policy engine:
|
||||
|
||||
iptables -A FORWARD -o weave -m physdev ! --physdev-out vethwe-bridge -j WEAVE-NPC
|
||||
iptables -A FORWARD -o weave -m physdev ! --physdev-out vethwe-bridge -j DROP
|
||||
|
||||
Note this only affects traffic which egresses the bridge on a physical
|
||||
port which is not the Weave Net router - in other words, it is
|
||||
destined for an application container veth. The following traffic is
|
||||
affected:
|
||||
|
||||
* Traffic bridged between local application containers
|
||||
* Traffic bridged from the router to a local application container
|
||||
* Traffic originating from the internet destined for nodeports - this
|
||||
is routed via the FORWARD chain to a container pod IP after DNAT
|
||||
|
||||
The following traffic is NOT affected:
|
||||
|
||||
* Traffic bridged from a local application container to the router
|
||||
* Traffic originating from processes in the host network namespace
|
||||
(e.g. kubelet health checks)
|
||||
* Traffic routed from an application container to the internet
|
||||
|
||||
The above mechanism relies on the kernel module `br_netfilter` being
|
||||
loaded and enabled via `/proc/sys/net/bridge/bridge-nf-call-iptables`.
|
||||
|
||||
See these resources for helpful context:
|
||||
|
||||
* http://ebtables.netfilter.org/br_fw_ia/br_fw_ia.html
|
||||
* https://commons.wikimedia.org/wiki/File:Netfilter-packet-flow.svg
|
||||
+5
-4
@@ -3,6 +3,7 @@ package ipam
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
@@ -13,9 +14,9 @@ type allocateResult struct {
|
||||
|
||||
type allocate struct {
|
||||
resultChan chan<- allocateResult
|
||||
ident string
|
||||
ident string // a container ID, something like "weave:expose", or api.NoContainerID
|
||||
r address.CIDR // Subnet we are trying to allocate within
|
||||
isContainer bool
|
||||
isContainer bool // true if ident is a container ID
|
||||
hasBeenCancelled func() bool
|
||||
}
|
||||
|
||||
@@ -33,7 +34,7 @@ func (g *allocate) Try(alloc *Allocator) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if !alloc.universe.Overlaps(g.r.Range()) {
|
||||
if !alloc.universe.Range().Overlaps(g.r.Range()) {
|
||||
g.resultChan <- allocateResult{err: fmt.Errorf("range %s out of bounds: %s", g.r, alloc.universe)}
|
||||
return true
|
||||
}
|
||||
@@ -43,7 +44,7 @@ func (g *allocate) Try(alloc *Allocator) bool {
|
||||
if ok, addr := alloc.space.Allocate(g.r.HostRange()); ok {
|
||||
// If caller hasn't supplied a unique ID, file it under the IP address
|
||||
// which lets the caller then release the address using DELETE /ip/address
|
||||
if g.ident == "_" {
|
||||
if g.ident == api.NoContainerID {
|
||||
g.ident = addr.String()
|
||||
}
|
||||
alloc.debugln("Allocated", addr, "for", g.ident, "in", g.r)
|
||||
|
||||
+152
-63
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/weaveworks/weave/ipam/paxos"
|
||||
"github.com/weaveworks/weave/ipam/ring"
|
||||
"github.com/weaveworks/weave/ipam/space"
|
||||
"github.com/weaveworks/weave/ipam/tracker"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
@@ -55,7 +56,7 @@ type Allocator struct {
|
||||
stopChan chan<- struct{}
|
||||
ourName mesh.PeerName
|
||||
seed []mesh.PeerName // optional user supplied ring seed
|
||||
universe address.Range // superset of all ranges
|
||||
universe address.CIDR // superset of all ranges
|
||||
ring *ring.Ring // information on ranges owned by all peers
|
||||
space space.Space // more detail on ranges owned by us
|
||||
owned map[string]ownedData // who owns what addresses, indexed by container-ID
|
||||
@@ -75,31 +76,52 @@ type Allocator struct {
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
// PreClaims are IP addresses discovered before we could initialize IPAM
|
||||
type PreClaim struct {
|
||||
Ident string // a container ID, something like "weave:expose", or api.NoContainerID
|
||||
IsContainer bool // true if Ident is a container ID
|
||||
Cidr address.CIDR
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
OurName mesh.PeerName
|
||||
OurUID mesh.PeerUID
|
||||
OurNickname string
|
||||
Seed []mesh.PeerName
|
||||
Universe address.Range
|
||||
Universe address.CIDR
|
||||
IsObserver bool
|
||||
PreClaims []PreClaim
|
||||
Quorum func() uint
|
||||
Db db.DB
|
||||
IsKnownPeer func(name mesh.PeerName) bool
|
||||
Tracker tracker.LocalRangeTracker
|
||||
}
|
||||
|
||||
// NewAllocator creates and initialises a new Allocator
|
||||
func NewAllocator(config Config) *Allocator {
|
||||
var participant paxos.Participant
|
||||
var alloc *Allocator
|
||||
var onUpdate ring.OnUpdate
|
||||
|
||||
if config.IsObserver {
|
||||
participant = paxos.NewObserver()
|
||||
} else {
|
||||
participant = paxos.NewNode(config.OurName, config.OurUID, 0)
|
||||
}
|
||||
return &Allocator{
|
||||
|
||||
if config.Tracker != nil {
|
||||
onUpdate = func(prev []address.Range, curr []address.Range, local bool) {
|
||||
if err := config.Tracker.HandleUpdate(prev, curr, local); err != nil {
|
||||
alloc.errorf("HandleUpdate failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alloc = &Allocator{
|
||||
ourName: config.OurName,
|
||||
seed: config.Seed,
|
||||
universe: config.Universe,
|
||||
ring: ring.New(config.Universe.Start, config.Universe.End, config.OurName),
|
||||
ring: ring.New(config.Universe.Range().Start, config.Universe.Range().End, config.OurName, onUpdate),
|
||||
owned: make(map[string]ownedData),
|
||||
db: config.Db,
|
||||
paxos: participant,
|
||||
@@ -109,11 +131,48 @@ func NewAllocator(config Config) *Allocator {
|
||||
dead: make(map[string]time.Time),
|
||||
now: time.Now,
|
||||
}
|
||||
|
||||
alloc.pendingClaims = make([]operation, len(config.PreClaims))
|
||||
for i, c := range config.PreClaims {
|
||||
alloc.pendingClaims[i] = &claim{ident: c.Ident, cidr: c.Cidr}
|
||||
}
|
||||
|
||||
return alloc
|
||||
}
|
||||
|
||||
func ParseCIDRSubnet(cidrStr string) (cidr address.CIDR, err error) {
|
||||
cidr, err = address.ParseCIDR(cidrStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !cidr.IsSubnet() {
|
||||
err = fmt.Errorf("invalid subnet - bits after network prefix are not all zero: %s", cidrStr)
|
||||
}
|
||||
if cidr.Size() < MinSubnetSize {
|
||||
err = fmt.Errorf("invalid subnet - smaller than minimum size %d: %s", MinSubnetSize, cidrStr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Start runs the allocator goroutine
|
||||
func (alloc *Allocator) Start() {
|
||||
alloc.loadPersistedData()
|
||||
loadedPersistedData := alloc.loadPersistedData()
|
||||
switch {
|
||||
case loadedPersistedData && len(alloc.seed) != 0:
|
||||
alloc.infof("Found persisted IPAM data, ignoring supplied IPAM seed")
|
||||
case loadedPersistedData:
|
||||
alloc.infof("Initialising with persisted data")
|
||||
case len(alloc.seed) != 0:
|
||||
alloc.infof("Initialising with supplied IPAM seed")
|
||||
alloc.createRing(alloc.seed)
|
||||
case alloc.paxos.IsElector():
|
||||
alloc.infof("Initialising via deferred consensus")
|
||||
default:
|
||||
alloc.infof("Initialising as observer - awaiting IPAM data from another peer")
|
||||
}
|
||||
if loadedPersistedData { // do any pre-claims right away
|
||||
alloc.tryOps(&alloc.pendingClaims)
|
||||
}
|
||||
actionChan := make(chan func(), mesh.ChannelSize)
|
||||
stopChan := make(chan struct{})
|
||||
alloc.actionChan = actionChan
|
||||
@@ -263,7 +322,7 @@ func (alloc *Allocator) Lookup(ident string, r address.Range) ([]address.CIDR, e
|
||||
}
|
||||
|
||||
// Claim an address that we think we should own (Sync)
|
||||
func (alloc *Allocator) Claim(ident string, cidr address.CIDR, isContainer, noErrorOnUnknown bool) error {
|
||||
func (alloc *Allocator) Claim(ident string, cidr address.CIDR, isContainer, noErrorOnUnknown bool, hasBeenCancelled func() bool) error {
|
||||
resultChan := make(chan error)
|
||||
op := &claim{
|
||||
resultChan: resultChan,
|
||||
@@ -271,6 +330,7 @@ func (alloc *Allocator) Claim(ident string, cidr address.CIDR, isContainer, noEr
|
||||
cidr: cidr,
|
||||
isContainer: isContainer,
|
||||
noErrorOnUnknown: noErrorOnUnknown,
|
||||
hasBeenCancelled: hasBeenCancelled,
|
||||
}
|
||||
alloc.doOperation(op, &alloc.pendingClaims)
|
||||
return <-resultChan
|
||||
@@ -279,7 +339,7 @@ func (alloc *Allocator) Claim(ident string, cidr address.CIDR, isContainer, noEr
|
||||
// ContainerDied called from the updater interface. Async.
|
||||
func (alloc *Allocator) ContainerDied(ident string) {
|
||||
alloc.actionChan <- func() {
|
||||
if alloc.hasOwned(ident) {
|
||||
if alloc.hasOwnedByContainer(ident) {
|
||||
alloc.debugln("Container", ident, "died; noting to remove later")
|
||||
alloc.dead[ident] = alloc.now()
|
||||
}
|
||||
@@ -292,7 +352,7 @@ func (alloc *Allocator) ContainerDied(ident string) {
|
||||
// ContainerDestroyed called from the updater interface. Async.
|
||||
func (alloc *Allocator) ContainerDestroyed(ident string) {
|
||||
alloc.actionChan <- func() {
|
||||
if alloc.hasOwned(ident) {
|
||||
if alloc.hasOwnedByContainer(ident) {
|
||||
alloc.debugln("Container", ident, "destroyed; removing addresses")
|
||||
alloc.delete(ident)
|
||||
delete(alloc.dead, ident)
|
||||
@@ -318,13 +378,13 @@ func (alloc *Allocator) ContainerStarted(ident string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (alloc *Allocator) AllContainerIDs(ids []string) {
|
||||
func (alloc *Allocator) PruneOwned(ids []string) {
|
||||
idmap := make(map[string]struct{}, len(ids))
|
||||
for _, id := range ids {
|
||||
idmap[id] = struct{}{}
|
||||
}
|
||||
alloc.actionChan <- func() {
|
||||
idmap := make(map[string]struct{}, len(ids))
|
||||
for _, id := range ids {
|
||||
idmap[id] = struct{}{}
|
||||
}
|
||||
alloc.syncOwned(idmap)
|
||||
alloc.pruneOwned(idmap)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,11 +460,12 @@ func (alloc *Allocator) Shutdown() {
|
||||
alloc.cancelOps(&alloc.pendingClaims)
|
||||
alloc.cancelOps(&alloc.pendingAllocates)
|
||||
alloc.cancelOps(&alloc.pendingPrimes)
|
||||
if heir := alloc.pickPeerForTransfer(); heir != mesh.UnknownPeerName {
|
||||
alloc.ring.Transfer(alloc.ourName, heir)
|
||||
alloc.space.Clear()
|
||||
heir := alloc.pickPeerForTransfer()
|
||||
alloc.ring.Transfer(alloc.ourName, heir)
|
||||
alloc.space.Clear()
|
||||
if heir != mesh.UnknownPeerName {
|
||||
alloc.persistRing()
|
||||
alloc.gossip.GossipBroadcast(alloc.Gossip())
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
doneChan <- struct{}{}
|
||||
}
|
||||
@@ -439,7 +500,7 @@ func (alloc *Allocator) AdminTakeoverRanges(peerNameOrNickname string) address.C
|
||||
}
|
||||
|
||||
before := alloc.space.NumFreeAddresses()
|
||||
alloc.space.AddRanges(newRanges)
|
||||
alloc.ringUpdated()
|
||||
after := alloc.space.NumFreeAddresses()
|
||||
|
||||
alloc.gossip.GossipBroadcast(alloc.Gossip())
|
||||
@@ -485,6 +546,22 @@ func (alloc *Allocator) annotatePeernames(names []mesh.PeerName) []string {
|
||||
return res
|
||||
}
|
||||
|
||||
// PeerGone removes nicknames of peers which are no longer mentioned
|
||||
// in the ring. Async.
|
||||
//
|
||||
// NB: the function is invoked by the gossip library routines and should be
|
||||
// registered manually.
|
||||
func (alloc *Allocator) PeerGone(peerName mesh.PeerName) {
|
||||
alloc.debugf("PeerGone: peer %s", peerName)
|
||||
|
||||
alloc.actionChan <- func() {
|
||||
ringPeers := alloc.ring.PeerNames()
|
||||
if _, ok := ringPeers[peerName]; !ok {
|
||||
delete(alloc.nicknames, peerName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func decodeRange(msg []byte) (r address.Range, err error) {
|
||||
decoder := gob.NewDecoder(bytes.NewReader(msg))
|
||||
return r, decoder.Decode(&r)
|
||||
@@ -497,9 +574,11 @@ func (alloc *Allocator) OnGossipUnicast(sender mesh.PeerName, msg []byte) error
|
||||
alloc.actionChan <- func() {
|
||||
switch msg[0] {
|
||||
case msgSpaceRequest:
|
||||
// some other peer asked us for space
|
||||
alloc.debugln("Peer", sender, "asked me for space")
|
||||
r, err := decodeRange(msg[1:])
|
||||
if err == nil {
|
||||
// If we don't have a ring, just ignore a request for space.
|
||||
// They'll probably ask again later.
|
||||
if err == nil && !alloc.ring.Empty() {
|
||||
alloc.donateSpace(r, sender)
|
||||
}
|
||||
resultChan <- err
|
||||
@@ -614,11 +693,13 @@ func (alloc *Allocator) actorLoop(actionChan <-chan func(), stopChan <-chan stru
|
||||
case <-stopChan:
|
||||
return
|
||||
case <-alloc.ticker.C:
|
||||
// Retry things in case messages got lost between here and recipients
|
||||
if alloc.awaitingConsensus {
|
||||
alloc.propose()
|
||||
} else {
|
||||
alloc.tryPendingOps()
|
||||
}
|
||||
alloc.removeDeadContainers()
|
||||
alloc.tryPendingOps()
|
||||
}
|
||||
|
||||
alloc.assertInvariants()
|
||||
@@ -647,8 +728,8 @@ func (alloc *Allocator) establishRing() {
|
||||
func (alloc *Allocator) createRing(peers []mesh.PeerName) {
|
||||
alloc.debugln("Paxos consensus:", peers)
|
||||
alloc.ring.ClaimForPeers(normalizeConsensus(peers))
|
||||
alloc.gossip.GossipBroadcast(alloc.Gossip())
|
||||
alloc.ringUpdated()
|
||||
alloc.gossip.GossipBroadcast(alloc.Gossip())
|
||||
}
|
||||
|
||||
func (alloc *Allocator) ringUpdated() {
|
||||
@@ -727,7 +808,6 @@ func (alloc *Allocator) update(sender mesh.PeerName, msg []byte) error {
|
||||
reader := bytes.NewReader(msg)
|
||||
decoder := gob.NewDecoder(reader)
|
||||
var data gossipState
|
||||
var err error
|
||||
|
||||
if err := decoder.Decode(&data); err != nil {
|
||||
return err
|
||||
@@ -742,9 +822,10 @@ func (alloc *Allocator) update(sender mesh.PeerName, msg []byte) error {
|
||||
// If someone sent us a ring, merge it into ours. Note this will move us
|
||||
// out of the awaiting-consensus state if we didn't have a ring already.
|
||||
case data.Ring != nil:
|
||||
switch err = alloc.ring.Merge(*data.Ring); err {
|
||||
updated, err := alloc.ring.Merge(*data.Ring)
|
||||
switch err {
|
||||
case nil:
|
||||
if !alloc.ring.Empty() {
|
||||
if updated {
|
||||
alloc.pruneNicknames()
|
||||
alloc.ringUpdated()
|
||||
}
|
||||
@@ -806,7 +887,6 @@ func (alloc *Allocator) donateSpace(r address.Range, to mesh.PeerName) {
|
||||
// more.
|
||||
defer alloc.sendRingUpdate(to)
|
||||
|
||||
alloc.debugln("Peer", to, "asked me for space")
|
||||
chunk, ok := alloc.space.Donate(r)
|
||||
if !ok {
|
||||
free := alloc.space.NumFreeAddressesInRange(r)
|
||||
@@ -849,19 +929,20 @@ func (alloc *Allocator) reportFreeSpace() {
|
||||
for _, r := range ranges {
|
||||
freespace[r.Start] = alloc.space.NumFreeAddressesInRange(r)
|
||||
}
|
||||
alloc.ring.ReportFree(freespace)
|
||||
if alloc.ring.ReportFree(freespace) {
|
||||
alloc.persistRing()
|
||||
}
|
||||
}
|
||||
|
||||
// Persistent data
|
||||
const (
|
||||
ringIdent = "ring"
|
||||
nameIdent = "peername"
|
||||
ownedIdent = "ownedAddresses"
|
||||
)
|
||||
|
||||
func (alloc *Allocator) persistRing() {
|
||||
// It would be better if these two Save operations happened in the same transaction
|
||||
if err := alloc.db.Save(nameIdent, alloc.ourName); err != nil {
|
||||
if err := alloc.db.Save(db.NameIdent, alloc.ourName); err != nil {
|
||||
alloc.fatalf("Error persisting ring data: %s", err)
|
||||
return
|
||||
}
|
||||
@@ -870,52 +951,57 @@ func (alloc *Allocator) persistRing() {
|
||||
}
|
||||
}
|
||||
|
||||
func (alloc *Allocator) loadPersistedData() {
|
||||
// Returns true if persisted data is to be used, otherwise false
|
||||
func (alloc *Allocator) loadPersistedData() bool {
|
||||
var checkPeerName mesh.PeerName
|
||||
nameFound, err := alloc.db.Load(nameIdent, &checkPeerName)
|
||||
nameFound, err := alloc.db.Load(db.NameIdent, &checkPeerName)
|
||||
if err != nil {
|
||||
alloc.fatalf("Error loading persisted peer name: %s", err)
|
||||
}
|
||||
ringFound, err := alloc.db.Load(ringIdent, &alloc.ring)
|
||||
var persistedRing *ring.Ring
|
||||
ringFound, err := alloc.db.Load(ringIdent, &persistedRing)
|
||||
if err != nil {
|
||||
alloc.fatalf("Error loading persisted IPAM data: %s", err)
|
||||
}
|
||||
ownedFound, err := alloc.db.Load(ownedIdent, &alloc.owned)
|
||||
var persistedOwned map[string]ownedData
|
||||
ownedFound, err := alloc.db.Load(ownedIdent, &persistedOwned)
|
||||
if err != nil {
|
||||
alloc.fatalf("Error loading persisted address data: %s", err)
|
||||
}
|
||||
|
||||
if nameFound {
|
||||
if checkPeerName == alloc.ourName {
|
||||
if ringFound {
|
||||
if len(alloc.seed) != 0 {
|
||||
alloc.infof("Found persisted IPAM data, ignoring supplied IPAM seed")
|
||||
}
|
||||
alloc.space.UpdateRanges(alloc.ring.OwnedRanges())
|
||||
}
|
||||
if ownedFound {
|
||||
for _, d := range alloc.owned {
|
||||
for _, cidr := range d.Cidrs {
|
||||
alloc.space.Claim(cidr.Addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
alloc.infof("Deleting persisted data for peername %s", checkPeerName)
|
||||
overwritePersisted := func(fmt string, args ...interface{}) {
|
||||
alloc.infof(fmt, args...)
|
||||
alloc.persistRing()
|
||||
alloc.persistOwned()
|
||||
}
|
||||
|
||||
if len(alloc.seed) != 0 {
|
||||
alloc.infof("Initialising with supplied IPAM seed")
|
||||
alloc.createRing(alloc.seed)
|
||||
} else if alloc.paxos.IsElector() {
|
||||
alloc.infof("Initialising via deferred consensus")
|
||||
} else {
|
||||
alloc.infof("Initialising as observer - awaiting IPAM data from another peer")
|
||||
if !nameFound || !ringFound {
|
||||
overwritePersisted("No valid persisted data")
|
||||
return false
|
||||
}
|
||||
|
||||
if checkPeerName != alloc.ourName {
|
||||
overwritePersisted("Deleting persisted data for peername %s", checkPeerName)
|
||||
return false
|
||||
}
|
||||
|
||||
if persistedRing.Range() != alloc.universe.Range() {
|
||||
overwritePersisted("Deleting persisted data for IPAM range %s; our range is %s", persistedRing.Range(), alloc.universe)
|
||||
return false
|
||||
}
|
||||
|
||||
alloc.ring.Restore(persistedRing)
|
||||
alloc.space.UpdateRanges(alloc.ring.OwnedRanges())
|
||||
|
||||
if ownedFound {
|
||||
alloc.owned = persistedOwned
|
||||
for _, d := range alloc.owned {
|
||||
for _, cidr := range d.Cidrs {
|
||||
alloc.space.Claim(cidr.Addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (alloc *Allocator) persistOwned() {
|
||||
@@ -926,9 +1012,9 @@ func (alloc *Allocator) persistOwned() {
|
||||
|
||||
// Owned addresses
|
||||
|
||||
func (alloc *Allocator) hasOwned(ident string) bool {
|
||||
_, b := alloc.owned[ident]
|
||||
return b
|
||||
func (alloc *Allocator) hasOwnedByContainer(ident string) bool {
|
||||
d, b := alloc.owned[ident]
|
||||
return b && d.IsContainer
|
||||
}
|
||||
|
||||
// NB: addr must not be owned by ident already
|
||||
@@ -986,7 +1072,7 @@ func (alloc *Allocator) findOwner(addr address.Address) string {
|
||||
}
|
||||
|
||||
// For each ID in the 'owned' map, remove the entry if it isn't in the map
|
||||
func (alloc *Allocator) syncOwned(ids map[string]struct{}) {
|
||||
func (alloc *Allocator) pruneOwned(ids map[string]struct{}) {
|
||||
changed := false
|
||||
for ident, d := range alloc.owned {
|
||||
if !d.IsContainer {
|
||||
@@ -1014,6 +1100,9 @@ func (alloc *Allocator) fatalf(fmt string, args ...interface{}) {
|
||||
func (alloc *Allocator) warnf(fmt string, args ...interface{}) {
|
||||
alloc.logf(common.Log.Warnf, fmt, args...)
|
||||
}
|
||||
func (alloc *Allocator) errorf(fmt string, args ...interface{}) {
|
||||
common.Log.Errorf("[allocator %s] "+fmt, append([]interface{}{alloc.ourName}, args...)...)
|
||||
}
|
||||
func (alloc *Allocator) infof(fmt string, args ...interface{}) {
|
||||
alloc.logf(common.Log.Infof, fmt, args...)
|
||||
}
|
||||
|
||||
+60
-27
@@ -22,6 +22,14 @@ const (
|
||||
|
||||
func returnFalse() bool { return false }
|
||||
|
||||
func (alloc *Allocator) SimplyAllocate(ident string, cidr address.CIDR) (address.Address, error) {
|
||||
return alloc.Allocate(ident, cidr, true, returnFalse)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) SimplyClaim(ident string, cidr address.CIDR) error {
|
||||
return alloc.Claim(ident, cidr, true, true, returnFalse)
|
||||
}
|
||||
|
||||
func TestAllocFree(t *testing.T) {
|
||||
const (
|
||||
container1 = "abcdef"
|
||||
@@ -41,11 +49,11 @@ func TestAllocFree(t *testing.T) {
|
||||
cidr2, _ := address.ParseCIDR(subnet2)
|
||||
|
||||
alloc.claimRingForTesting()
|
||||
addr1, err := alloc.Allocate(container1, cidr1, true, returnFalse)
|
||||
addr1, err := alloc.SimplyAllocate(container1, cidr1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testAddr1, addr1.String(), "address")
|
||||
|
||||
addr2, err := alloc.Allocate(container1, cidr2, true, returnFalse)
|
||||
addr2, err := alloc.SimplyAllocate(container1, cidr2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testAddr2, addr2.String(), "address")
|
||||
|
||||
@@ -54,28 +62,28 @@ func TestAllocFree(t *testing.T) {
|
||||
require.Equal(t, []address.CIDR{address.MakeCIDR(cidr1, addr1), address.MakeCIDR(cidr2, addr2)}, addrs)
|
||||
|
||||
// Ask for another address for a different container and check it's different
|
||||
addr1b, _ := alloc.Allocate(container2, cidr1, true, returnFalse)
|
||||
addr1b, _ := alloc.SimplyAllocate(container2, cidr1)
|
||||
if addr1b.String() == testAddr1 {
|
||||
t.Fatalf("Expected different address but got %s", addr1b.String())
|
||||
}
|
||||
|
||||
// Ask for the first container again and we should get the same addresses again
|
||||
addr1a, _ := alloc.Allocate(container1, cidr1, true, returnFalse)
|
||||
addr1a, _ := alloc.SimplyAllocate(container1, cidr1)
|
||||
require.Equal(t, testAddr1, addr1a.String(), "address")
|
||||
addr2a, _ := alloc.Allocate(container1, cidr2, true, returnFalse)
|
||||
addr2a, _ := alloc.SimplyAllocate(container1, cidr2)
|
||||
require.Equal(t, testAddr2, addr2a.String(), "address")
|
||||
|
||||
// Now delete the first container, and we should get its addresses back
|
||||
require.NoError(t, alloc.Delete(container1))
|
||||
addr3, _ := alloc.Allocate(container3, cidr1, true, returnFalse)
|
||||
addr3, _ := alloc.SimplyAllocate(container3, cidr1)
|
||||
require.Equal(t, testAddr1, addr3.String(), "address")
|
||||
addr4, _ := alloc.Allocate(container3, cidr2, true, returnFalse)
|
||||
addr4, _ := alloc.SimplyAllocate(container3, cidr2)
|
||||
require.Equal(t, testAddr2, addr4.String(), "address")
|
||||
|
||||
alloc.ContainerDied(container2)
|
||||
|
||||
// Resurrect
|
||||
addr1c, err := alloc.Allocate(container2, cidr1, true, returnFalse)
|
||||
addr1c, err := alloc.SimplyAllocate(container2, cidr1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, addr1b, addr1c, "address")
|
||||
|
||||
@@ -160,41 +168,51 @@ func TestAllocatorClaim(t *testing.T) {
|
||||
container1 = "abcdef"
|
||||
container3 = "b01df00d"
|
||||
universe = "10.0.3.0/24"
|
||||
testAddr1 = "10.0.3.2/24"
|
||||
testAddr1 = "10.0.3.5/24"
|
||||
testAddr2 = "10.0.4.2/24"
|
||||
testPre = "10.0.3.1/24"
|
||||
)
|
||||
|
||||
allocs, router, subnet := makeNetworkOfAllocators(2, universe)
|
||||
preAddr, _ := address.ParseCIDR(testPre)
|
||||
allocs, router, subnet := makeNetworkOfAllocators(2, universe, []PreClaim{{container1, true, preAddr}})
|
||||
defer stopNetworkOfAllocators(allocs, router)
|
||||
alloc := allocs[1]
|
||||
addr1, _ := address.ParseCIDR(testAddr1)
|
||||
|
||||
// First claim should trigger "dunno, I'm going to wait"
|
||||
err := alloc.Claim(container3, addr1, true, true)
|
||||
err := alloc.SimplyClaim(container3, addr1)
|
||||
require.NoError(t, err)
|
||||
|
||||
alloc.Prime()
|
||||
// Do an allocate on the other peer, which we will try to claim later
|
||||
addrx, err := allocs[0].Allocate(container1, subnet, true, returnFalse)
|
||||
// Should not get the address we pre-claimed
|
||||
require.NotEqual(t, addrx, preAddr)
|
||||
router.Flush()
|
||||
|
||||
// Now try the claim again
|
||||
err = alloc.Claim(container3, addr1, true, true)
|
||||
err = alloc.SimplyClaim(container3, addr1)
|
||||
require.NoError(t, err)
|
||||
// Check we get this address back if we try an allocate
|
||||
addr3, _ := alloc.Allocate(container3, subnet, true, returnFalse)
|
||||
addr3, _ := alloc.SimplyAllocate(container3, subnet)
|
||||
require.Equal(t, testAddr1, address.MakeCIDR(subnet, addr3).String(), "address")
|
||||
// one more claim should still work
|
||||
err = alloc.Claim(container3, addr1, true, true)
|
||||
err = alloc.SimplyClaim(container3, addr1)
|
||||
require.NoError(t, err)
|
||||
// claim for a different container should fail
|
||||
err = alloc.Claim(container1, addr1, true, true)
|
||||
err = alloc.SimplyClaim(container1, addr1)
|
||||
require.Error(t, err)
|
||||
// claiming the address allocated on the other peer should fail
|
||||
err = alloc.Claim(container1, address.MakeCIDR(subnet, addrx), true, true)
|
||||
err = alloc.SimplyClaim(container1, address.MakeCIDR(subnet, addrx))
|
||||
require.Error(t, err, "claiming address allocated on other peer should fail")
|
||||
// claiming the pre-claimed address should fail on both peers
|
||||
err = alloc.SimplyClaim(container3, preAddr)
|
||||
require.Error(t, err, "claiming address allocated on other peer should fail")
|
||||
err = allocs[0].SimplyClaim(container3, preAddr)
|
||||
require.Error(t, err, "claiming address allocated on other peer should fail")
|
||||
// Check an address outside of our universe
|
||||
addr2, _ := address.ParseCIDR(testAddr2)
|
||||
err = alloc.Claim(container1, addr2, true, true)
|
||||
err = alloc.SimplyClaim(container1, addr2)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -211,7 +229,7 @@ func (alloc *Allocator) pause() func() {
|
||||
}
|
||||
|
||||
func TestCancel(t *testing.T) {
|
||||
const cidr = "10.0.1.7/26"
|
||||
const cidr = "10.0.4.0/26"
|
||||
router := gossip.NewTestRouter(0.0)
|
||||
|
||||
alloc1, subnet := makeAllocator("01:00:00:02:00:00", cidr, 2)
|
||||
@@ -272,7 +290,7 @@ func TestCancel(t *testing.T) {
|
||||
|
||||
func TestCancelOnDied(t *testing.T) {
|
||||
const (
|
||||
cidr = "10.0.1.7/26"
|
||||
cidr = "10.0.4.0/26"
|
||||
container1 = "abcdef"
|
||||
)
|
||||
|
||||
@@ -314,18 +332,33 @@ func TestGossipShutdown(t *testing.T) {
|
||||
defer alloc.Stop()
|
||||
|
||||
alloc.claimRingForTesting()
|
||||
alloc.Allocate(container1, subnet, true, returnFalse)
|
||||
alloc.SimplyAllocate(container1, subnet)
|
||||
|
||||
alloc.Shutdown()
|
||||
|
||||
_, err := alloc.Allocate(container2, subnet, true, returnFalse) // trying to allocate after shutdown should fail
|
||||
_, err := alloc.SimplyAllocate(container2, subnet) // trying to allocate after shutdown should fail
|
||||
require.False(t, err == nil, "no address")
|
||||
|
||||
CheckAllExpectedMessagesSent(alloc)
|
||||
}
|
||||
|
||||
func TestNoFrag(t *testing.T) {
|
||||
const cidr = "10.0.4.0/22"
|
||||
resultChan := make(chan int)
|
||||
for i := 0; i < 100; i++ {
|
||||
allocs, router, subnet := makeNetworkOfAllocators(3, cidr)
|
||||
allocs[1].Allocate("foo", subnet, true, returnFalse)
|
||||
allocs[2].Allocate("bar", subnet, true, returnFalse)
|
||||
allocs[2].actionChan <- func() {
|
||||
resultChan <- len(allocs[2].ring.Entries)
|
||||
}
|
||||
require.True(t, <-resultChan < 6, "excessive ring fragmentation")
|
||||
stopNetworkOfAllocators(allocs, router)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransfer(t *testing.T) {
|
||||
const cidr = "10.0.1.7/22"
|
||||
const cidr = "10.0.4.0/22"
|
||||
allocs, router, subnet := makeNetworkOfAllocators(3, cidr)
|
||||
defer stopNetworkOfAllocators(allocs, router)
|
||||
alloc0 := allocs[0]
|
||||
@@ -365,7 +398,7 @@ func TestTransfer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFakeRouterSimple(t *testing.T) {
|
||||
const cidr = "10.0.1.7/22"
|
||||
const cidr = "10.0.4.0/22"
|
||||
allocs, router, subnet := makeNetworkOfAllocators(2, cidr)
|
||||
defer stopNetworkOfAllocators(allocs, router)
|
||||
|
||||
@@ -383,7 +416,7 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
nodes = 5
|
||||
maxAddresses = 1000
|
||||
concurrency = 5
|
||||
cidr = "10.0.1.7/22"
|
||||
cidr = "10.0.4.0/22"
|
||||
)
|
||||
allocs, router, subnet := makeNetworkOfAllocators(nodes, cidr)
|
||||
defer stopNetworkOfAllocators(allocs, router)
|
||||
@@ -453,7 +486,7 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
allocIndex := rand.Int31n(nodes)
|
||||
alloc := allocs[allocIndex]
|
||||
//common.Log.Infof("Allocate: asking allocator %d", allocIndex)
|
||||
addr, err := alloc.Allocate(name, subnet, true, returnFalse)
|
||||
addr, err := alloc.SimplyAllocate(name, subnet)
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Could not allocate addr"))
|
||||
@@ -510,7 +543,7 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
|
||||
//common.Log.Infof("Asking for %s (%s) on allocator %d again", res.name, addr, res.alloc)
|
||||
|
||||
newAddr, _ := alloc.Allocate(res.name, subnet, true, returnFalse)
|
||||
newAddr, _ := alloc.SimplyAllocate(res.name, subnet)
|
||||
oldAddr, _ := address.ParseIP(addr)
|
||||
if newAddr != oldAddr {
|
||||
panic(fmt.Sprintf("Got different address for repeat request for %s: %s != %s", res.name, newAddr, oldAddr))
|
||||
@@ -531,7 +564,7 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
addressIndex := rand.Int31n(int32(subnet.Size()))
|
||||
alloc := allocs[allocIndex]
|
||||
addr := address.Add(subnet.Addr, address.Offset(addressIndex))
|
||||
err := alloc.Claim(name, address.MakeCIDR(subnet, addr), true, true)
|
||||
err := alloc.SimplyClaim(name, address.MakeCIDR(subnet, addr))
|
||||
if err == nil {
|
||||
noteAllocation(allocIndex, name, addr)
|
||||
}
|
||||
|
||||
+29
-15
@@ -5,16 +5,18 @@ import (
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
type claim struct {
|
||||
resultChan chan<- error
|
||||
ident string
|
||||
cidr address.CIDR
|
||||
isContainer bool
|
||||
noErrorOnUnknown bool
|
||||
ident string // a container ID, something like "weave:expose", or api.NoContainerID
|
||||
cidr address.CIDR // single address being claimed
|
||||
isContainer bool // true if ident is a container ID
|
||||
noErrorOnUnknown bool // if false, error or block if we don't know; if true return ok but keep trying
|
||||
hasBeenCancelled func() bool
|
||||
}
|
||||
|
||||
// Send an error (or nil for success) back to caller listening on resultChan
|
||||
@@ -33,6 +35,11 @@ func (c *claim) sendResult(result error) {
|
||||
|
||||
// Try returns true for success (or failure), false if we need to try again later
|
||||
func (c *claim) Try(alloc *Allocator) bool {
|
||||
if c.hasBeenCancelled != nil && c.hasBeenCancelled() {
|
||||
c.Cancel()
|
||||
return true
|
||||
}
|
||||
|
||||
if !alloc.ring.Contains(c.cidr.Addr) {
|
||||
// Address not within our universe; assume user knows what they are doing
|
||||
alloc.infof("Address %s claimed by %s - not in our range", c.cidr, c.ident)
|
||||
@@ -51,11 +58,10 @@ func (c *claim) Try(alloc *Allocator) bool {
|
||||
// success
|
||||
case mesh.UnknownPeerName:
|
||||
// If our ring doesn't know, it must be empty.
|
||||
alloc.infof("Claim %s for %s: is in the range %s, but the allocator is not initialized yet; will try later.",
|
||||
c.cidr, c.ident, alloc.universe)
|
||||
if c.noErrorOnUnknown {
|
||||
alloc.infof("Claim %s for %s: address allocator still initializing; will try later.", c.cidr, c.ident)
|
||||
c.sendResult(nil)
|
||||
} else {
|
||||
c.sendResult(fmt.Errorf("%s is in the range %s, but the allocator is not initialized yet", c.cidr, alloc.universe.AsCIDRString()))
|
||||
}
|
||||
return false
|
||||
default:
|
||||
@@ -73,25 +79,33 @@ func (c *claim) Try(alloc *Allocator) bool {
|
||||
}
|
||||
|
||||
// We are the owner, check we haven't given it to another container
|
||||
switch existingIdent := alloc.findOwner(c.cidr.Addr); existingIdent {
|
||||
case "":
|
||||
existingIdent := alloc.findOwner(c.cidr.Addr)
|
||||
switch {
|
||||
case existingIdent == "":
|
||||
// Unused address, we try to claim it:
|
||||
if err := alloc.space.Claim(c.cidr.Addr); err == nil {
|
||||
alloc.debugln("Claimed", c.cidr, "for", c.ident)
|
||||
if c.ident == "_" { // Special "I don't have a unique ID" identifier
|
||||
c.ident = c.cidr.String()
|
||||
if c.ident == api.NoContainerID {
|
||||
alloc.addOwned(c.cidr.Addr.String(), c.cidr, c.isContainer)
|
||||
} else {
|
||||
alloc.addOwned(c.ident, c.cidr, c.isContainer)
|
||||
}
|
||||
alloc.addOwned(c.ident, c.cidr, c.isContainer)
|
||||
c.sendResult(nil)
|
||||
} else {
|
||||
c.sendResult(err)
|
||||
}
|
||||
case c.ident:
|
||||
case (existingIdent == c.ident) || (c.ident == api.NoContainerID && existingIdent == c.cidr.Addr.String()):
|
||||
// same identifier is claiming same address; that's OK
|
||||
alloc.debugln("Re-Claimed", c.cidr, "for", c.ident)
|
||||
c.sendResult(nil)
|
||||
case c.cidr.String():
|
||||
// Address already allocated via "_" name
|
||||
case existingIdent == c.cidr.Addr.String():
|
||||
// Address already allocated via api.NoContainerID name and current ID is a real container ID:
|
||||
c.sendResult(fmt.Errorf("address %s already in use", c.cidr))
|
||||
case c.ident == api.NoContainerID:
|
||||
// We do not know whether this is the same container or another one,
|
||||
// but we also cannot prove otherwise, so we let it reclaim the address:
|
||||
alloc.debugln("Re-Claimed", c.cidr, "for ID", c.ident, "having existing ID as", existingIdent)
|
||||
c.sendResult(nil)
|
||||
default:
|
||||
// Addr already owned by container on this machine
|
||||
c.sendResult(fmt.Errorf("address %s is already owned by %s", c.cidr.String(), existingIdent))
|
||||
|
||||
+52
-29
@@ -17,15 +17,17 @@ func badRequest(w http.ResponseWriter, err error) {
|
||||
}
|
||||
|
||||
func parseCIDR(w http.ResponseWriter, cidrStr string, net bool) (address.CIDR, bool) {
|
||||
cidr, err := address.ParseCIDR(cidrStr)
|
||||
var cidr address.CIDR
|
||||
var err error
|
||||
if net {
|
||||
cidr, err = ParseCIDRSubnet(cidrStr)
|
||||
} else {
|
||||
cidr, err = address.ParseCIDR(cidrStr)
|
||||
}
|
||||
if err != nil {
|
||||
badRequest(w, err)
|
||||
return address.CIDR{}, false
|
||||
}
|
||||
if net && !cidr.IsSubnet() {
|
||||
badRequest(w, fmt.Errorf("Invalid subnet %s - bits after network prefix are not all zero", cidrStr))
|
||||
return address.CIDR{}, false
|
||||
}
|
||||
return cidr, true
|
||||
}
|
||||
|
||||
@@ -38,34 +40,54 @@ func writeAddresses(w http.ResponseWriter, cidrs []address.CIDR) {
|
||||
}
|
||||
}
|
||||
|
||||
func hasBeenCancelled(dockerCli *docker.Client, closedChan <-chan bool, ident string, checkAlive bool) func() bool {
|
||||
return func() bool {
|
||||
select {
|
||||
case <-closedChan:
|
||||
return true
|
||||
default:
|
||||
res := checkAlive && dockerCli != nil && dockerCli.IsContainerNotRunning(ident)
|
||||
checkAlive = false // we check only once; if the container dies later we learn about that through events
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cancellationErr(w http.ResponseWriter, err error) bool {
|
||||
if _, ok := err.(*errorCancelled); ok {
|
||||
common.Log.Infoln("[allocator]:", err.Error())
|
||||
fmt.Fprint(w, "cancelled")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (alloc *Allocator) handleHTTPAllocate(dockerCli *docker.Client, w http.ResponseWriter, ident string, checkAlive bool, subnet address.CIDR) {
|
||||
closedChan := w.(http.CloseNotifier).CloseNotify()
|
||||
addr, err := alloc.Allocate(ident, subnet, checkAlive,
|
||||
func() bool {
|
||||
select {
|
||||
case <-closedChan:
|
||||
return true
|
||||
default:
|
||||
res := checkAlive && dockerCli != nil && dockerCli.IsContainerNotRunning(ident)
|
||||
checkAlive = false // we check only once; if the container dies later we learn about that through events
|
||||
return res
|
||||
}
|
||||
})
|
||||
hasBeenCancelled(dockerCli, w.(http.CloseNotifier).CloseNotify(), ident, checkAlive))
|
||||
if err != nil {
|
||||
if _, ok := err.(*errorCancelled); ok { // cancellation is not really an error
|
||||
common.Log.Infoln("[allocator]:", err.Error())
|
||||
fmt.Fprint(w, "cancelled")
|
||||
return
|
||||
if !cancellationErr(w, err) {
|
||||
badRequest(w, err)
|
||||
}
|
||||
badRequest(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s/%d", addr, subnet.PrefixLen)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) handleHTTPClaim(dockerCli *docker.Client, w http.ResponseWriter, ident string, cidr address.CIDR, checkAlive, noErrorOnUnknown bool) {
|
||||
err := alloc.Claim(ident, cidr, checkAlive, noErrorOnUnknown,
|
||||
hasBeenCancelled(dockerCli, w.(http.CloseNotifier).CloseNotify(), ident, checkAlive))
|
||||
if err != nil {
|
||||
if !cancellationErr(w, err) {
|
||||
badRequest(w, fmt.Errorf("Unable to claim: %s", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
w.WriteHeader(204)
|
||||
}
|
||||
|
||||
// HandleHTTP wires up ipams HTTP endpoints to the provided mux.
|
||||
func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, dockerCli *docker.Client) {
|
||||
func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, tracker string, dockerCli *docker.Client) {
|
||||
router.Methods("GET").Path("/ipinfo/defaultsubnet").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "%s", defaultSubnet)
|
||||
})
|
||||
@@ -73,13 +95,10 @@ func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CID
|
||||
router.Methods("PUT").Path("/ip/{id}/{ip}/{prefixlen}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
if cidr, ok := parseCIDR(w, vars["ip"]+"/"+vars["prefixlen"], false); ok {
|
||||
noErrorOnUnknown := r.FormValue("noErrorOnUnknown") == "true"
|
||||
ident := vars["id"]
|
||||
checkAlive := r.FormValue("check-alive") == "true"
|
||||
if err := alloc.Claim(vars["id"], cidr, checkAlive, noErrorOnUnknown); err != nil {
|
||||
badRequest(w, fmt.Errorf("Unable to claim: %s", err))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(204)
|
||||
noErrorOnUnknown := r.FormValue("noErrorOnUnknown") == "true"
|
||||
alloc.handleHTTPClaim(dockerCli, w, ident, cidr, checkAlive, noErrorOnUnknown)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -155,4 +174,8 @@ func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CID
|
||||
transferred := alloc.AdminTakeoverRanges(ident)
|
||||
fmt.Fprintf(w, "%d IPs taken over from %s\n", transferred, ident)
|
||||
})
|
||||
|
||||
router.Methods("GET").Path("/ipinfo/tracker").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, tracker)
|
||||
})
|
||||
}
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
+2
-3
@@ -14,8 +14,7 @@ type entry struct {
|
||||
Token address.Address // The start of this range
|
||||
Peer mesh.PeerName // Who owns this range
|
||||
Version uint32 // Version of this range
|
||||
Free address.Offset // Number of free IPs in this range
|
||||
// Note: Free should perhaps be an address.Count, but we can't change the wire protocol
|
||||
Free address.Count // Number of free IPs in this range
|
||||
}
|
||||
|
||||
func (e *entry) Equal(e2 *entry) bool {
|
||||
@@ -23,7 +22,7 @@ func (e *entry) Equal(e2 *entry) bool {
|
||||
e.Version == e2.Version
|
||||
}
|
||||
|
||||
func (e *entry) update(peername mesh.PeerName, free address.Offset) {
|
||||
func (e *entry) update(peername mesh.PeerName, free address.Count) {
|
||||
e.Peer = peername
|
||||
e.Version++
|
||||
e.Free = free
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package ring
|
||||
|
||||
/* Exported variables for monitoring and management */
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
expRingSize = expvar.NewMap("ipam.ringSize")
|
||||
expRingEntries = expvar.NewMap("ipam.ringEntries")
|
||||
)
|
||||
|
||||
type _int int
|
||||
|
||||
func (i _int) String() string {
|
||||
return strconv.Itoa(int(i))
|
||||
}
|
||||
|
||||
type _uint32 uint32
|
||||
|
||||
func (i _uint32) String() string {
|
||||
return strconv.FormatUint(uint64(i), 10)
|
||||
}
|
||||
|
||||
func (r *Ring) updateExportedVariables() {
|
||||
ringName := r.Start.String()
|
||||
expRingSize.Set(ringName, _uint32(r.End-r.Start))
|
||||
expRingEntries.Set(ringName, _int(len(r.Entries)))
|
||||
}
|
||||
+131
-81
@@ -18,12 +18,15 @@ import (
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
type OnUpdate func(prev, curr []address.Range, local bool)
|
||||
|
||||
// Ring represents the ring itself
|
||||
type Ring struct {
|
||||
Start, End address.Address // [min, max) tokens in this ring. Due to wrapping, min == max (effectively)
|
||||
Peer mesh.PeerName // name of peer owning this ring instance
|
||||
Entries entries // list of entries sorted by token
|
||||
Seeds []mesh.PeerName // peers with which the ring was seeded
|
||||
onUpdate OnUpdate
|
||||
}
|
||||
|
||||
func (r *Ring) assertInvariants() {
|
||||
@@ -38,9 +41,8 @@ var (
|
||||
ErrNotSorted = errors.New("Ring not sorted")
|
||||
ErrTokenRepeated = errors.New("Token appears twice in ring")
|
||||
ErrTokenOutOfRange = errors.New("Token is out of range")
|
||||
ErrDifferentSeeds = errors.New("Received ring was seeded differently from ours!")
|
||||
ErrDifferentRange = errors.New("Received range differs from ours!")
|
||||
ErrNotFound = errors.New("No entries for peer found")
|
||||
ErrDifferentSeeds = errors.New("Received ring was seeded differently from ours")
|
||||
ErrDifferentRange = errors.New("Received range differs from ours")
|
||||
)
|
||||
|
||||
func errInconsistentEntry(mine, theirs *entry) error {
|
||||
@@ -95,27 +97,46 @@ func (r *Ring) checkEntries(entries entries) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Ring) trackUpdates() func() {
|
||||
return r.trackUpdatesOfPeer(r.Peer)
|
||||
}
|
||||
|
||||
func (r *Ring) trackUpdatesOfPeer(peer mesh.PeerName) func() {
|
||||
if r.onUpdate == nil {
|
||||
return func() {}
|
||||
}
|
||||
ranges := r.OwnedRangesOfPeer(peer)
|
||||
return func() { r.onUpdate(ranges, r.OwnedRangesOfPeer(peer), peer == r.Peer) }
|
||||
}
|
||||
|
||||
// New creates an empty ring belonging to peer.
|
||||
func New(start, end address.Address, peer mesh.PeerName) *Ring {
|
||||
func New(start, end address.Address, peer mesh.PeerName, f OnUpdate) *Ring {
|
||||
common.Assert(start < end)
|
||||
|
||||
ring := &Ring{Start: start, End: end, Peer: peer, Entries: make([]*entry, 0)}
|
||||
ring.updateExportedVariables()
|
||||
ring := &Ring{Start: start, End: end, Peer: peer, Entries: make([]*entry, 0), onUpdate: f}
|
||||
return ring
|
||||
}
|
||||
|
||||
func (r *Ring) Restore(other *Ring) {
|
||||
defer r.trackUpdates()()
|
||||
|
||||
onUpdate := r.onUpdate
|
||||
*r = *other
|
||||
r.onUpdate = onUpdate
|
||||
}
|
||||
|
||||
func (r *Ring) Range() address.Range {
|
||||
return address.Range{Start: r.Start, End: r.End}
|
||||
}
|
||||
|
||||
// Returns the distance between two tokens on this ring, dealing
|
||||
// with ranges which cross the origin
|
||||
func (r *Ring) distance(start, end address.Address) address.Offset {
|
||||
func (r *Ring) distance(start, end address.Address) address.Count {
|
||||
if end > start {
|
||||
return address.Offset(end - start)
|
||||
return address.Count(end - start)
|
||||
}
|
||||
|
||||
return address.Offset((r.End - start) + (end - r.Start))
|
||||
return address.Count((r.End - start) + (end - r.Start))
|
||||
}
|
||||
|
||||
// GrantRangeToHost modifies the ring such that range [start, end)
|
||||
@@ -127,8 +148,8 @@ func (r *Ring) GrantRangeToHost(start, end address.Address, peer mesh.PeerName)
|
||||
//fmt.Printf("%s GrantRangeToHost [%v,%v) -> %s\n", r.Peer, start, end, peer)
|
||||
|
||||
r.assertInvariants()
|
||||
defer r.trackUpdates()()
|
||||
defer r.assertInvariants()
|
||||
defer r.updateExportedVariables()
|
||||
|
||||
// ----------------- Start of Checks -----------------
|
||||
|
||||
@@ -190,42 +211,69 @@ func (r *Ring) GrantRangeToHost(start, end address.Address, peer mesh.PeerName)
|
||||
r.Entries.insert(entry{Token: end, Peer: r.Peer, Free: endFree})
|
||||
}
|
||||
|
||||
// Merge the given ring into this ring and return any new ranges added
|
||||
func (r *Ring) Merge(gossip Ring) error {
|
||||
// Merge the given ring into this ring and indicate whether this ring
|
||||
// got updated as a result.
|
||||
func (r *Ring) Merge(gossip Ring) (bool, error) {
|
||||
r.assertInvariants()
|
||||
defer r.updateExportedVariables()
|
||||
defer r.trackUpdates()()
|
||||
|
||||
// Don't panic when checking the gossiped in ring.
|
||||
// In this case just return any error found.
|
||||
if err := gossip.checkInvariants(); err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(gossip.Seeds) > 0 && len(r.Seeds) > 0 {
|
||||
if len(gossip.Seeds) != len(r.Seeds) {
|
||||
return ErrDifferentSeeds
|
||||
return false, ErrDifferentSeeds
|
||||
}
|
||||
for i, seed := range gossip.Seeds {
|
||||
if seed != r.Seeds[i] {
|
||||
return ErrDifferentSeeds
|
||||
return false, ErrDifferentSeeds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r.Start != gossip.Start || r.End != gossip.End {
|
||||
return ErrDifferentRange
|
||||
return false, ErrDifferentRange
|
||||
}
|
||||
|
||||
// Now merge their ring with yours, in a temporary ring.
|
||||
var result entries
|
||||
addToResult := func(e entry) { result = append(result, &e) }
|
||||
result, updated, err := r.Entries.merge(gossip.Entries, r.Peer)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := r.checkEntries(result); err != nil {
|
||||
return false, fmt.Errorf("Merge of incoming data causes: %s", err)
|
||||
}
|
||||
|
||||
if len(r.Seeds) == 0 {
|
||||
r.Seeds = gossip.Seeds
|
||||
}
|
||||
r.Entries = result
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
// Merge other entries into ours, and complain when that stomps on
|
||||
// entries belonging to ourPeer. Returns the merged entries and an
|
||||
// indication whether the merge resulted in any changes, i.e. the
|
||||
// result differs from the original.
|
||||
func (es entries) merge(other entries, ourPeer mesh.PeerName) (result entries, updated bool, err error) {
|
||||
var mine, theirs *entry
|
||||
var previousOwner *mesh.PeerName
|
||||
// i is index into r.Entries; j is index into gossip.Entries
|
||||
addToResult := func(e entry) { result = append(result, &e) }
|
||||
addTheirs := func(e entry) {
|
||||
addToResult(e)
|
||||
updated = true
|
||||
previousOwner = nil
|
||||
}
|
||||
|
||||
// i is index into es; j is index into other
|
||||
var i, j int
|
||||
for i < len(r.Entries) && j < len(gossip.Entries) {
|
||||
mine, theirs = r.Entries[i], gossip.Entries[j]
|
||||
for i < len(es) && j < len(other) {
|
||||
mine, theirs = es[i], other[j]
|
||||
switch {
|
||||
case mine.Token < theirs.Token:
|
||||
addToResult(*mine)
|
||||
@@ -233,59 +281,52 @@ func (r *Ring) Merge(gossip Ring) error {
|
||||
i++
|
||||
case mine.Token > theirs.Token:
|
||||
// insert, checking that a range owned by us hasn't been split
|
||||
if previousOwner != nil && *previousOwner == r.Peer && theirs.Peer != r.Peer {
|
||||
return errEntryInMyRange(theirs)
|
||||
if previousOwner != nil && *previousOwner == ourPeer && theirs.Peer != ourPeer {
|
||||
err = errEntryInMyRange(theirs)
|
||||
return
|
||||
}
|
||||
addToResult(*theirs)
|
||||
previousOwner = nil
|
||||
addTheirs(*theirs)
|
||||
j++
|
||||
case mine.Token == theirs.Token:
|
||||
// merge
|
||||
switch {
|
||||
case mine.Version >= theirs.Version:
|
||||
if mine.Version == theirs.Version && !mine.Equal(theirs) {
|
||||
return errInconsistentEntry(mine, theirs)
|
||||
err = errInconsistentEntry(mine, theirs)
|
||||
return
|
||||
}
|
||||
addToResult(*mine)
|
||||
previousOwner = &mine.Peer
|
||||
case mine.Version < theirs.Version:
|
||||
if mine.Peer == r.Peer { // We shouldn't receive updates to our own tokens
|
||||
return errNewerVersion(mine, theirs)
|
||||
if mine.Peer == ourPeer { // We shouldn't receive updates to our own tokens
|
||||
err = errNewerVersion(mine, theirs)
|
||||
return
|
||||
}
|
||||
addToResult(*theirs)
|
||||
previousOwner = nil
|
||||
addTheirs(*theirs)
|
||||
}
|
||||
i++
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, either i is at the end of r or j is at the end
|
||||
// of gossip, so copy over the remaining entries.
|
||||
// At this point, either i is at the end of es or j is at the end
|
||||
// of other, so copy over the remaining entries.
|
||||
|
||||
for ; i < len(r.Entries); i++ {
|
||||
mine = r.Entries[i]
|
||||
for ; i < len(es); i++ {
|
||||
mine = es[i]
|
||||
addToResult(*mine)
|
||||
}
|
||||
|
||||
for ; j < len(gossip.Entries); j++ {
|
||||
theirs = gossip.Entries[j]
|
||||
if previousOwner != nil && *previousOwner == r.Peer && theirs.Peer != r.Peer {
|
||||
return errEntryInMyRange(theirs)
|
||||
for ; j < len(other); j++ {
|
||||
theirs = other[j]
|
||||
if previousOwner != nil && *previousOwner == ourPeer && theirs.Peer != ourPeer {
|
||||
err = errEntryInMyRange(theirs)
|
||||
return
|
||||
}
|
||||
addToResult(*theirs)
|
||||
previousOwner = nil
|
||||
addTheirs(*theirs)
|
||||
}
|
||||
|
||||
if err := r.checkEntries(result); err != nil {
|
||||
return fmt.Errorf("Merge of incoming data causes: %s", err)
|
||||
}
|
||||
|
||||
if len(r.Seeds) == 0 {
|
||||
r.Seeds = gossip.Seeds
|
||||
}
|
||||
r.Entries = result
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
// Empty returns true if the ring has no entries
|
||||
@@ -318,10 +359,14 @@ func (r *Ring) splitRangesOverZero(ranges []address.Range) []address.Range {
|
||||
// ranges are owned by this peer. Will split ranges which
|
||||
// span 0 in the ring.
|
||||
func (r *Ring) OwnedRanges() (result []address.Range) {
|
||||
return r.OwnedRangesOfPeer(r.Peer)
|
||||
}
|
||||
|
||||
func (r *Ring) OwnedRangesOfPeer(peer mesh.PeerName) (result []address.Range) {
|
||||
r.assertInvariants()
|
||||
|
||||
for i, entry := range r.Entries {
|
||||
if entry.Peer == r.Peer {
|
||||
if entry.Peer == peer {
|
||||
nextEntry := r.Entries.entry(i + 1)
|
||||
result = append(result, address.Range{Start: entry.Token, End: nextEntry.Token})
|
||||
}
|
||||
@@ -350,34 +395,37 @@ func (r *Ring) AllRangeInfo() (result []RangeInfo) {
|
||||
}
|
||||
|
||||
// ClaimForPeers claims the entire ring for the array of peers passed
|
||||
// in. Only works for empty rings.
|
||||
// in. Only works for empty rings. Each claimed range is CIDR-aligned.
|
||||
func (r *Ring) ClaimForPeers(peers []mesh.PeerName) {
|
||||
common.Assert(r.Empty())
|
||||
|
||||
defer r.trackUpdates()()
|
||||
defer r.assertInvariants()
|
||||
defer r.updateExportedVariables()
|
||||
|
||||
totalSize := r.distance(r.Start, r.End)
|
||||
share := totalSize/address.Offset(len(peers)) + 1
|
||||
remainder := totalSize % address.Offset(len(peers))
|
||||
pos := r.Start
|
||||
|
||||
for i, peer := range peers {
|
||||
if address.Offset(i) == remainder {
|
||||
share--
|
||||
if share == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
r.Entries.insert(entry{Token: pos, Peer: peer, Free: share})
|
||||
pos += address.Address(share)
|
||||
}
|
||||
|
||||
common.Assert(pos == r.End)
|
||||
defer func() {
|
||||
e := r.Entries[len(r.Entries)-1]
|
||||
common.Assert(address.Add(e.Token, address.Offset(e.Free)) == r.End)
|
||||
}()
|
||||
|
||||
r.subdivide(r.Start, r.End, peers)
|
||||
r.Seeds = peers
|
||||
}
|
||||
|
||||
// subdivide subdivides the [from,to) CIDR for the given peers into
|
||||
// CIDR-aligned subranges.
|
||||
func (r *Ring) subdivide(from, to address.Address, peers []mesh.PeerName) {
|
||||
share := address.Length(to, from)
|
||||
if share == 0 {
|
||||
return
|
||||
}
|
||||
if share == 1 || len(peers) == 1 {
|
||||
r.Entries.insert(entry{Token: from, Peer: peers[0], Free: share})
|
||||
return
|
||||
}
|
||||
mid := address.Add(from, address.Offset(share/2))
|
||||
r.subdivide(from, mid, peers[:len(peers)/2])
|
||||
r.subdivide(address.Add(mid, address.Offset(share%2)), to, peers[len(peers)/2:])
|
||||
}
|
||||
|
||||
func (r *Ring) FprintWithNicknames(w io.Writer, m map[mesh.PeerName]string) {
|
||||
for _, entry := range r.Entries {
|
||||
nickname, found := m[entry.Peer]
|
||||
@@ -399,11 +447,10 @@ func (r *Ring) String() string {
|
||||
|
||||
// ReportFree is used by the allocator to tell the ring how many free
|
||||
// ips are in a given range, so that ChoosePeersToAskForSpace can make
|
||||
// more intelligent decisions.
|
||||
func (r *Ring) ReportFree(freespace map[address.Address]address.Count) {
|
||||
// more intelligent decisions. Returns true if any changes made.
|
||||
func (r *Ring) ReportFree(freespace map[address.Address]address.Count) (updated bool) {
|
||||
r.assertInvariants()
|
||||
defer r.assertInvariants()
|
||||
defer r.updateExportedVariables()
|
||||
|
||||
common.Assert(!r.Empty())
|
||||
entries := r.Entries
|
||||
@@ -432,13 +479,15 @@ func (r *Ring) ReportFree(freespace map[address.Address]address.Count) {
|
||||
maxSize := r.distance(entry.Token, next.Token)
|
||||
common.Assert(free <= address.Count(maxSize))
|
||||
|
||||
if address.Count(entries[i].Free) == free {
|
||||
return
|
||||
if entries[i].Free == free {
|
||||
continue
|
||||
}
|
||||
|
||||
entries[i].Free = address.Offset(free)
|
||||
entries[i].Free = free
|
||||
entries[i].Version++
|
||||
updated = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type weightedPeer struct {
|
||||
@@ -455,7 +504,7 @@ func (ws weightedPeers) Swap(i, j int) { ws[i], ws[j] = ws[j], ws[i] }
|
||||
// ChoosePeersToAskForSpace returns all peers we can ask for space in
|
||||
// the range [start, end), in weighted-random order. Assumes start<end.
|
||||
func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []mesh.PeerName {
|
||||
totalSpacePerPeer := make(map[mesh.PeerName]address.Offset)
|
||||
totalSpacePerPeer := make(map[mesh.PeerName]address.Count)
|
||||
|
||||
// iterate through tokens
|
||||
for i, entry := range r.Entries {
|
||||
@@ -507,8 +556,9 @@ func (r *Ring) PickPeerForTransfer(isValid func(mesh.PeerName) bool) mesh.PeerNa
|
||||
// and return ranges indicating the new space we picked up
|
||||
func (r *Ring) Transfer(from, to mesh.PeerName) []address.Range {
|
||||
r.assertInvariants()
|
||||
defer r.trackUpdates()()
|
||||
defer r.trackUpdatesOfPeer(from)()
|
||||
defer r.assertInvariants()
|
||||
defer r.updateExportedVariables()
|
||||
|
||||
var newRanges []address.Range
|
||||
|
||||
|
||||
+124
-107
@@ -19,7 +19,8 @@ var (
|
||||
peer2name, _ = mesh.PeerNameFromString("02:00:00:00:02:00")
|
||||
peer3name, _ = mesh.PeerNameFromString("03:00:00:00:02:00")
|
||||
|
||||
start, end = ParseIP("10.0.0.0"), ParseIP("10.0.0.255")
|
||||
start, end = ParseIP("10.0.0.0"), ParseIP("10.0.1.0")
|
||||
dot8 = ParseIP("10.0.0.8")
|
||||
dot10, dot245 = ParseIP("10.0.0.10"), ParseIP("10.0.0.245")
|
||||
dot250 = ParseIP("10.0.0.250")
|
||||
middle = ParseIP("10.0.0.128")
|
||||
@@ -30,8 +31,17 @@ func ParseIP(s string) address.Address {
|
||||
return addr
|
||||
}
|
||||
|
||||
func merge(r1, r2 *Ring) error {
|
||||
_, err := r1.Merge(*r2)
|
||||
return err
|
||||
}
|
||||
|
||||
func NewRing(start, end address.Address, peer mesh.PeerName) *Ring {
|
||||
return New(start, end, peer, nil)
|
||||
}
|
||||
|
||||
func TestInvariants(t *testing.T) {
|
||||
ring := New(start, end, peer1name)
|
||||
ring := NewRing(start, end, peer1name)
|
||||
|
||||
// Check ring is sorted
|
||||
ring.Entries = []*entry{{Token: dot245, Peer: peer1name}, {Token: dot10, Peer: peer2name}}
|
||||
@@ -42,7 +52,7 @@ func TestInvariants(t *testing.T) {
|
||||
require.True(t, ring.checkInvariants() == ErrTokenRepeated, "Expected error")
|
||||
|
||||
// Check tokens are in bounds
|
||||
ring = New(dot10, dot245, peer1name)
|
||||
ring = NewRing(dot10, dot245, peer1name)
|
||||
ring.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
require.True(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
|
||||
|
||||
@@ -51,7 +61,7 @@ func TestInvariants(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInsert(t *testing.T) {
|
||||
ring := New(start, end, peer1name)
|
||||
ring := NewRing(start, end, peer1name)
|
||||
ring.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
|
||||
|
||||
require.Panics(t, func() {
|
||||
@@ -76,7 +86,7 @@ func TestInsert(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBetween(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
|
||||
|
||||
// First off, in a ring where everything is owned by the peer
|
||||
@@ -118,29 +128,29 @@ func TestBetween(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGrantSimple(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
|
||||
// Claim everything for peer1
|
||||
ring1.ClaimItAll()
|
||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 255}}, ring1.Entries)
|
||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 256}}, ring1.Entries)
|
||||
|
||||
// Now grant everything to peer2
|
||||
ring1.GrantRangeToHost(start, end, peer2name)
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}}
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
|
||||
// Now spint back to peer 1
|
||||
ring2.GrantRangeToHost(dot10, end, peer1name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 10, Version: 2},
|
||||
{Token: dot10, Peer: peer1name, Free: 245}}
|
||||
{Token: dot10, Peer: peer1name, Free: 246}}
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
|
||||
// And spint back to peer 2 again
|
||||
ring1.GrantRangeToHost(dot245, end, peer2name)
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 2},
|
||||
{Token: dot10, Peer: peer1name, Free: 235, Version: 1},
|
||||
{Token: dot245, Peer: peer2name, Free: 10}}, ring1.Entries)
|
||||
{Token: dot245, Peer: peer2name, Free: 11}}, ring1.Entries)
|
||||
|
||||
// Grant range spanning a live token
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
||||
@@ -153,19 +163,19 @@ func TestGrantSimple(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGrantSplit(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
|
||||
// Claim everything for peer1
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
|
||||
ring2.Merge(*ring1)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 256}}
|
||||
merge(ring2, ring1)
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
|
||||
// Now grant a split range to peer2
|
||||
ring1.GrantRangeToHost(dot10, dot245, peer2name)
|
||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 10, Version: 1},
|
||||
{Token: dot10, Peer: peer2name, Free: 235},
|
||||
{Token: dot245, Peer: peer1name, Free: 10}}, ring1.Entries)
|
||||
{Token: dot245, Peer: peer1name, Free: 11}}, ring1.Entries)
|
||||
|
||||
ring1.assertInvariants()
|
||||
|
||||
@@ -176,22 +186,22 @@ func TestGrantSplit(t *testing.T) {
|
||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
||||
{Token: dot10, Peer: peer2name, Free: 118, Version: 1},
|
||||
{Token: middle, Peer: peer2name, Free: 117, Version: 1},
|
||||
{Token: dot245, Peer: peer1name, Free: 10, Version: 0}}, ring1.Entries)
|
||||
{Token: dot245, Peer: peer1name, Free: 11, Version: 0}}, ring1.Entries)
|
||||
|
||||
ring1.assertInvariants()
|
||||
}
|
||||
|
||||
func TestMergeSimple(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
|
||||
// Claim everything for peer1
|
||||
ring1.ClaimItAll()
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
|
||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 128, Version: 1},
|
||||
{Token: middle, Peer: peer2name, Free: 127}}, ring1.Entries)
|
||||
{Token: middle, Peer: peer2name, Free: 128}}, ring1.Entries)
|
||||
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
|
||||
@@ -200,48 +210,48 @@ func TestMergeSimple(t *testing.T) {
|
||||
ring1.GrantRangeToHost(start, middle, peer2name)
|
||||
ring2.GrantRangeToHost(middle, end, peer1name)
|
||||
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, ring1.Merge(*ring2))
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 127, Version: 1}}, ring1.Entries)
|
||||
{Token: middle, Peer: peer1name, Free: 128, Version: 1}}, ring1.Entries)
|
||||
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
}
|
||||
|
||||
func TestMergeErrors(t *testing.T) {
|
||||
// Cannot Merge in an invalid ring
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}, {Token: start, Peer: peer2name}}
|
||||
require.True(t, ring1.Merge(*ring2) == ErrNotSorted, "Expected ErrNotSorted")
|
||||
require.True(t, merge(ring1, ring2) == ErrNotSorted, "Expected ErrNotSorted")
|
||||
|
||||
// Should Merge two rings for different ranges
|
||||
ring2 = New(start, middle, peer2name)
|
||||
ring2 = NewRing(start, middle, peer2name)
|
||||
ring2.Entries = []*entry{}
|
||||
require.True(t, ring1.Merge(*ring2) == ErrDifferentRange, "Expected ErrDifferentRange")
|
||||
require.True(t, merge(ring1, ring2) == ErrDifferentRange, "Expected ErrDifferentRange")
|
||||
|
||||
// Cannot Merge newer version of entry I own
|
||||
ring2 = New(start, end, peer2name)
|
||||
ring2 = NewRing(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Version: 1}}
|
||||
fmt.Println(ring1.Merge(*ring2))
|
||||
require.Error(t, ring1.Merge(*ring2), "Expected error")
|
||||
fmt.Println(merge(ring1, ring2))
|
||||
require.Error(t, merge(ring1, ring2), "Expected error")
|
||||
|
||||
// Cannot Merge two entries with same version but different hosts
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name}}
|
||||
require.Error(t, ring1.Merge(*ring2), "Expected error")
|
||||
require.Error(t, merge(ring1, ring2), "Expected error")
|
||||
|
||||
// Cannot Merge an entry into a range I own
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}}
|
||||
require.Error(t, ring1.Merge(*ring2), "Expected error")
|
||||
require.Error(t, merge(ring1, ring2), "Expected error")
|
||||
}
|
||||
|
||||
func TestMergeMore(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
|
||||
assertRing := func(ring *Ring, entries entries) {
|
||||
require.Equal(t, entries, ring.Entries)
|
||||
@@ -252,74 +262,74 @@ func TestMergeMore(t *testing.T) {
|
||||
|
||||
// Claim everything for peer1
|
||||
ring1.ClaimItAll()
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||
assertRing(ring2, []*entry{})
|
||||
|
||||
// Check the Merge sends it to the other ring
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||
|
||||
// Give everything to peer2
|
||||
ring1.GrantRangeToHost(start, end, peer2name)
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}})
|
||||
|
||||
// And carve off some space
|
||||
ring2.GrantRangeToHost(middle, end, peer1name)
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 127}})
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
||||
{Token: middle, Peer: peer1name, Free: 128}})
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}})
|
||||
|
||||
// And Merge back
|
||||
require.NoError(t, ring1.Merge(*ring2))
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 127}})
|
||||
{Token: middle, Peer: peer1name, Free: 128}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 127}})
|
||||
{Token: middle, Peer: peer1name, Free: 128}})
|
||||
|
||||
// This should be a no-op
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 127}})
|
||||
{Token: middle, Peer: peer1name, Free: 128}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 127}})
|
||||
{Token: middle, Peer: peer1name, Free: 128}})
|
||||
}
|
||||
|
||||
func TestMergeSplit(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
|
||||
// Claim everything for peer2
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 255}}
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 256}}
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
|
||||
// Now grant a split range to peer1
|
||||
ring2.GrantRangeToHost(dot10, dot245, peer1name)
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
{Token: dot10, Peer: peer1name, Free: 235},
|
||||
{Token: dot245, Peer: peer2name, Free: 10}}, ring2.Entries)
|
||||
{Token: dot245, Peer: peer2name, Free: 11}}, ring2.Entries)
|
||||
|
||||
require.NoError(t, ring1.Merge(*ring2))
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
{Token: dot10, Peer: peer1name, Free: 235},
|
||||
{Token: dot245, Peer: peer2name, Free: 10}}, ring1.Entries)
|
||||
{Token: dot245, Peer: peer2name, Free: 11}}, ring1.Entries)
|
||||
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
}
|
||||
|
||||
func TestMergeSplit2(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
|
||||
// Claim everything for peer2
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 250}, {Token: dot250, Peer: peer2name, Free: 5}}
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
|
||||
// Now grant a split range to peer1
|
||||
@@ -328,7 +338,7 @@ func TestMergeSplit2(t *testing.T) {
|
||||
{Token: dot10, Peer: peer1name, Free: 235},
|
||||
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}}, ring2.Entries)
|
||||
|
||||
require.NoError(t, ring1.Merge(*ring2))
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
{Token: dot10, Peer: peer1name, Free: 235},
|
||||
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}}, ring1.Entries)
|
||||
@@ -338,8 +348,8 @@ func TestMergeSplit2(t *testing.T) {
|
||||
|
||||
// A simple test, very similar to above, but using the marshalling to byte[]s
|
||||
func TestGossip(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
|
||||
assertRing := func(ring *Ring, entries entries) {
|
||||
require.Equal(t, entries, ring.Entries)
|
||||
@@ -350,13 +360,13 @@ func TestGossip(t *testing.T) {
|
||||
|
||||
// Claim everything for peer1
|
||||
ring1.ClaimItAll()
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||
assertRing(ring2, []*entry{})
|
||||
|
||||
// Check the Merge sends it to the other ring
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||
}
|
||||
|
||||
func assertPeersWithSpace(t *testing.T, ring *Ring, start, end address.Address, expected int) []mesh.PeerName {
|
||||
@@ -366,7 +376,7 @@ func assertPeersWithSpace(t *testing.T, ring *Ring, start, end address.Address,
|
||||
}
|
||||
|
||||
func TestFindFree(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
|
||||
assertPeersWithSpace(t, ring1, start, end, 0)
|
||||
|
||||
@@ -402,12 +412,12 @@ func TestFindFree(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReportFree(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
|
||||
ring1.ClaimItAll()
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
|
||||
freespace := make(map[address.Address]address.Count)
|
||||
for _, r := range ring2.OwnedRanges() {
|
||||
@@ -417,7 +427,7 @@ func TestReportFree(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMisc(t *testing.T) {
|
||||
ring := New(start, end, peer1name)
|
||||
ring := NewRing(start, end, peer1name)
|
||||
|
||||
require.True(t, ring.Empty(), "empty")
|
||||
|
||||
@@ -426,38 +436,38 @@ func TestMisc(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEmptyGossip(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
|
||||
ring1.ClaimItAll()
|
||||
// This used to panic, and it shouldn't
|
||||
require.NoError(t, ring1.Merge(*ring2))
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
}
|
||||
|
||||
func TestMergeOldMessage(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
|
||||
ring1.ClaimItAll()
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
|
||||
ring1.GrantRangeToHost(middle, end, peer1name)
|
||||
require.NoError(t, ring1.Merge(*ring2))
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
}
|
||||
|
||||
func TestSplitRangeAtBeginning(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
|
||||
ring1.ClaimItAll()
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
|
||||
ring1.GrantRangeToHost(start, middle, peer2name)
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
}
|
||||
|
||||
func TestOwnedRange(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring1.ClaimItAll()
|
||||
|
||||
require.Equal(t, []address.Range{{Start: start, End: end}}, ring1.OwnedRanges())
|
||||
@@ -465,8 +475,8 @@ func TestOwnedRange(t *testing.T) {
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
require.Equal(t, []address.Range{{Start: start, End: middle}}, ring1.OwnedRanges())
|
||||
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring2.Merge(*ring1)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
merge(ring2, ring1)
|
||||
require.Equal(t, []address.Range{{Start: middle, End: end}}, ring2.OwnedRanges())
|
||||
|
||||
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}}
|
||||
@@ -480,24 +490,24 @@ func TestOwnedRange(t *testing.T) {
|
||||
|
||||
func TestTransfer(t *testing.T) {
|
||||
// First test just checks if we can grant some range to a host, when we transfer it, we get it back
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring1.ClaimItAll()
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
ring1.Transfer(peer2name, peer1name)
|
||||
require.Equal(t, []address.Range{{start, middle}, {middle, end}}, ring1.OwnedRanges())
|
||||
require.Equal(t, []address.Range{{Start: start, End: middle}, {Start: middle, End: end}}, ring1.OwnedRanges())
|
||||
|
||||
// Second test is what happens when a token exists at the end of a range but is transferred
|
||||
// - does it get resurrected correctly?
|
||||
ring1 = New(start, end, peer1name)
|
||||
ring1 = NewRing(start, end, peer1name)
|
||||
ring1.ClaimItAll()
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
ring1.Transfer(peer2name, peer1name)
|
||||
ring1.GrantRangeToHost(dot10, middle, peer2name)
|
||||
require.Equal(t, []address.Range{{start, dot10}, {middle, end}}, ring1.OwnedRanges())
|
||||
require.Equal(t, []address.Range{{Start: start, End: dot10}, {Start: middle, End: end}}, ring1.OwnedRanges())
|
||||
}
|
||||
|
||||
func TestOwner(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
require.True(t, ring1.Contains(start), "start should be in ring")
|
||||
require.False(t, ring1.Contains(end), "end should not be in ring")
|
||||
|
||||
@@ -521,17 +531,24 @@ func makePeerName(i int) mesh.PeerName {
|
||||
return peer
|
||||
}
|
||||
|
||||
func makePeers(numPeers int) []mesh.PeerName {
|
||||
peers := make([]mesh.PeerName, numPeers)
|
||||
for i := 0; i < numPeers; i++ {
|
||||
peers[i] = makePeerName(i)
|
||||
}
|
||||
return peers
|
||||
}
|
||||
|
||||
func TestClaimForPeers(t *testing.T) {
|
||||
const numPeers = 12
|
||||
// Different end to usual so we get a number of addresses that a)
|
||||
// is smaller than the max number of peers, and b) is divisible by
|
||||
// some number of peers. This maximises coverage of edge cases.
|
||||
end := dot10
|
||||
peers := make([]mesh.PeerName, numPeers)
|
||||
end := dot8
|
||||
peers := makePeers(numPeers)
|
||||
// Test for a range of peer counts
|
||||
for i := 0; i < numPeers; i++ {
|
||||
peers[i] = makePeerName(i)
|
||||
ring := New(start, end, peers[0])
|
||||
ring := NewRing(start, end, peers[0])
|
||||
ring.ClaimForPeers(peers[:i+1])
|
||||
}
|
||||
}
|
||||
@@ -569,7 +586,7 @@ func TestFuzzRing(t *testing.T) {
|
||||
sort.Sort(addressSlice(tokens))
|
||||
|
||||
peer := peers[rand.Intn(len(peers))]
|
||||
ring := New(start, end, peer)
|
||||
ring := NewRing(start, end, peer)
|
||||
for _, token := range tokens {
|
||||
peer = peers[rand.Intn(len(peers))]
|
||||
ring.Entries = append(ring.Entries, &entry{Token: start + token, Peer: peer})
|
||||
@@ -586,7 +603,7 @@ func TestFuzzRing(t *testing.T) {
|
||||
|
||||
// Merge them - this might fail, we don't care
|
||||
// We just want to make sure it doesn't panic
|
||||
ring1.Merge(*ring2)
|
||||
merge(ring1, ring2)
|
||||
|
||||
// Check whats left still passes assertions
|
||||
ring1.assertInvariants()
|
||||
@@ -603,7 +620,7 @@ func TestFuzzRing(t *testing.T) {
|
||||
}
|
||||
|
||||
peer := peers[rand.Intn(len(peers))]
|
||||
ring := New(start, end, peer)
|
||||
ring := NewRing(start, end, peer)
|
||||
for _, token := range tokens {
|
||||
peer = peers[rand.Intn(len(peers))]
|
||||
ring.Entries = append(ring.Entries, &entry{Token: start + token, Peer: peer})
|
||||
@@ -619,7 +636,7 @@ func TestFuzzRing(t *testing.T) {
|
||||
|
||||
// Merge them - this might fail, we don't care
|
||||
// We just want to make sure it doesn't panic
|
||||
ring1.Merge(*ring2)
|
||||
merge(ring1, ring2)
|
||||
|
||||
// Check whats left still passes assertions
|
||||
ring1.assertInvariants()
|
||||
@@ -641,7 +658,7 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
common.Log.Debugf("%s: Adding peer", peer)
|
||||
nextPeerID++
|
||||
peers = append(peers, peer)
|
||||
rings = append(rings, New(start, end, peer))
|
||||
rings = append(rings, NewRing(start, end, peer))
|
||||
}
|
||||
|
||||
for i := 0; i < numPeers; i++ {
|
||||
@@ -680,7 +697,7 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
|
||||
// We need to be in a ~converged ring to rmpeer
|
||||
for _, ring := range rings {
|
||||
require.NoError(t, otherRing.Merge(*ring))
|
||||
require.NoError(t, merge(otherRing, ring))
|
||||
}
|
||||
|
||||
common.Log.Debugf("%s: transferring from peer %s", otherPeername, peername)
|
||||
@@ -694,7 +711,7 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
// And now tell everyone about the transfer - rmpeer is
|
||||
// not partition safe
|
||||
for i, ring := range rings {
|
||||
require.NoError(t, ring.Merge(*otherRing))
|
||||
require.NoError(t, merge(ring, otherRing))
|
||||
theRanges[i] = ring.OwnedRanges()
|
||||
}
|
||||
}
|
||||
@@ -723,7 +740,7 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
// Now 'gossip' this to a random host (note, note could be same host as above)
|
||||
otherIndex, _, otherRing := randomPeer(-1)
|
||||
common.Log.Debugf("%s: 'Gossiping' to %s", ring.Peer, otherRing.Peer)
|
||||
require.NoError(t, otherRing.Merge(*ring))
|
||||
require.NoError(t, merge(otherRing, ring))
|
||||
|
||||
theRanges[indexWithRanges] = ring.OwnedRanges()
|
||||
theRanges[otherIndex] = otherRing.OwnedRanges()
|
||||
@@ -742,7 +759,7 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
ring1 := ringsWithEntries[rand.Intn(len(ringsWithEntries))]
|
||||
ring2index, _, ring2 := randomPeer(-1)
|
||||
common.Log.Debugf("%s: 'Gossiping' to %s", ring1.Peer, ring2.Peer)
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, merge(ring2, ring1))
|
||||
theRanges[ring2index] = ring2.OwnedRanges()
|
||||
}
|
||||
|
||||
|
||||
+19
-5
@@ -88,6 +88,14 @@ func (s *Space) Claim(addr address.Address) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Space) NumOwnedAddresses() address.Count {
|
||||
res := address.Count(0)
|
||||
for i := 0; i < len(s.ours); i += 2 {
|
||||
res += address.Length(s.ours[i+1], s.ours[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *Space) NumFreeAddresses() address.Count {
|
||||
res := address.Count(0)
|
||||
for i := 0; i < len(s.free); i += 2 {
|
||||
@@ -122,8 +130,11 @@ func (s *Space) biggestFreeRange(r address.Range) (biggest address.Range) {
|
||||
biggestSize := address.Count(0)
|
||||
s.walkFree(r, func(chunk address.Range) bool {
|
||||
if size := chunk.Size(); size >= biggestSize {
|
||||
biggest = chunk
|
||||
biggestSize = size
|
||||
chunk = chunk.BiggestCIDRRange()
|
||||
if size = chunk.Size(); size >= biggestSize {
|
||||
biggest = chunk
|
||||
biggestSize = size
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
@@ -137,9 +148,12 @@ func (s *Space) Donate(r address.Range) (address.Range, bool) {
|
||||
return address.Range{}, false
|
||||
}
|
||||
|
||||
// Donate half of that biggest free range. Note size/2 rounds down, so
|
||||
// the resulting donation size rounds up, and in particular can't be empty.
|
||||
biggest.Start = address.Add(biggest.Start, address.Offset(biggest.Size()/2))
|
||||
// Donate no more than half what we have available
|
||||
if biggest.Size() > s.NumFreeAddressesInRange(r)/2 {
|
||||
// Note size/2 rounds down, so the resulting donation size
|
||||
// rounds up, and in particular can't be empty.
|
||||
biggest.Start = address.Add(biggest.Start, address.Offset(biggest.Size()/2))
|
||||
}
|
||||
|
||||
s.ours = subtract(s.ours, biggest.Start, biggest.End)
|
||||
s.free = subtract(s.free, biggest.Start, biggest.End)
|
||||
|
||||
+35
-31
@@ -68,7 +68,7 @@ func TestLowlevel(t *testing.T) {
|
||||
wt.AssertErrorInterface(t, (*error)(nil), s.Free(100), "double free")
|
||||
|
||||
r, ok := s.Donate(address.NewRange(0, 1000))
|
||||
require.True(t, ok && r.Start == 125 && r.Size() == 25, "donate")
|
||||
require.Equal(t, address.NewRange(0xa0, 0x20), r, "donate")
|
||||
|
||||
// test Donate when addresses are scarce
|
||||
s = New()
|
||||
@@ -78,7 +78,7 @@ func TestLowlevel(t *testing.T) {
|
||||
require.NoError(t, s.Claim(0))
|
||||
require.NoError(t, s.Claim(2))
|
||||
r, ok = s.Donate(address.NewRange(0, 1000))
|
||||
require.True(t, ok && r.Start == 1 && r.End == 2, "donate")
|
||||
require.Equal(t, address.NewRange(1, 1), r, "donate")
|
||||
r, ok = s.Donate(address.NewRange(0, 1000))
|
||||
require.True(t, !ok, "donate should fail")
|
||||
}
|
||||
@@ -98,16 +98,19 @@ func TestSpaceAllocate(t *testing.T) {
|
||||
|
||||
space1 := makeSpace(start, size)
|
||||
require.Equal(t, address.Count(20), space1.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(0), space1.NumOwnedAddresses())
|
||||
space1.assertInvariants()
|
||||
|
||||
_, addr1 := space1.Allocate(address.NewRange(start, size))
|
||||
require.Equal(t, testAddr1, addr1.String(), "address")
|
||||
require.Equal(t, address.Count(19), space1.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(1), space1.NumOwnedAddresses())
|
||||
space1.assertInvariants()
|
||||
|
||||
_, addr2 := space1.Allocate(address.NewRange(start, size))
|
||||
require.False(t, addr2.String() == testAddr1, "address")
|
||||
require.Equal(t, address.Count(18), space1.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(2), space1.NumOwnedAddresses())
|
||||
require.Equal(t, address.Count(13), space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddrx)}))
|
||||
require.Equal(t, address.Count(18), space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddry)}))
|
||||
space1.assertInvariants()
|
||||
@@ -124,20 +127,21 @@ func TestSpaceAllocate(t *testing.T) {
|
||||
|
||||
func TestSpaceFree(t *testing.T) {
|
||||
const (
|
||||
testAddr1 = "10.0.3.4"
|
||||
testAddr1 = "10.0.3.16"
|
||||
testAddrx = "10.0.3.19"
|
||||
testAddry = "10.0.9.19"
|
||||
containerID = "deadbeef"
|
||||
size = 16
|
||||
)
|
||||
|
||||
entireRange := address.NewRange(ip(testAddr1), 20)
|
||||
space := makeSpace(ip(testAddr1), 20)
|
||||
entireRange := address.NewRange(ip(testAddr1), size)
|
||||
space := makeSpace(ip(testAddr1), size)
|
||||
|
||||
// Check we are prepared to give up the entire space
|
||||
r := space.biggestFreeRange(entireRange)
|
||||
require.True(t, r.Start == ip(testAddr1) && r.Size() == 20, "Wrong space")
|
||||
require.Equal(t, address.NewRange(ip(testAddr1), size), r, "Wrong space")
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
for i := 0; i < size; i++ {
|
||||
ok, _ := space.Allocate(entireRange)
|
||||
require.True(t, ok, "Failed to get address")
|
||||
}
|
||||
@@ -149,44 +153,44 @@ func TestSpaceFree(t *testing.T) {
|
||||
require.True(t, r.Size() == 0, "Wrong space")
|
||||
|
||||
// Free in the middle
|
||||
require.NoError(t, space.Free(ip("10.0.3.13")))
|
||||
r = space.biggestFreeRange(entireRange)
|
||||
require.True(t, r.Start == ip("10.0.3.13") && r.Size() == 1, "Wrong space")
|
||||
|
||||
// Free one at the end
|
||||
require.NoError(t, space.Free(ip("10.0.3.23")))
|
||||
r = space.biggestFreeRange(entireRange)
|
||||
require.True(t, r.Start == ip("10.0.3.23") && r.Size() == 1, "Wrong space")
|
||||
|
||||
// Free one at the end
|
||||
require.NoError(t, space.Free(ip("10.0.3.31")))
|
||||
r = space.biggestFreeRange(entireRange)
|
||||
require.True(t, r.Start == ip("10.0.3.31") && r.Size() == 1, "Wrong space")
|
||||
|
||||
// Now free a few at the end
|
||||
require.NoError(t, space.Free(ip("10.0.3.22")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.21")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.30")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.29")))
|
||||
|
||||
require.Equal(t, address.Count(4), space.NumFreeAddresses())
|
||||
|
||||
// Now get the biggest free space; should be 3.21
|
||||
// Now get the biggest free space; should be 3.30
|
||||
r = space.biggestFreeRange(entireRange)
|
||||
require.True(t, r.Start == ip("10.0.3.21") && r.Size() == 3, "Wrong space")
|
||||
require.Equal(t, address.NewRange(ip("10.0.3.30"), 2), r, "Wrong space")
|
||||
|
||||
// Now free a few in the middle
|
||||
require.NoError(t, space.Free(ip("10.0.3.12")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.11")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.10")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.24")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.22")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.21")))
|
||||
|
||||
require.Equal(t, address.Count(7), space.NumFreeAddresses())
|
||||
|
||||
// Now get the biggest free space; should be 3.21
|
||||
// Now get the biggest free space; should be 3.30
|
||||
r = space.biggestFreeRange(entireRange)
|
||||
require.True(t, r.Start == ip("10.0.3.10") && r.Size() == 4, "Wrong space")
|
||||
require.Equal(t, address.NewRange(ip("10.0.3.30"), 2), r, "Wrong space")
|
||||
|
||||
require.Equal(t, []address.Range{{Start: ip("10.0.3.4"), End: ip("10.0.3.24")}}, space.OwnedRanges())
|
||||
require.Equal(t, []address.Range{{Start: ip("10.0.3.16"), End: ip("10.0.3.32")}}, space.OwnedRanges())
|
||||
}
|
||||
|
||||
func TestDonateSimple(t *testing.T) {
|
||||
const (
|
||||
testAddr1 = "10.0.1.0"
|
||||
testAddr2 = "10.0.1.32"
|
||||
size = 48
|
||||
size = 64
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -199,7 +203,7 @@ func TestDonateSimple(t *testing.T) {
|
||||
r, ok := ps1.Donate(address.NewRange(ip(testAddr1), size))
|
||||
numGivenUp := r.Size()
|
||||
require.True(t, ok, "Donate result")
|
||||
require.Equal(t, "10.0.1.24", r.Start.String(), "Invalid start")
|
||||
require.Equal(t, "10.0.1.32", r.Start.String(), "Invalid start")
|
||||
require.Equal(t, address.Count(size/2), numGivenUp)
|
||||
require.Equal(t, address.Count(size/2), ps1.NumFreeAddresses())
|
||||
|
||||
@@ -241,13 +245,13 @@ func TestDonateHard(t *testing.T) {
|
||||
// Now split
|
||||
newRange, ok := spaceset.Donate(address.NewRange(start, size))
|
||||
require.True(t, ok, "GiveUpSpace result")
|
||||
require.Equal(t, ip("10.0.1.23"), newRange.Start)
|
||||
require.Equal(t, address.Count(24), newRange.Size())
|
||||
require.Equal(t, address.Count(23), spaceset.NumFreeAddresses())
|
||||
require.Equal(t, address.NewRange(ip("10.0.1.16"), 16), newRange, "Wrong space")
|
||||
require.Equal(t, address.Count(31), spaceset.NumFreeAddresses())
|
||||
|
||||
//Space set should now have 2 spaces
|
||||
expected := New()
|
||||
expected.Add(start, 23)
|
||||
expected.ours = add(nil, ip("10.0.1.47"), ip("10.0.1.48"))
|
||||
//Space set should now have 3 spaces
|
||||
expected := &Space{
|
||||
ours: add(nil, ip("10.0.1.47"), ip("10.0.1.48")),
|
||||
free: add(add(nil, ip("10.0.1.0"), ip("10.0.1.16")), ip("10.0.1.32"), ip("10.0.1.47")),
|
||||
}
|
||||
require.Equal(t, expected, spaceset)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ type Status struct {
|
||||
Paxos *paxos.Status
|
||||
Range string
|
||||
RangeNumIPs int
|
||||
ActiveIPs int
|
||||
DefaultSubnet string
|
||||
Entries []EntryStatus
|
||||
PendingClaims []ClaimStatus
|
||||
@@ -52,6 +53,7 @@ func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status {
|
||||
paxosStatus,
|
||||
allocator.universe.String(),
|
||||
int(allocator.universe.Size()),
|
||||
int(allocator.space.NumOwnedAddresses()),
|
||||
defaultSubnet.String(),
|
||||
newEntryStatusSlice(allocator),
|
||||
newClaimStatusSlice(allocator),
|
||||
|
||||
@@ -128,7 +128,7 @@ type mockDB struct{}
|
||||
func (d *mockDB) Load(_ string, _ interface{}) (bool, error) { return false, nil }
|
||||
func (d *mockDB) Save(_ string, _ interface{}) error { return nil }
|
||||
|
||||
func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, address.CIDR) {
|
||||
func makeAllocator(name string, cidrStr string, quorum uint, preClaims ...PreClaim) (*Allocator, address.CIDR) {
|
||||
peername, err := mesh.PeerNameFromString(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -143,8 +143,9 @@ func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, addres
|
||||
OurName: peername,
|
||||
OurUID: mesh.PeerUID(rand.Int63()),
|
||||
OurNickname: "nick-" + name,
|
||||
Universe: cidr.Range(),
|
||||
Universe: cidr,
|
||||
IsObserver: quorum == 0,
|
||||
PreClaims: preClaims,
|
||||
Quorum: func() uint { return quorum },
|
||||
Db: new(mockDB),
|
||||
IsKnownPeer: func(mesh.PeerName) bool { return true },
|
||||
@@ -213,15 +214,19 @@ func AssertNothingSentErr(t *testing.T, ch <-chan error) {
|
||||
}
|
||||
}
|
||||
|
||||
func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, *gossip.TestRouter, address.CIDR) {
|
||||
func makeNetworkOfAllocators(size int, cidr string, preClaims ...[]PreClaim) ([]*Allocator, *gossip.TestRouter, address.CIDR) {
|
||||
gossipRouter := gossip.NewTestRouter(0.0)
|
||||
allocs := make([]*Allocator, size)
|
||||
var subnet address.CIDR
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
var alloc *Allocator
|
||||
preClaim := []PreClaim{}
|
||||
if i < len(preClaims) {
|
||||
preClaim = preClaims[i]
|
||||
}
|
||||
alloc, subnet = makeAllocator(fmt.Sprintf("%02d:00:00:02:00:00", i),
|
||||
cidr, uint(size/2+1))
|
||||
cidr, uint(size/2+1), preClaim...)
|
||||
alloc.SetInterfaces(gossipRouter.Connect(alloc.ourName, alloc))
|
||||
alloc.Start()
|
||||
allocs[i] = alloc
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
package tracker
|
||||
|
||||
// The AWSVPC tracker tracks the IPAM ring changes and accordingly updates
|
||||
// the AWS VPC route table and, if the announced change is local
|
||||
// (denoted by local=true) to the host, the host route table.
|
||||
//
|
||||
// During the initialization, the tracker detects AWS VPC route table id which
|
||||
// is associated with the host's subnet on the AWS network. If such a table does
|
||||
// not exist, the default VPC route table is used.
|
||||
//
|
||||
// When a host A donates a range to a host B, the necessary route table
|
||||
// updates (removal) happen on the host A first, and afterwards on
|
||||
// the host B (installation).
|
||||
//
|
||||
// NB: there is a hard limit for 50 routes within any VPC route table
|
||||
// (practically, it is 48, because one route is used by the AWS Internet GW and
|
||||
// one by the AWS host subnet), therefore it is suggested to avoid
|
||||
// an excessive fragmentation within the IPAM ring which might happen due to
|
||||
// the claim operations or uneven distribution of containers across the hosts.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
wnet "github.com/weaveworks/weave/net"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
type AWSVPCTracker struct {
|
||||
ec2 *ec2.EC2
|
||||
instanceID string // EC2 Instance ID
|
||||
routeTableID string // VPC Route Table ID
|
||||
linkIndex int // The weave bridge link index
|
||||
}
|
||||
|
||||
// NewAWSVPCTracker creates and initialises AWS VPC based tracker.
|
||||
func NewAWSVPCTracker() (*AWSVPCTracker, error) {
|
||||
var (
|
||||
err error
|
||||
session = session.New()
|
||||
t = &AWSVPCTracker{}
|
||||
)
|
||||
|
||||
// Detect region and instance id
|
||||
meta := ec2metadata.New(session)
|
||||
t.instanceID, err = meta.GetMetadata("instance-id")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot detect instance-id: %s", err)
|
||||
}
|
||||
region, err := meta.Region()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot detect region: %s", err)
|
||||
}
|
||||
|
||||
t.ec2 = ec2.New(session, aws.NewConfig().WithRegion(region))
|
||||
|
||||
routeTableID, err := t.detectRouteTableID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.routeTableID = *routeTableID
|
||||
|
||||
// Detect Weave bridge link index
|
||||
link, err := netlink.LinkByName(wnet.WeaveBridgeName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find \"%s\" interface: %s", wnet.WeaveBridgeName, err)
|
||||
}
|
||||
t.linkIndex = link.Attrs().Index
|
||||
|
||||
t.infof("AWSVPC has been initialized on %s instance for %s route table at %s region",
|
||||
t.instanceID, t.routeTableID, region)
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// HandleUpdate method updates the AWS VPC and the host route tables.
|
||||
func (t *AWSVPCTracker) HandleUpdate(prevRanges, currRanges []address.Range, local bool) error {
|
||||
t.debugf("replacing %q by %q; local(%t)", prevRanges, currRanges, local)
|
||||
|
||||
prev, curr := removeCommon(address.NewCIDRs(merge(prevRanges)), address.NewCIDRs(merge(currRanges)))
|
||||
|
||||
// It might make sense to do the removal first and then add entries
|
||||
// because of the 50 routes limit. However, in such case a container might
|
||||
// not be reachable for a short period of time which is not a desired behavior.
|
||||
|
||||
// Add new entries
|
||||
for _, cidr := range curr {
|
||||
cidrStr := cidr.String()
|
||||
t.debugf("adding route %s to %s", cidrStr, t.instanceID)
|
||||
if _, err := t.createVPCRoute(cidrStr); err != nil {
|
||||
return fmt.Errorf("createVPCRoutes failed: %s", err)
|
||||
}
|
||||
if local {
|
||||
if err := t.createHostRoute(cidrStr); err != nil {
|
||||
if errno, ok := err.(syscall.Errno); !(ok && errno == syscall.EEXIST) {
|
||||
return fmt.Errorf("createHostRoute failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove obsolete entries
|
||||
for _, cidr := range prev {
|
||||
cidrStr := cidr.String()
|
||||
t.debugf("removing %s route", cidrStr)
|
||||
if _, err := t.deleteVPCRoute(cidrStr); err != nil {
|
||||
return fmt.Errorf("deleteVPCRoute failed: %s", err)
|
||||
}
|
||||
if local {
|
||||
if err := t.deleteHostRoute(cidrStr); err != nil {
|
||||
return fmt.Errorf("deleteHostRoute failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *AWSVPCTracker) createVPCRoute(cidr string) (*ec2.CreateRouteOutput, error) {
|
||||
route := &ec2.CreateRouteInput{
|
||||
RouteTableId: &t.routeTableID,
|
||||
InstanceId: &t.instanceID,
|
||||
DestinationCidrBlock: &cidr,
|
||||
}
|
||||
return t.ec2.CreateRoute(route)
|
||||
}
|
||||
|
||||
func (t *AWSVPCTracker) createHostRoute(cidr string) error {
|
||||
dst, err := parseCIDR(cidr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
route := &netlink.Route{
|
||||
LinkIndex: t.linkIndex,
|
||||
Dst: dst,
|
||||
Scope: netlink.SCOPE_LINK,
|
||||
}
|
||||
return netlink.RouteAdd(route)
|
||||
}
|
||||
|
||||
func (t *AWSVPCTracker) deleteVPCRoute(cidr string) (*ec2.DeleteRouteOutput, error) {
|
||||
route := &ec2.DeleteRouteInput{
|
||||
RouteTableId: &t.routeTableID,
|
||||
DestinationCidrBlock: &cidr,
|
||||
}
|
||||
return t.ec2.DeleteRoute(route)
|
||||
}
|
||||
|
||||
func (t *AWSVPCTracker) deleteHostRoute(cidr string) error {
|
||||
dst, err := parseCIDR(cidr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
route := &netlink.Route{
|
||||
LinkIndex: t.linkIndex,
|
||||
Dst: dst,
|
||||
Scope: netlink.SCOPE_LINK,
|
||||
}
|
||||
return netlink.RouteDel(route)
|
||||
}
|
||||
|
||||
// detectRouteTableID detects AWS VPC Route Table ID of the given tracker instance.
|
||||
func (t *AWSVPCTracker) detectRouteTableID() (*string, error) {
|
||||
instancesParams := &ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{aws.String(t.instanceID)},
|
||||
}
|
||||
instancesResp, err := t.ec2.DescribeInstances(instancesParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DescribeInstances failed: %s", err)
|
||||
}
|
||||
if len(instancesResp.Reservations) == 0 ||
|
||||
len(instancesResp.Reservations[0].Instances) == 0 {
|
||||
return nil, fmt.Errorf("cannot find %s instance within reservations", t.instanceID)
|
||||
}
|
||||
vpcID := instancesResp.Reservations[0].Instances[0].VpcId
|
||||
subnetID := instancesResp.Reservations[0].Instances[0].SubnetId
|
||||
|
||||
// First try to find a routing table associated with the subnet of the instance
|
||||
tablesParams := &ec2.DescribeRouteTablesInput{
|
||||
Filters: []*ec2.Filter{
|
||||
{
|
||||
Name: aws.String("association.subnet-id"),
|
||||
Values: []*string{subnetID},
|
||||
},
|
||||
},
|
||||
}
|
||||
tablesResp, err := t.ec2.DescribeRouteTables(tablesParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DescribeRouteTables failed: %s", err)
|
||||
}
|
||||
if len(tablesResp.RouteTables) != 0 {
|
||||
return tablesResp.RouteTables[0].RouteTableId, nil
|
||||
}
|
||||
// Fallback to the default routing table
|
||||
tablesParams = &ec2.DescribeRouteTablesInput{
|
||||
Filters: []*ec2.Filter{
|
||||
{
|
||||
Name: aws.String("association.main"),
|
||||
Values: []*string{aws.String("true")},
|
||||
},
|
||||
{
|
||||
Name: aws.String("vpc-id"),
|
||||
Values: []*string{vpcID},
|
||||
},
|
||||
},
|
||||
}
|
||||
tablesResp, err = t.ec2.DescribeRouteTables(tablesParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DescribeRouteTables failed: %s", err)
|
||||
}
|
||||
if len(tablesResp.RouteTables) != 0 {
|
||||
return tablesResp.RouteTables[0].RouteTableId, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("cannot find routetable for %s instance", t.instanceID)
|
||||
}
|
||||
|
||||
func (t *AWSVPCTracker) debugf(fmt string, args ...interface{}) {
|
||||
common.Log.Debugf("[tracker] "+fmt, args...)
|
||||
}
|
||||
|
||||
func (t *AWSVPCTracker) infof(fmt string, args ...interface{}) {
|
||||
common.Log.Infof("[tracker] "+fmt, args...)
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
ipnet.IP = ip
|
||||
|
||||
return ipnet, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package tracker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
var (
|
||||
r0to127 = cidr("10.0.0.0", "10.0.0.127")
|
||||
r128to255 = cidr("10.0.0.128", "10.0.0.255")
|
||||
r0to255 = cidr("10.0.0.0", "10.0.0.255")
|
||||
r1dot0to255 = cidr("10.0.1.0", "10.0.1.255")
|
||||
r2dot0to255 = cidr("10.0.2.0", "10.0.2.255")
|
||||
)
|
||||
|
||||
func TestRemoveCommon(t *testing.T) {
|
||||
a := []address.CIDR{r0to127, r1dot0to255}
|
||||
b := []address.CIDR{r1dot0to255, r2dot0to255}
|
||||
newA, newB := removeCommon(a, b)
|
||||
require.Equal(t, []address.CIDR{r0to127}, newA)
|
||||
require.Equal(t, []address.CIDR{r2dot0to255}, newB)
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
ranges := []address.Range{
|
||||
r0to127.Range(),
|
||||
r128to255.Range(),
|
||||
r2dot0to255.Range(),
|
||||
}
|
||||
require.Equal(t, []address.Range{r0to255.Range(), r2dot0to255.Range()}, merge(ranges))
|
||||
}
|
||||
|
||||
// Helper
|
||||
|
||||
// TODO(mp) DRY with helpers of other tests.
|
||||
|
||||
func ip(s string) address.Address {
|
||||
addr, _ := address.ParseIP(s)
|
||||
return addr
|
||||
}
|
||||
|
||||
// [start; end]
|
||||
func cidr(start, end string) address.CIDR {
|
||||
c := address.Range{Start: ip(start), End: ip(end) + 1}.CIDRs()
|
||||
if len(c) != 1 {
|
||||
panic("invalid cidr")
|
||||
}
|
||||
return c[0]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package tracker
|
||||
|
||||
import (
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
// LocalRangeTracker is an interface for tracking changes in the IPAM ring.
|
||||
type LocalRangeTracker interface {
|
||||
// HandleUpdate is called whenever an address ring gets updated.
|
||||
//
|
||||
// prevRanges corresponds to ranges which were owned by a peer before
|
||||
// a change in the ring, while currRanges to the ones which are currently
|
||||
// owned by the peer.
|
||||
// Both slices have to be sorted in increasing order.
|
||||
// Adjacent ranges within each slice might appear as separate ranges.
|
||||
//
|
||||
// The local parameter indicates whether the ranges belong to the peer
|
||||
// by which the method is called.
|
||||
HandleUpdate(prevRanges, currRanges []address.Range, local bool) error
|
||||
}
|
||||
+77
-24
@@ -5,8 +5,10 @@ import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
@@ -17,7 +19,6 @@ import (
|
||||
const (
|
||||
topDomain = "."
|
||||
reverseDNSdomain = "in-addr.arpa."
|
||||
etcResolvConf = "/etc/resolv.conf"
|
||||
udpBuffSize = uint16(4096)
|
||||
minUDPSize = 512
|
||||
|
||||
@@ -26,16 +27,52 @@ const (
|
||||
DefaultClientTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
type DNSServer struct {
|
||||
ns *Nameserver
|
||||
domain string
|
||||
ttl uint32
|
||||
address string
|
||||
type Upstream interface {
|
||||
Config() (*dns.ClientConfig, error)
|
||||
}
|
||||
|
||||
servers []*dns.Server
|
||||
upstream *dns.ClientConfig
|
||||
tcpClient *dns.Client
|
||||
udpClient *dns.Client
|
||||
func NewUpstream(resolvConf, filterAddress string) Upstream {
|
||||
return &upstream{
|
||||
resolvConf: resolvConf,
|
||||
filterAddress: filterAddress,
|
||||
cachedConfig: &dns.ClientConfig{}}
|
||||
}
|
||||
|
||||
type upstream struct {
|
||||
sync.Mutex
|
||||
|
||||
resolvConf string
|
||||
filterAddress string
|
||||
cachedConfig *dns.ClientConfig
|
||||
lastStat time.Time
|
||||
lastModified time.Time
|
||||
}
|
||||
|
||||
func (u *upstream) Config() (*dns.ClientConfig, error) {
|
||||
u.Lock()
|
||||
defer u.Unlock()
|
||||
|
||||
// If we checked less than five seconds ago return what we already have
|
||||
now := time.Now()
|
||||
if now.Sub(u.lastStat) < 5*time.Second {
|
||||
return u.cachedConfig, nil
|
||||
}
|
||||
u.lastStat = now
|
||||
|
||||
// Reread file if it has changed
|
||||
if fi, err := os.Stat(u.resolvConf); err != nil {
|
||||
return u.cachedConfig, err
|
||||
} else if fi.ModTime() != u.lastModified {
|
||||
if config, err := dns.ClientConfigFromFile(u.resolvConf); err == nil {
|
||||
config.Servers = filter(config.Servers, u.filterAddress)
|
||||
u.lastModified = fi.ModTime()
|
||||
u.cachedConfig = config
|
||||
} else {
|
||||
return u.cachedConfig, err
|
||||
}
|
||||
}
|
||||
|
||||
return u.cachedConfig, nil
|
||||
}
|
||||
|
||||
func filter(ss []string, s string) []string {
|
||||
@@ -49,24 +86,30 @@ func filter(ss []string, s string) []string {
|
||||
return ss
|
||||
}
|
||||
|
||||
func NewDNSServer(ns *Nameserver, domain, address, effectiveAddress string, ttl uint32, clientTimeout time.Duration) (*DNSServer, error) {
|
||||
type DNSServer struct {
|
||||
ns *Nameserver
|
||||
domain string
|
||||
ttl uint32
|
||||
address string
|
||||
|
||||
servers []*dns.Server
|
||||
upstream Upstream
|
||||
tcpClient *dns.Client
|
||||
udpClient *dns.Client
|
||||
}
|
||||
|
||||
func NewDNSServer(ns *Nameserver, domain, address string, upstream Upstream, ttl uint32, clientTimeout time.Duration) (*DNSServer, error) {
|
||||
s := &DNSServer{
|
||||
ns: ns,
|
||||
domain: dns.Fqdn(domain),
|
||||
ttl: ttl,
|
||||
address: address,
|
||||
upstream: upstream,
|
||||
tcpClient: &dns.Client{Net: "tcp", ReadTimeout: clientTimeout},
|
||||
udpClient: &dns.Client{Net: "udp", ReadTimeout: clientTimeout, UDPSize: udpBuffSize},
|
||||
}
|
||||
var err error
|
||||
if s.upstream, err = dns.ClientConfigFromFile(etcResolvConf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.upstream != nil {
|
||||
s.upstream.Servers = filter(s.upstream.Servers, effectiveAddress)
|
||||
}
|
||||
|
||||
err = s.listen(address)
|
||||
err := s.listen(address)
|
||||
return s, err
|
||||
}
|
||||
|
||||
@@ -134,7 +177,7 @@ func (d *DNSServer) createMux(client *dns.Client, defaultMaxResponseSize int) *d
|
||||
|
||||
func (h *handler) handleLocal(w dns.ResponseWriter, req *dns.Msg) {
|
||||
h.ns.debugf("local request: %+v", *req)
|
||||
if len(req.Question) != 1 || req.Question[0].Qtype != dns.TypeA {
|
||||
if len(req.Question) != 1 {
|
||||
h.nameError(w, req)
|
||||
return
|
||||
}
|
||||
@@ -149,6 +192,12 @@ func (h *handler) handleLocal(w dns.ResponseWriter, req *dns.Msg) {
|
||||
h.nameError(w, req)
|
||||
return
|
||||
}
|
||||
// Per RFC4074, if we have an A but another type was requested,
|
||||
// return 'no error' with empty answer section
|
||||
if req.Question[0].Qtype != dns.TypeA {
|
||||
h.respond(w, h.makeResponse(req, nil))
|
||||
return
|
||||
}
|
||||
|
||||
header := dns.RR_Header{
|
||||
Name: req.Question[0].Name,
|
||||
@@ -173,7 +222,7 @@ func (h *handler) handleReverse(w dns.ResponseWriter, req *dns.Msg) {
|
||||
return
|
||||
}
|
||||
|
||||
ipStr := strings.TrimSuffix(req.Question[0].Name, "."+reverseDNSdomain)
|
||||
ipStr := strings.TrimSuffix(strings.ToLower(req.Question[0].Name), "."+reverseDNSdomain)
|
||||
ip, err := address.ParseIP(ipStr)
|
||||
if err != nil {
|
||||
h.nameError(w, req)
|
||||
@@ -204,7 +253,7 @@ func (h *handler) handleRecursive(w dns.ResponseWriter, req *dns.Msg) {
|
||||
h.ns.debugf("recursive request: %+v", *req)
|
||||
|
||||
// Resolve unqualified names locally
|
||||
if len(req.Question) == 1 && req.Question[0].Qtype == dns.TypeA {
|
||||
if len(req.Question) == 1 {
|
||||
hostname := dns.Fqdn(req.Question[0].Name)
|
||||
if strings.Count(hostname, ".") == 1 {
|
||||
h.handleLocal(w, req)
|
||||
@@ -212,10 +261,14 @@ func (h *handler) handleRecursive(w dns.ResponseWriter, req *dns.Msg) {
|
||||
}
|
||||
}
|
||||
|
||||
for _, server := range h.upstream.Servers {
|
||||
upstreamConfig, err := h.upstream.Config()
|
||||
if err != nil {
|
||||
h.ns.errorf("unable to read upstream config: %s", err)
|
||||
}
|
||||
for _, server := range upstreamConfig.Servers {
|
||||
reqCopy := req.Copy()
|
||||
reqCopy.Id = dns.Id()
|
||||
response, _, err := h.client.Exchange(reqCopy, fmt.Sprintf("%s:%s", server, h.upstream.Port))
|
||||
response, _, err := h.client.Exchange(reqCopy, fmt.Sprintf("%s:%s", server, upstreamConfig.Port))
|
||||
if (err != nil && err != dns.ErrTruncated) || response == nil {
|
||||
h.ns.debugf("error trying %s: %v", server, err)
|
||||
continue
|
||||
|
||||
@@ -15,17 +15,22 @@ import (
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
type mockUpstream struct {
|
||||
config *dns.ClientConfig
|
||||
}
|
||||
|
||||
func (mu *mockUpstream) Config() (*dns.ClientConfig, error) {
|
||||
return mu.config, nil
|
||||
}
|
||||
|
||||
func startServer(t *testing.T, upstream *dns.ClientConfig) (*DNSServer, *Nameserver, int, int) {
|
||||
peername, err := mesh.PeerNameFromString("00:00:00:02:00:00")
|
||||
require.Nil(t, err)
|
||||
nameserver := New(peername, "", func(mesh.PeerName) bool { return true })
|
||||
dnsserver, err := NewDNSServer(nameserver, "weave.local.", "0.0.0.0:0", "", 30, 5*time.Second)
|
||||
dnsserver, err := NewDNSServer(nameserver, "weave.local.", "0.0.0.0:0", &mockUpstream{upstream}, 30, 5*time.Second)
|
||||
require.Nil(t, err)
|
||||
udpPort := dnsserver.servers[0].PacketConn.LocalAddr().(*net.UDPAddr).Port
|
||||
tcpPort := dnsserver.servers[1].Listener.Addr().(*net.TCPAddr).Port
|
||||
if upstream != nil {
|
||||
dnsserver.upstream = upstream
|
||||
}
|
||||
go dnsserver.ActivateAndServe()
|
||||
return dnsserver, nameserver, udpPort, tcpPort
|
||||
}
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ func (e1 *Entry) tombstone() bool {
|
||||
|
||||
func check(es SortableEntries) error {
|
||||
if !sort.IsSorted(es) {
|
||||
return fmt.Errorf("Not sorted!")
|
||||
return fmt.Errorf("Not sorted")
|
||||
}
|
||||
for i := 1; i < es.Len(); i++ {
|
||||
if es.Get(i).equal(es.Get(i - 1)) {
|
||||
|
||||
@@ -199,7 +199,7 @@ func (n *Nameserver) receiveGossip(msg []byte) (mesh.GossipData, mesh.GossipData
|
||||
return nil, nil, err
|
||||
}
|
||||
if delta := gossip.Timestamp - now(); delta > gossipWindow || delta < -gossipWindow {
|
||||
return nil, nil, fmt.Errorf("clock skew of %d detected", delta)
|
||||
return nil, nil, fmt.Errorf("host clock skew of %ds exceeds %ds limit", delta, gossipWindow)
|
||||
}
|
||||
|
||||
// Filter to remove entries from unknown peers, done before we take
|
||||
|
||||
@@ -36,9 +36,10 @@ func NewStatus(ns *Nameserver, dnsServer *DNSServer) *Status {
|
||||
entry.Tombstone})
|
||||
}
|
||||
|
||||
upstreamConfig, _ := dnsServer.upstream.Config()
|
||||
return &Status{
|
||||
dnsServer.domain,
|
||||
dnsServer.upstream.Servers,
|
||||
upstreamConfig.Servers,
|
||||
dnsServer.address,
|
||||
dnsServer.ttl,
|
||||
entryStatusSlice}
|
||||
|
||||
+80
-1
@@ -35,6 +35,35 @@ func (r Range) AsCIDRString() string {
|
||||
return CIDR{Addr: r.Start, PrefixLen: prefixLen}.String()
|
||||
}
|
||||
|
||||
// return the highest bit set in v
|
||||
// algorithm from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
func msb(v Count) Count {
|
||||
v |= v >> 1
|
||||
v |= v >> 2
|
||||
v |= v >> 4
|
||||
v |= v >> 8
|
||||
v |= v >> 16
|
||||
// Cast to 64-bit to avoid overflow when original v >= 0x80000000
|
||||
return Count((uint64(v) + 1) / 2)
|
||||
}
|
||||
|
||||
func (r Range) BiggestCIDRRange() Range {
|
||||
sizeMsb := Offset(msb(r.Size()))
|
||||
maskedSize := Offset(r.Size()) & (sizeMsb - 1)
|
||||
maskedStart := Offset(r.Start) & (sizeMsb - 1)
|
||||
if maskedStart == 0 {
|
||||
return NewRange(r.Start, sizeMsb)
|
||||
}
|
||||
if sizeMsb-maskedStart <= maskedSize {
|
||||
return NewRange(Add(r.Start, sizeMsb-maskedStart), sizeMsb)
|
||||
}
|
||||
size := sizeMsb / 2
|
||||
if size >= maskedStart {
|
||||
return NewRange(Add(r.Start, size-maskedStart), size)
|
||||
}
|
||||
return NewRange(Add(r.Start, sizeMsb-maskedStart), size)
|
||||
}
|
||||
|
||||
func MakeCIDR(subnet CIDR, addr Address) CIDR {
|
||||
return CIDR{Addr: addr, PrefixLen: subnet.PrefixLen}
|
||||
}
|
||||
@@ -44,6 +73,40 @@ type CIDR struct {
|
||||
PrefixLen int
|
||||
}
|
||||
|
||||
// CIDRs returns a list of CIDR-aligned ranges which cover this range.
|
||||
func (r Range) CIDRs() []CIDR {
|
||||
const (
|
||||
fullMask = ^Address(0)
|
||||
cidrMaxPrefixLen = 32
|
||||
)
|
||||
var cidrs []CIDR
|
||||
|
||||
for start, end := r.Start, r.End-1; end >= start; {
|
||||
mask, prefixLen := fullMask, cidrMaxPrefixLen
|
||||
// Find the smallest mask which would cover some part of [start;end].
|
||||
// Once we found such, apply it by OR'ing
|
||||
for mask > 0 {
|
||||
tmpMask := mask << 1
|
||||
// Check whether mask neither too short nor too long
|
||||
if (start&tmpMask) != start || (start|^tmpMask) > end {
|
||||
break
|
||||
}
|
||||
mask = tmpMask
|
||||
prefixLen--
|
||||
}
|
||||
cidrs = append(cidrs, CIDR{start, prefixLen})
|
||||
// Apply mask
|
||||
start |= ^mask
|
||||
// Check for overflow
|
||||
if start+1 < start {
|
||||
break
|
||||
}
|
||||
start++
|
||||
}
|
||||
|
||||
return cidrs
|
||||
}
|
||||
|
||||
func ParseIP(s string) (Address, error) {
|
||||
if ip := net.ParseIP(s); ip != nil {
|
||||
return FromIP4(ip), nil
|
||||
@@ -62,6 +125,22 @@ func ParseCIDR(s string) (CIDR, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func NewCIDRs(ranges []Range) (cidrs []CIDR) {
|
||||
for _, r := range ranges {
|
||||
cidrs = append(cidrs, r.CIDRs()...)
|
||||
}
|
||||
return cidrs
|
||||
}
|
||||
|
||||
func (cidr CIDR) Start() Address {
|
||||
return cidr.Addr
|
||||
}
|
||||
|
||||
// cidr = [Start; End)
|
||||
func (cidr CIDR) End() Address {
|
||||
return cidr.Range().End
|
||||
}
|
||||
|
||||
func (cidr CIDR) IsSubnet() bool {
|
||||
mask := cidr.Size() - 1
|
||||
return Offset(cidr.Addr)&mask == 0
|
||||
@@ -122,7 +201,7 @@ func Length(a, b Address) Count {
|
||||
return Count(a - b)
|
||||
}
|
||||
|
||||
func Min(a, b Offset) Offset {
|
||||
func Min(a, b Count) Count {
|
||||
if a > b {
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package address
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func isPower2(x Count) bool {
|
||||
if x == 0 {
|
||||
return false
|
||||
}
|
||||
for ; x > 1; x /= 2 {
|
||||
if x&1 != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestBiggestPow2AlignedRange(t *testing.T) {
|
||||
require.Equal(t, NewRange(0, 1), NewRange(0, 1).BiggestCIDRRange())
|
||||
require.Equal(t, NewRange(1, 1), NewRange(1, 2).BiggestCIDRRange())
|
||||
require.Equal(t, NewRange(2, 2), NewRange(1, 3).BiggestCIDRRange())
|
||||
require.Equal(t, NewRange(0, 0x40000000), NewRange(0, 0x7fffffff).BiggestCIDRRange())
|
||||
require.Equal(t, NewRange(0xfffffffe, 1), NewRange(0xfffffffe, 1).BiggestCIDRRange())
|
||||
prop := func(start Address, size Offset) bool {
|
||||
if size > Offset(0xffffffff)-Offset(start) { // out of range
|
||||
return true
|
||||
}
|
||||
r := NewRange(start, size)
|
||||
result := r.BiggestCIDRRange()
|
||||
return r.Contains(result.Start) &&
|
||||
r.Contains(result.End-1) &&
|
||||
isPower2(result.Size()) &&
|
||||
result.Size() > r.Size()/4 &&
|
||||
Count(result.Start)%result.Size() == 0
|
||||
}
|
||||
require.NoError(t, quick.Check(prop, &quick.Config{MaxCount: 1000000}))
|
||||
}
|
||||
|
||||
func ip(s string) Address {
|
||||
addr, _ := ParseIP(s)
|
||||
return addr
|
||||
}
|
||||
|
||||
func cidr(s string) CIDR {
|
||||
c, err := ParseCIDR(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func TestCIDRs(t *testing.T) {
|
||||
start := ip("10.0.0.1")
|
||||
end := ip("10.0.0.9")
|
||||
r := NewRange(start, Subtract(end, start))
|
||||
require.Equal(t,
|
||||
[]CIDR{cidr("10.0.0.1/32"), cidr("10.0.0.2/31"), cidr("10.0.0.4/30"), cidr("10.0.0.8/32")},
|
||||
r.CIDRs())
|
||||
}
|
||||
|
||||
func TestCIDRStartAndEnd(t *testing.T) {
|
||||
cidr, _ := ParseCIDR("10.0.0.0/24")
|
||||
require.Equal(t, ip("10.0.0.0"), cidr.Start(), "")
|
||||
require.Equal(t, ip("10.0.1.0"), cidr.End(), "")
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package net
|
||||
|
||||
import "fmt"
|
||||
import "io"
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
type BridgeType int
|
||||
|
||||
const (
|
||||
WeaveBridgeName = "weave"
|
||||
DatapathName = "datapath"
|
||||
|
||||
None BridgeType = iota
|
||||
Bridge
|
||||
Fastdp
|
||||
BridgedFastdp
|
||||
Inconsistent
|
||||
)
|
||||
|
||||
func DetectBridgeType(weaveBridgeName, datapathName string) BridgeType {
|
||||
bridge, _ := netlink.LinkByName(weaveBridgeName)
|
||||
datapath, _ := netlink.LinkByName(datapathName)
|
||||
|
||||
switch {
|
||||
case bridge == nil && datapath == nil:
|
||||
return None
|
||||
case isBridge(bridge) && datapath == nil:
|
||||
return Bridge
|
||||
case isDatapath(bridge) && datapath == nil:
|
||||
return Fastdp
|
||||
case isDatapath(datapath) && isBridge(bridge):
|
||||
return BridgedFastdp
|
||||
default:
|
||||
return Inconsistent
|
||||
}
|
||||
}
|
||||
|
||||
func isBridge(link netlink.Link) bool {
|
||||
_, isBridge := link.(*netlink.Bridge)
|
||||
return isBridge
|
||||
}
|
||||
|
||||
func isDatapath(link netlink.Link) bool {
|
||||
switch link.(type) {
|
||||
case *netlink.GenericLink:
|
||||
return link.Type() == "openvswitch"
|
||||
case *netlink.Device:
|
||||
// Assume it's our openvswitch device, and the kernel has not been updated to report the kind.
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func DetectHairpin(portIfName string, log *logrus.Logger) error {
|
||||
link, err := netlink.LinkByName(portIfName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to find link %q: %s", portIfName, err)
|
||||
}
|
||||
|
||||
ch := make(chan netlink.LinkUpdate)
|
||||
// See EnsureInterface for why done channel is not passed
|
||||
if err := netlink.LinkSubscribe(ch, nil); err != nil {
|
||||
return fmt.Errorf("Unable to subscribe to netlink updates: %s", err)
|
||||
}
|
||||
|
||||
pi, err := netlink.LinkGetProtinfo(link)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to get link protinfo %q: %s", portIfName, err)
|
||||
}
|
||||
if pi.Hairpin {
|
||||
return fmt.Errorf("Hairpin mode enabled on %q", portIfName)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for up := range ch {
|
||||
if up.Attrs().Name == portIfName && up.Attrs().Protinfo != nil &&
|
||||
up.Attrs().Protinfo.Hairpin {
|
||||
log.Errorf("Hairpin mode enabled on %q", portIfName)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrBridgeNoIP = fmt.Errorf("Bridge has no IP address")
|
||||
|
||||
func FindBridgeIP(bridgeName string, subnet *net.IPNet) (net.IP, error) {
|
||||
netdev, err := GetBridgeNetDev(bridgeName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to get netdev for %q bridge: %s", bridgeName, err)
|
||||
}
|
||||
if len(netdev.CIDRs) == 0 {
|
||||
return nil, ErrBridgeNoIP
|
||||
}
|
||||
if subnet != nil {
|
||||
for _, cidr := range netdev.CIDRs {
|
||||
if subnet.Contains(cidr.IP) {
|
||||
return cidr.IP, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// No subnet, or none in the required subnet; just return the first one
|
||||
return netdev.CIDRs[0].IP, nil
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package net
|
||||
|
||||
import "fmt"
|
||||
import "syscall"
|
||||
import "unsafe"
|
||||
|
||||
const (
|
||||
SIOCETHTOOL = 0x8946 // linux/sockios.h
|
||||
ETHTOOL_GTXCSUM = 0x00000016 // linux/ethtool.h
|
||||
ETHTOOL_STXCSUM = 0x00000017 // linux/ethtool.h
|
||||
IFNAMSIZ = 16 // linux/if.h
|
||||
)
|
||||
|
||||
// linux/if.h 'struct ifreq'
|
||||
type IFReqData struct {
|
||||
Name [IFNAMSIZ]byte
|
||||
Data uintptr
|
||||
}
|
||||
|
||||
// linux/ethtool.h 'struct ethtool_value'
|
||||
type EthtoolValue struct {
|
||||
Cmd uint32
|
||||
Data uint32
|
||||
}
|
||||
|
||||
func ioctlEthtool(fd int, argp uintptr) error {
|
||||
_, _, errno := syscall.RawSyscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(SIOCETHTOOL), argp)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disable TX checksum offload on specified interface
|
||||
func EthtoolTXOff(name string) error {
|
||||
if len(name)+1 > IFNAMSIZ {
|
||||
return fmt.Errorf("name too long")
|
||||
}
|
||||
|
||||
socket, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(socket)
|
||||
|
||||
// Request current value
|
||||
value := EthtoolValue{Cmd: ETHTOOL_GTXCSUM}
|
||||
request := IFReqData{Data: uintptr(unsafe.Pointer(&value))}
|
||||
copy(request.Name[:], name)
|
||||
|
||||
if err := ioctlEthtool(socket, uintptr(unsafe.Pointer(&request))); err != nil {
|
||||
return err
|
||||
}
|
||||
if value.Data == 0 { // if already off, don't try to change
|
||||
return nil
|
||||
}
|
||||
|
||||
value = EthtoolValue{ETHTOOL_STXCSUM, 0}
|
||||
return ioctlEthtool(socket, uintptr(unsafe.Pointer(&request)))
|
||||
}
|
||||
@@ -0,0 +1,562 @@
|
||||
// package IPsec provides primitives for establishing IPsec in the fastdp mode.
|
||||
package ipsec
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
)
|
||||
|
||||
const (
|
||||
keySize = 36 // AES-GCM key 32 bytes + 4 bytes salt
|
||||
nonceSize = 32 // HKDF nonce size
|
||||
|
||||
mark = uint32(0x1) << 17 // iptables marks
|
||||
markStr = "0x20000/0x20000" // update if the above mark changes
|
||||
|
||||
tableMangle = "mangle"
|
||||
tableFilter = "filter"
|
||||
chainIn = "WEAVE-IPSEC-IN"
|
||||
chainInMark = "WEAVE-IPSEC-IN-MARK"
|
||||
chainOut = "WEAVE-IPSEC-OUT"
|
||||
chainOutMark = "WEAVE-IPSEC-OUT-MARK"
|
||||
)
|
||||
|
||||
type SPI uint32
|
||||
|
||||
// Used to identify:
|
||||
// - directional SPIs,
|
||||
// - ipsec establishments.
|
||||
type spiID [24]byte
|
||||
|
||||
func getSPIId(srcPeer, dstPeer mesh.PeerName, connUID uint64) (id spiID) {
|
||||
binary.BigEndian.PutUint64(id[:], uint64(srcPeer))
|
||||
binary.BigEndian.PutUint64(id[8:], uint64(dstPeer))
|
||||
binary.BigEndian.PutUint64(id[16:], connUID)
|
||||
return
|
||||
}
|
||||
|
||||
type spiInfo struct {
|
||||
spi SPI
|
||||
isDirOut bool
|
||||
}
|
||||
|
||||
// IPSec
|
||||
|
||||
type IPSec struct {
|
||||
sync.RWMutex
|
||||
ipt *iptables.IPTables
|
||||
log *logrus.Logger
|
||||
|
||||
spiInfo map[spiID]spiInfo
|
||||
// A reference to spiInfo; spiInfo might be of an expired SPI.
|
||||
spis map[SPI]*spiInfo
|
||||
}
|
||||
|
||||
func New(log *logrus.Logger) (*IPSec, error) {
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "iptables new")
|
||||
}
|
||||
|
||||
ipsec := &IPSec{
|
||||
ipt: ipt,
|
||||
log: log,
|
||||
spiInfo: make(map[spiID]spiInfo),
|
||||
spis: make(map[SPI]*spiInfo),
|
||||
}
|
||||
|
||||
return ipsec, nil
|
||||
}
|
||||
|
||||
// InitSALocal initializes inbound ipsec from remotePeer and triggers
|
||||
// the initialization on remotePeer.
|
||||
func (ipsec *IPSec) InitSALocal(localPeer, remotePeer mesh.PeerName, connUID uint64, localIP, remoteIP net.IP, udpPort int, sessionKey *[32]byte, initRemote func([]byte) error) error {
|
||||
// ID of inbound SPI
|
||||
spiID := getSPIId(remotePeer, localPeer, connUID)
|
||||
|
||||
ipsec.Lock()
|
||||
defer ipsec.Unlock()
|
||||
|
||||
// Derive SA key
|
||||
nonce, err := genNonce()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "generate nonce")
|
||||
}
|
||||
key, err := deriveKey(sessionKey[:], nonce, localPeer)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "derive key")
|
||||
}
|
||||
|
||||
// Allocate SA
|
||||
sa, err := netlink.XfrmStateAllocSpi(xfrmAllocSpiState(remoteIP, localIP))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("ip xfrm state allocspi (in, %s, %s)", remoteIP, localIP))
|
||||
}
|
||||
|
||||
// Use SPI generated by the kernel. The kernel ensures {dstIP, spi} to
|
||||
// be unique.
|
||||
spi := SPI(sa.Spi)
|
||||
|
||||
ipsec.log.Infof("ipsec: InitSALocal: %s -> %s :%d 0x%x", remoteIP, localIP, udpPort, spi)
|
||||
|
||||
// Create SA
|
||||
if sa, err := xfrmState(remoteIP, localIP, spi, false, key); err == nil {
|
||||
if err := netlink.XfrmStateUpdate(sa); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("xfrm state update (in, %s, %s, 0x%x)", sa.Src, sa.Dst, sa.Spi))
|
||||
}
|
||||
} else {
|
||||
return errors.Wrap(err, "new xfrm state (in)")
|
||||
}
|
||||
|
||||
// Install iptables rules
|
||||
if err := ipsec.installDropNonEncrypted(localIP, remoteIP, udpPort, spi); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("install protecting rules (%s, %s, %d, 0x%x)", localIP, remoteIP, udpPort, spi))
|
||||
}
|
||||
|
||||
si := spiInfo{spi: spi, isDirOut: false}
|
||||
ipsec.spiInfo[spiID] = si
|
||||
ipsec.spis[spi] = &si
|
||||
|
||||
// Trigger the initialization on the remote peer
|
||||
msg := &msgInitSARemote{nonce, spi}
|
||||
if err := initRemote(msg.serialize()); err != nil {
|
||||
return errors.Wrap(err, "send InitSARemote")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitSARemote initializes outbound ipsec to remotePeer.
|
||||
// Triggered by remotePeer.
|
||||
func (ipsec *IPSec) InitSARemote(msgInitSARemote []byte, localPeer, remotePeer mesh.PeerName, connUID uint64, localIP, remoteIP net.IP, udpPort int, sessionKey *[32]byte) error {
|
||||
// ID of outbound SPI
|
||||
spiID := getSPIId(localPeer, remotePeer, connUID)
|
||||
|
||||
msg, err := deserializeMsgInitSARemote(msgInitSARemote)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "deserialize InitSARemote")
|
||||
}
|
||||
spi := msg.spi
|
||||
|
||||
ipsec.Lock()
|
||||
defer ipsec.Unlock()
|
||||
|
||||
ipsec.log.Infof("ipsec: InitSARemote: %s -> %s :%d 0x%x", localIP, remoteIP, udpPort, spi)
|
||||
|
||||
// Derive SA key by using the received nonce
|
||||
key, err := deriveKey(sessionKey[:], msg.nonce, remotePeer)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "derive key")
|
||||
}
|
||||
|
||||
// Create SA
|
||||
if sa, err := xfrmState(localIP, remoteIP, spi, true, key); err == nil {
|
||||
if err := netlink.XfrmStateAdd(sa); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("xfrm state update (out, %s, %s, 0x%x)", sa.Src, sa.Dst, sa.Spi))
|
||||
}
|
||||
} else {
|
||||
return errors.Wrap(err, "new xfrm state (out)")
|
||||
}
|
||||
|
||||
// Create or update SP
|
||||
sp := xfrmPolicy(localIP, remoteIP, spi)
|
||||
if err := netlink.XfrmPolicyUpdate(sp); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("xfrm policy update (%s, %s, 0x%x)", localIP, remoteIP, spi))
|
||||
}
|
||||
|
||||
si := spiInfo{spi: spi, isDirOut: true}
|
||||
ipsec.spiInfo[spiID] = si
|
||||
ipsec.spis[spi] = &si
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy destroys any (inbound / outbound) ipsec establishment between the peers.
|
||||
func (ipsec *IPSec) Destroy(localPeer, remotePeer mesh.PeerName, connUID uint64, localIP, remoteIP net.IP, udpPort int) error {
|
||||
outSPIID := getSPIId(localPeer, remotePeer, connUID)
|
||||
inSPIID := getSPIId(remotePeer, localPeer, connUID)
|
||||
|
||||
ipsec.Lock()
|
||||
defer ipsec.Unlock()
|
||||
|
||||
// Destroy inbound
|
||||
|
||||
if inSPIInfo, ok := ipsec.spiInfo[inSPIID]; ok {
|
||||
ipsec.log.Infof("ipsec: destroy: in %s -> %s 0x%x", remoteIP, localIP, inSPIInfo.spi)
|
||||
|
||||
inSPI := inSPIInfo.spi
|
||||
|
||||
inSA := &netlink.XfrmState{
|
||||
Src: remoteIP,
|
||||
Dst: localIP,
|
||||
Proto: netlink.XFRM_PROTO_ESP,
|
||||
Spi: int(inSPI),
|
||||
}
|
||||
if err := netlink.XfrmStateDel(inSA); err != nil {
|
||||
ipsec.log.Warnf("ipsec: xfrm state del (in, %s, %s, 0x%x) failed: %s", inSA.Src, inSA.Dst, inSA.Spi, err)
|
||||
}
|
||||
|
||||
if err := ipsec.removeDropNonEncrypted(localIP, remoteIP, udpPort, inSPI); err != nil {
|
||||
ipsec.log.Warnf("ipsec: remove protecting rules (%s, %s, %d, 0x%x) failed: %s", localIP, remoteIP, udpPort, inSPI, err)
|
||||
}
|
||||
|
||||
delete(ipsec.spiInfo, inSPIID)
|
||||
delete(ipsec.spis, inSPI)
|
||||
}
|
||||
|
||||
// Destroy outbound
|
||||
|
||||
if outSPIInfo, ok := ipsec.spiInfo[outSPIID]; ok {
|
||||
ipsec.log.Infof("ipsec: destroy: out %s -> %s 0x%x", localIP, remoteIP, outSPIInfo.spi)
|
||||
|
||||
if err := netlink.XfrmPolicyDel(xfrmPolicy(localIP, remoteIP, outSPIInfo.spi)); err != nil {
|
||||
ipsec.log.Warnf("ipsec: xfrm policy del (%s, %s, 0x%x) failed: %s", localIP, remoteIP, outSPIInfo.spi, err)
|
||||
}
|
||||
|
||||
outSA := &netlink.XfrmState{
|
||||
Src: localIP,
|
||||
Dst: remoteIP,
|
||||
Proto: netlink.XFRM_PROTO_ESP,
|
||||
Spi: int(outSPIInfo.spi),
|
||||
}
|
||||
if err := netlink.XfrmStateDel(outSA); err != nil {
|
||||
ipsec.log.Warnf("ipsec: xfrm state del (out, %s, %s, 0x%x) failed: %s", outSA.Src, outSA.Dst, outSA.Spi, err)
|
||||
}
|
||||
|
||||
delete(ipsec.spiInfo, outSPIID)
|
||||
delete(ipsec.spis, outSPIInfo.spi)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush removes all policies/SAs established by us. Also, it removes chains and
|
||||
// rules of iptables.
|
||||
//
|
||||
// If destroy is true, the chains and the rules won't be re-created.
|
||||
func (ipsec *IPSec) Flush(destroy bool) error {
|
||||
ipsec.Lock()
|
||||
defer ipsec.Unlock()
|
||||
|
||||
policies, err := netlink.XfrmPolicyList(syscall.AF_INET)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "xfrm policy list")
|
||||
}
|
||||
for _, p := range policies {
|
||||
if p.Mark != nil && p.Mark.Value == mark && len(p.Tmpls) != 0 {
|
||||
spi := SPI(p.Tmpls[0].Spi)
|
||||
if err := netlink.XfrmPolicyDel(&p); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("xfrm policy del (%s, %s, 0x%x)", p.Src, p.Dst, spi))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states, err := netlink.XfrmStateList(syscall.AF_INET)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "xfrm state list")
|
||||
}
|
||||
for _, s := range states {
|
||||
if _, ok := ipsec.spis[SPI(s.Spi)]; ok {
|
||||
if err := netlink.XfrmStateDel(&s); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("xfrm state list (%s, %s, 0x%x)", s.Src, s.Dst, s.Spi))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := ipsec.resetIPTables(destroy); err != nil {
|
||||
return errors.Wrap(err, "reset ip tables")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// iptables
|
||||
|
||||
type chain struct {
|
||||
table string
|
||||
chain string
|
||||
}
|
||||
type rule struct {
|
||||
table string
|
||||
chain string
|
||||
rulespec []string
|
||||
unique bool
|
||||
}
|
||||
|
||||
func (ipsec *IPSec) clearChains(chains []chain) error {
|
||||
for _, c := range chains {
|
||||
if err := ipsec.ipt.ClearChain(c.table, c.chain); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("iptables clear chain (%s, %s)", c.table, c.chain))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ipsec *IPSec) deleteChains(chains []chain) error {
|
||||
for _, c := range chains {
|
||||
if err := ipsec.ipt.DeleteChain(c.table, c.chain); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("iptables delete chain (%s, %s)", c.table, c.chain))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ipsec *IPSec) resetRules(rules []rule, destroy bool) error {
|
||||
for _, r := range rules {
|
||||
ok, err := ipsec.ipt.Exists(r.table, r.chain, r.rulespec...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("iptables exists rule (%s, %s, %s)", r.table, r.chain, r.rulespec))
|
||||
}
|
||||
switch {
|
||||
case !destroy && !ok:
|
||||
if err := ipsec.ipt.Append(r.table, r.chain, r.rulespec...); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("iptables append rule (%s, %s, %s)", r.table, r.chain, r.rulespec))
|
||||
}
|
||||
case destroy && ok:
|
||||
if err := ipsec.ipt.Delete(r.table, r.chain, r.rulespec...); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("iptables delete rule (%s, %s, %s)", r.table, r.chain, r.rulespec))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ipsec *IPSec) resetIPTables(destroy bool) error {
|
||||
chains := []chain{
|
||||
{tableMangle, chainIn},
|
||||
{tableMangle, chainInMark},
|
||||
{tableFilter, chainIn},
|
||||
{tableMangle, chainOut},
|
||||
{tableMangle, chainOutMark},
|
||||
}
|
||||
rules := []rule{
|
||||
{tableMangle, "INPUT", []string{"-j", chainIn}, true},
|
||||
{tableMangle, chainInMark, []string{"-j", "MARK", "--set-xmark", markStr}, true},
|
||||
{tableFilter, "INPUT", []string{"-j", chainIn}, true},
|
||||
{tableMangle, "OUTPUT", []string{"-j", chainOut}, true},
|
||||
{tableMangle, chainOutMark, []string{"-j", "MARK", "--set-xmark", markStr}, true},
|
||||
{tableFilter, "OUTPUT",
|
||||
[]string{
|
||||
"!", "-p", "esp",
|
||||
"-m", "policy", "--dir", "out", "--pol", "none",
|
||||
"-m", "mark", "--mark", markStr,
|
||||
"-j", "DROP"}, true},
|
||||
}
|
||||
|
||||
if err := ipsec.clearChains(chains); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ipsec.resetRules(rules, destroy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if destroy {
|
||||
if err := ipsec.deleteChains(chains); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ruleMarkInboundESP(srcIP, dstIP net.IP, inSPI SPI) rule {
|
||||
return rule{tableMangle, chainIn,
|
||||
[]string{
|
||||
"-s", dstIP.String(), "-d", srcIP.String(),
|
||||
"-p", "esp",
|
||||
"-m", "esp", "--espspi", "0x" + strconv.FormatUint(uint64(inSPI), 16),
|
||||
"-j", chainInMark,
|
||||
}, true}
|
||||
}
|
||||
|
||||
func rulesDropNonEncrypted(srcIP, dstIP net.IP, udpPort int, inSPI SPI) []rule {
|
||||
udpPortStr := strconv.FormatUint(uint64(udpPort), 10)
|
||||
return []rule{
|
||||
ruleMarkInboundESP(srcIP, dstIP, inSPI),
|
||||
{tableFilter, chainIn,
|
||||
[]string{
|
||||
"-s", dstIP.String(), "-d", srcIP.String(),
|
||||
"-p", "udp", "--dport", udpPortStr,
|
||||
"-m", "mark", "!", "--mark", markStr,
|
||||
"-j", "DROP",
|
||||
}, false},
|
||||
{tableMangle, chainOut,
|
||||
[]string{
|
||||
"-s", srcIP.String(), "-d", dstIP.String(),
|
||||
"-p", "udp", "--dport", udpPortStr,
|
||||
"-j", chainOutMark,
|
||||
}, false},
|
||||
}
|
||||
}
|
||||
|
||||
func (ipsec *IPSec) installDropNonEncrypted(srcIP, dstIP net.IP, udpPort int, inSPI SPI) error {
|
||||
rules := rulesDropNonEncrypted(srcIP, dstIP, udpPort, inSPI)
|
||||
for _, r := range rules {
|
||||
appendFunc := ipsec.ipt.Append
|
||||
if r.unique {
|
||||
appendFunc = ipsec.ipt.AppendUnique
|
||||
}
|
||||
if err := appendFunc(r.table, r.chain, r.rulespec...); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("iptables append (%s, %s, %s)", r.table, r.chain, r.rulespec))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ipsec *IPSec) removeDropNonEncrypted(srcIP, dstIP net.IP, udpPort int, inSPI SPI) error {
|
||||
rules := rulesDropNonEncrypted(srcIP, dstIP, udpPort, inSPI)
|
||||
if err := ipsec.resetRules(rules, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ipsec *IPSec) removeDropNonEncryptedInbound(srcIP, dstIP net.IP, inSPI SPI) error {
|
||||
r := ruleMarkInboundESP(srcIP, dstIP, inSPI)
|
||||
if err := ipsec.ipt.Delete(r.table, r.chain, r.rulespec...); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("iptables delete unique (%s, %s, %s)", r.table, r.chain, r.rulespec))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// xfrm
|
||||
|
||||
func xfrmAllocSpiState(srcIP, dstIP net.IP) *netlink.XfrmState {
|
||||
return &netlink.XfrmState{
|
||||
Src: srcIP,
|
||||
Dst: dstIP,
|
||||
Proto: netlink.XFRM_PROTO_ESP,
|
||||
Mode: netlink.XFRM_MODE_TRANSPORT,
|
||||
ReplayWindow: 256,
|
||||
ESN: true,
|
||||
}
|
||||
}
|
||||
|
||||
func xfrmState(srcIP, dstIP net.IP, spi SPI, isDirOut bool, key []byte) (*netlink.XfrmState, error) {
|
||||
if len(key) != keySize {
|
||||
return nil, fmt.Errorf("key should be %d bytes long", keySize)
|
||||
}
|
||||
|
||||
state := xfrmAllocSpiState(srcIP, dstIP)
|
||||
|
||||
state.Spi = int(spi)
|
||||
state.Aead = &netlink.XfrmStateAlgo{
|
||||
Name: "rfc4106(gcm(aes))",
|
||||
Key: key,
|
||||
ICVLen: 128,
|
||||
}
|
||||
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func xfrmPolicy(srcIP, dstIP net.IP, spi SPI) *netlink.XfrmPolicy {
|
||||
ipMask := []byte{0xff, 0xff, 0xff, 0xff} // /32
|
||||
|
||||
return &netlink.XfrmPolicy{
|
||||
Src: &net.IPNet{IP: srcIP, Mask: ipMask},
|
||||
Dst: &net.IPNet{IP: dstIP, Mask: ipMask},
|
||||
Proto: syscall.IPPROTO_UDP,
|
||||
Dir: netlink.XFRM_DIR_OUT,
|
||||
Mark: &netlink.XfrmMark{
|
||||
Value: mark,
|
||||
Mask: mark,
|
||||
},
|
||||
Tmpls: []netlink.XfrmPolicyTmpl{
|
||||
{
|
||||
Src: srcIP,
|
||||
Dst: dstIP,
|
||||
Proto: netlink.XFRM_PROTO_ESP,
|
||||
Mode: netlink.XFRM_MODE_TRANSPORT,
|
||||
Spi: int(spi),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Key derivation
|
||||
|
||||
func genNonce() ([]byte, error) {
|
||||
buf := make([]byte, nonceSize)
|
||||
n, err := rand.Read(buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("crypto rand failed: %s", err)
|
||||
}
|
||||
if n != nonceSize {
|
||||
return nil, fmt.Errorf("not enough of random data: %d", n)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func deriveKey(sessionKey []byte, nonce []byte, peerName mesh.PeerName) ([]byte, error) {
|
||||
key := make([]byte, keySize)
|
||||
|
||||
info := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(info, uint64(peerName))
|
||||
|
||||
hkdf := hkdf.New(sha256.New, sessionKey, nonce, info)
|
||||
|
||||
n, err := io.ReadFull(hkdf, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n != keySize {
|
||||
return nil, fmt.Errorf("derived too short key: %d", n)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// Protocol Messages
|
||||
|
||||
type msgInitSARemote struct {
|
||||
nonce []byte
|
||||
spi SPI
|
||||
}
|
||||
|
||||
func deserializeMsgInitSARemote(b []byte) (*msgInitSARemote, error) {
|
||||
if len(b) == 0 {
|
||||
return nil, fmt.Errorf("empty msg")
|
||||
}
|
||||
|
||||
msg := &msgInitSARemote{}
|
||||
if len(b) != msg.size() {
|
||||
return nil, fmt.Errorf("invalid payload size: %d", len(b))
|
||||
}
|
||||
|
||||
msg.nonce = make([]byte, nonceSize)
|
||||
copy(msg.nonce, b[:nonceSize])
|
||||
b = b[nonceSize:]
|
||||
|
||||
msg.spi = SPI(binary.BigEndian.Uint32(b))
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (msg *msgInitSARemote) size() int {
|
||||
return nonceSize + 32 // SPI
|
||||
}
|
||||
|
||||
func (msg *msgInitSARemote) serialize() []byte {
|
||||
b := make([]byte, msg.size())
|
||||
|
||||
copy(b[:nonceSize], msg.nonce)
|
||||
binary.BigEndian.PutUint32(b[nonceSize:], uint32(msg.spi))
|
||||
|
||||
return b
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"net"
|
||||
)
|
||||
|
||||
func RandomMAC() (net.HardwareAddr, error) {
|
||||
mac := make([]byte, 6)
|
||||
if _, err := rand.Read(mac); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
setUnicastAndLocal(mac)
|
||||
|
||||
return net.HardwareAddr(mac), nil
|
||||
}
|
||||
|
||||
func MACfromUUID(uuid []byte) net.HardwareAddr {
|
||||
hash := sha256.New()
|
||||
// We salt the input just as a precaution to avoid clashes with
|
||||
// other applications who might have had the bright idea of
|
||||
// generating MACs in the same way.
|
||||
hash.Write([]byte("9oBJ0Jmip-"))
|
||||
hash.Write(uuid)
|
||||
sum := hash.Sum(nil)
|
||||
|
||||
setUnicastAndLocal(sum)
|
||||
|
||||
return net.HardwareAddr(sum[:6])
|
||||
}
|
||||
|
||||
// In the first byte of the MAC, the 'multicast' bit should be
|
||||
// clear and 'locally administered' bit should be set.
|
||||
func setUnicastAndLocal(mac []byte) {
|
||||
mac[0] = (mac[0] & 0xFE) | 0x02
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netns"
|
||||
)
|
||||
|
||||
type Dev struct {
|
||||
Name string `json:"Name,omitempty"`
|
||||
MAC net.HardwareAddr `json:"MAC,omitempty"`
|
||||
CIDRs []*net.IPNet `json:"CIDRs,omitempty"`
|
||||
}
|
||||
|
||||
func LinkToNetDev(link netlink.Link) (Dev, error) {
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return Dev{}, err
|
||||
}
|
||||
|
||||
netDev := Dev{Name: link.Attrs().Name, MAC: link.Attrs().HardwareAddr}
|
||||
for _, addr := range addrs {
|
||||
netDev.CIDRs = append(netDev.CIDRs, addr.IPNet)
|
||||
}
|
||||
return netDev, nil
|
||||
}
|
||||
|
||||
// ConnectedToBridgeVethPeerIds returns peer indexes of veth links connected to
|
||||
// the given bridge. The peer index is used to query from a container netns
|
||||
// whether the container is connected to the bridge.
|
||||
func ConnectedToBridgeVethPeerIds(bridgeName string) ([]int, error) {
|
||||
var ids []int
|
||||
|
||||
br, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, link := range links {
|
||||
if _, isveth := link.(*netlink.Veth); isveth && link.Attrs().MasterIndex == br.Attrs().Index {
|
||||
peerID := link.Attrs().ParentIndex
|
||||
if peerID == 0 {
|
||||
// perhaps running on an older kernel where ParentIndex doesn't work.
|
||||
// as fall-back, assume the peers are consecutive
|
||||
peerID = link.Attrs().Index - 1
|
||||
}
|
||||
ids = append(ids, peerID)
|
||||
}
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// Lookup the weave interface of a container
|
||||
func GetWeaveNetDevs(processID int) ([]Dev, error) {
|
||||
peerIDs, err := ConnectedToBridgeVethPeerIds("weave")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return GetNetDevsByVethPeerIds(processID, peerIDs)
|
||||
}
|
||||
|
||||
func GetNetDevsByVethPeerIds(processID int, peerIDs []int) ([]Dev, error) {
|
||||
// Bail out if this container is running in the root namespace
|
||||
netnsRoot, err := netns.GetFromPid(1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open root namespace: %s", err)
|
||||
}
|
||||
defer netnsRoot.Close()
|
||||
netnsContainer, err := netns.GetFromPid(processID)
|
||||
if err != nil {
|
||||
// Unable to find a namespace for this process - just return nothing
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unable to open process %d namespace: %s", processID, err)
|
||||
}
|
||||
defer netnsContainer.Close()
|
||||
if netnsRoot.Equal(netnsContainer) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
return netdevs, err
|
||||
}
|
||||
|
||||
// Get the weave bridge interface.
|
||||
// NB: Should be called from the root network namespace.
|
||||
func GetBridgeNetDev(bridgeName string) (Dev, error) {
|
||||
link, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return Dev{}, err
|
||||
}
|
||||
|
||||
return LinkToNetDev(link)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netns"
|
||||
)
|
||||
|
||||
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 {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
oldNs, err := netns.Get()
|
||||
if err == nil {
|
||||
defer oldNs.Close()
|
||||
|
||||
err = netns.Set(ns)
|
||||
if err == nil {
|
||||
defer netns.Set(oldNs)
|
||||
|
||||
err = work()
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func WithNetNSLinkUnsafe(ns netns.NsHandle, ifName string, work func(link netlink.Link) error) error {
|
||||
return WithNetNSUnsafe(ns, func() error {
|
||||
link, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
if err.Error() == errors.New("Link not found").Error() {
|
||||
return ErrLinkNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
return work(link)
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
|
||||
func WithNetNSByPid(pid int, cmd string, args ...string) ([]byte, error) {
|
||||
return WithNetNS(NSPathByPid(pid), cmd, args...)
|
||||
}
|
||||
|
||||
func NSPathByPid(pid int) string {
|
||||
return fmt.Sprintf("/proc/%d/ns/net", pid)
|
||||
}
|
||||
+16
-2
@@ -3,6 +3,7 @@ package net
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
func CheckNetworkFree(subnet *net.IPNet, ignoreIfaceNames map[string]struct{}) error {
|
||||
return forEachRoute(ignoreIfaceNames, func(route netlink.Route) error {
|
||||
if route.Dst != nil && overlaps(route.Dst, subnet) {
|
||||
return fmt.Errorf("Network %s overlaps with existing route %s on host.", subnet, route.Dst)
|
||||
return fmt.Errorf("Network %s overlaps with existing route %s on host", subnet, route.Dst)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -28,7 +29,7 @@ func overlaps(n1, n2 *net.IPNet) bool {
|
||||
func CheckAddressOverlap(addr net.IP, ignoreIfaceNames map[string]struct{}) error {
|
||||
return forEachRoute(ignoreIfaceNames, func(route netlink.Route) error {
|
||||
if route.Dst != nil && route.Dst.Contains(addr) {
|
||||
return fmt.Errorf("Address %s overlaps with existing route %s on host.", addr, route.Dst)
|
||||
return fmt.Errorf("Address %s overlaps with existing route %s on host", addr, route.Dst)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -55,3 +56,16 @@ func forEachRoute(ignoreIfaceNames map[string]struct{}, check func(r netlink.Rou
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddRoute(link netlink.Link, scope netlink.Scope, dst *net.IPNet, gw net.IP) error {
|
||||
err := netlink.RouteAdd(&netlink.Route{
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Scope: scope,
|
||||
Dst: dst,
|
||||
Gw: gw,
|
||||
})
|
||||
if os.IsExist(err) { // squash duplicate route errors
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
"github.com/weaveworks/weave/db"
|
||||
)
|
||||
|
||||
func getOldStyleSystemUUID() ([]byte, error) {
|
||||
uuid, err := ioutil.ReadFile("/sys/class/dmi/id/product_uuid")
|
||||
if os.IsNotExist(err) {
|
||||
uuid, err = ioutil.ReadFile("/sys/hypervisor/uuid")
|
||||
}
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
func getSystemUUID(hostRoot string) ([]byte, error) {
|
||||
uuid, err := getOldStyleSystemUUID()
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
machineid, err := ioutil.ReadFile(hostRoot + "/etc/machine-id")
|
||||
if os.IsNotExist(err) {
|
||||
machineid, err = ioutil.ReadFile(hostRoot + "/var/lib/dbus/machine-id")
|
||||
}
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
if len(uuid) == 0 && len(machineid) == 0 {
|
||||
return nil, errors.New("All system IDs are blank")
|
||||
}
|
||||
return append(machineid, uuid...), nil
|
||||
}
|
||||
|
||||
func getPersistedPeerName(pathName string) (mesh.PeerName, error) {
|
||||
d, err := db.NewBoltDBReadOnly(pathName)
|
||||
if err != nil {
|
||||
return mesh.UnknownPeerName, err
|
||||
}
|
||||
defer d.Close()
|
||||
var peerName mesh.PeerName
|
||||
nameFound, err := d.Load(db.NameIdent, &peerName)
|
||||
if err != nil || !nameFound {
|
||||
return mesh.UnknownPeerName, err
|
||||
}
|
||||
return peerName, nil
|
||||
}
|
||||
|
||||
// GetSystemPeerName returns an ID derived from concatenated machine-id
|
||||
// (either systemd or dbus), the system (aka bios) UUID and the
|
||||
// hypervisor UUID. It is tweaked and formatted to be usable as a mac address
|
||||
func GetSystemPeerName(dbPrefix, hostRoot string) (string, error) {
|
||||
// Check if we have a persisted name that matches the old-style ID for this host
|
||||
if oldUUID, err := getOldStyleSystemUUID(); err == nil {
|
||||
pathName := dbPrefix + db.FileName
|
||||
if _, err := os.Stat(pathName); err == nil {
|
||||
persistedPeerName, err := getPersistedPeerName(pathName)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
if persistedPeerName == mesh.PeerNameFromBin(MACfromUUID(oldUUID)) {
|
||||
return persistedPeerName.String(), nil
|
||||
}
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
var mac net.HardwareAddr
|
||||
if uuid, err := getSystemUUID(hostRoot); err == nil {
|
||||
mac = MACfromUUID(uuid)
|
||||
} else if !os.IsNotExist(err) {
|
||||
return "", err
|
||||
} else {
|
||||
mac, err = RandomMAC()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return mac.String(), nil
|
||||
}
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/j-keck/arping"
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netns"
|
||||
|
||||
"github.com/weaveworks/weave/common/odp"
|
||||
)
|
||||
|
||||
// create and attach a veth to the Weave bridge
|
||||
func CreateAndAttachVeth(name, peerName, bridgeName string, mtu int, keepTXOn bool, init func(peer netlink.Link) error) (*netlink.Veth, error) {
|
||||
bridge, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`bridge "%s" not present; did you launch weave?`, bridgeName)
|
||||
}
|
||||
|
||||
if mtu == 0 {
|
||||
mtu = bridge.Attrs().MTU
|
||||
}
|
||||
veth := &netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: name,
|
||||
MTU: mtu},
|
||||
PeerName: peerName,
|
||||
}
|
||||
if err := netlink.LinkAdd(veth); err != nil {
|
||||
return nil, fmt.Errorf(`could not create veth pair %s-%s: %s`, name, peerName, err)
|
||||
}
|
||||
|
||||
cleanup := func(format string, a ...interface{}) (*netlink.Veth, error) {
|
||||
netlink.LinkDel(veth)
|
||||
return nil, fmt.Errorf(format, a...)
|
||||
}
|
||||
|
||||
switch bridgeType := DetectBridgeType(bridgeName, DatapathName); bridgeType {
|
||||
case Bridge, BridgedFastdp:
|
||||
if err := netlink.LinkSetMasterByIndex(veth, bridge.Attrs().Index); err != nil {
|
||||
return cleanup(`unable to set master of %s: %s`, name, err)
|
||||
}
|
||||
if bridgeType == Bridge && !keepTXOn {
|
||||
if err := EthtoolTXOff(peerName); err != nil {
|
||||
return cleanup(`unable to set tx off on %q: %s`, peerName, err)
|
||||
}
|
||||
}
|
||||
case Fastdp:
|
||||
if err := odp.AddDatapathInterface(bridgeName, name); err != nil {
|
||||
return cleanup(`failed to attach %s to device "%s": %s`, name, bridgeName, err)
|
||||
}
|
||||
default:
|
||||
return cleanup(`invalid bridge configuration`)
|
||||
}
|
||||
|
||||
if init != nil {
|
||||
peer, err := netlink.LinkByName(peerName)
|
||||
if err != nil {
|
||||
return cleanup("unable to find peer veth %s: %s", peerName, err)
|
||||
}
|
||||
if err := init(peer); err != nil {
|
||||
return cleanup("initializing veth: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := netlink.LinkSetUp(veth); err != nil {
|
||||
return cleanup("unable to bring veth up: %s", err)
|
||||
}
|
||||
|
||||
return veth, nil
|
||||
}
|
||||
|
||||
func AddAddresses(link netlink.Link, cidrs []*net.IPNet) (newAddrs []*net.IPNet, err error) {
|
||||
existingAddrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get IP address for %q: %v", link.Attrs().Name, err)
|
||||
}
|
||||
for _, ipnet := range cidrs {
|
||||
if contains(existingAddrs, ipnet) {
|
||||
continue
|
||||
}
|
||||
if err := netlink.AddrAdd(link, &netlink.Addr{IPNet: ipnet}); err != nil {
|
||||
return nil, fmt.Errorf("failed to add IP address to %q: %v", link.Attrs().Name, err)
|
||||
}
|
||||
newAddrs = append(newAddrs, ipnet)
|
||||
}
|
||||
return newAddrs, nil
|
||||
}
|
||||
|
||||
func contains(addrs []netlink.Addr, addr *net.IPNet) bool {
|
||||
for _, x := range addrs {
|
||||
if addr.IP.Equal(x.IPNet.IP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const (
|
||||
VethName = "ethwe" // name inside container namespace
|
||||
vethPrefix = "v" + VethName // starts with "veth" to suppress UI notifications
|
||||
)
|
||||
|
||||
func interfaceExistsInNamespace(netNSPath string, ifName string) bool {
|
||||
_, err := WithNetNS(netNSPath, "check-iface", ifName)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// NB: This function can be used only by a process that terminates immediately
|
||||
// after calling the function as it changes netns via WithNetNSLinkUnsafe.
|
||||
func AttachContainer(netNSPath, id, ifName, bridgeName string, mtu int, withMulticastRoute bool, cidrs []*net.IPNet, keepTXOn bool) error {
|
||||
ns, err := netns.GetFromPath(netNSPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ns.Close()
|
||||
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !interfaceExistsInNamespace(netNSPath, ifName) {
|
||||
maxIDLen := IFNAMSIZ - 1 - len(vethPrefix+"pl")
|
||||
if len(id) > maxIDLen {
|
||||
id = id[:maxIDLen] // trim passed ID if too long
|
||||
}
|
||||
name, peerName := vethPrefix+"pl"+id, vethPrefix+"pg"+id
|
||||
_, err := CreateAndAttachVeth(name, peerName, bridgeName, mtu, keepTXOn, func(veth netlink.Link) error {
|
||||
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 {
|
||||
return fmt.Errorf("error setting up interface: %s", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := WithNetNSLinkUnsafe(ns, ifName, func(veth netlink.Link) error {
|
||||
newAddresses, err := AddAddresses(veth, cidrs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add multicast ACCEPT rules for new subnets
|
||||
for _, ipnet := range newAddresses {
|
||||
acceptRule := []string{"-i", ifName, "-s", subnet(ipnet), "-d", "224.0.0.0/4", "-j", "ACCEPT"}
|
||||
exists, err := ipt.Exists("filter", "INPUT", acceptRule...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
if err := ipt.Insert("filter", "INPUT", 1, acceptRule...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := netlink.LinkSetUp(veth); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, ipnet := range newAddresses {
|
||||
// If we don't wait for a bit here, we see the arp fail to reach the bridge.
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
arping.GratuitousArpOverIfaceByName(ipnet.IP, ifName)
|
||||
}
|
||||
if withMulticastRoute {
|
||||
/* Route multicast packets across the weave network.
|
||||
This must come last in 'attach'. If you change this, change weavewait to match.
|
||||
|
||||
TODO: Add the MTU lock to prevent PMTU discovery for multicast
|
||||
destinations. Without that, the kernel sets the DF flag on
|
||||
multicast packets. Since RFC1122 prohibits sending of ICMP
|
||||
errors for packets with multicast destinations, that causes
|
||||
packets larger than the PMTU to be dropped silently. */
|
||||
|
||||
_, multicast, _ := net.ParseCIDR("224.0.0.0/4")
|
||||
if err := AddRoute(veth, netlink.SCOPE_LINK, multicast, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NB: This function can be used only by a process that terminates immediately
|
||||
// after calling the function as it changes netns via WithNetNSLinkUnsafe.
|
||||
func DetachContainer(netNSPath, id, ifName string, cidrs []*net.IPNet) error {
|
||||
ns, err := netns.GetFromPath(netNSPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ns.Close()
|
||||
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return WithNetNSLinkUnsafe(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)
|
||||
}
|
||||
for _, ipnet := range cidrs {
|
||||
if !contains(existingAddrs, ipnet) {
|
||||
continue
|
||||
}
|
||||
if err := netlink.AddrDel(veth, &netlink.Addr{IPNet: ipnet}); err != nil {
|
||||
return fmt.Errorf("failed to remove IP address from %q: %v", veth.Attrs().Name, err)
|
||||
}
|
||||
}
|
||||
addrs, 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)
|
||||
}
|
||||
|
||||
// Remove multicast ACCEPT rules for subnets we no longer have addresses in
|
||||
subnets := subnets(addrs)
|
||||
rules, err := ipt.List("filter", "INPUT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rule := range rules {
|
||||
ps := strings.Split(rule, " ")
|
||||
if len(ps) == 10 &&
|
||||
ps[0] == "-A" && ps[2] == "-s" && ps[4] == "-d" && ps[5] == "224.0.0.0/4" &&
|
||||
ps[6] == "-i" && ps[7] == ifName && ps[8] == "-j" && ps[9] == "ACCEPT" {
|
||||
|
||||
if _, found := subnets[ps[3]]; !found {
|
||||
if err := ipt.Delete("filter", "INPUT", ps[2:]...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(addrs) == 0 { // all addresses gone: remove the interface
|
||||
if err := ipt.Delete("filter", "INPUT", "-i", ifName, "-d", "224.0.0.0/4", "-j", "DROP"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := netlink.LinkDel(veth); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func subnet(ipn *net.IPNet) string {
|
||||
ones, _ := ipn.Mask.Size()
|
||||
return fmt.Sprintf("%s/%d", ipn.IP.Mask(ipn.Mask).String(), ones)
|
||||
}
|
||||
|
||||
func subnets(addrs []netlink.Addr) map[string]struct{} {
|
||||
subnets := make(map[string]struct{})
|
||||
for _, addr := range addrs {
|
||||
subnets[subnet(addr.IPNet)] = struct{}{}
|
||||
}
|
||||
return subnets
|
||||
}
|
||||
|
||||
func addNatRule(ipt *iptables.IPTables, rulespec ...string) error {
|
||||
// Loop until we get an exit code other than "temporarily unavailable"
|
||||
for {
|
||||
if err := ipt.AppendUnique("nat", "WEAVE", rulespec...); err != nil {
|
||||
if ierr, ok := err.(*iptables.Error); ok {
|
||||
if status, ok := ierr.ExitError.Sys().(syscall.WaitStatus); ok {
|
||||
// (magic exit code 4 found in iptables source code; undocumented)
|
||||
if status.ExitStatus() == 4 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func ExposeNAT(ipnet net.IPNet) error {
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cidr := ipnet.String()
|
||||
if err := addNatRule(ipt, "-s", cidr, "-d", "224.0.0.0/4", "-j", "RETURN"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addNatRule(ipt, "-d", cidr, "!", "-s", cidr, "-j", "MASQUERADE"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addNatRule(ipt, "-s", cidr, "!", "-d", cidr, "-j", "MASQUERADE"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/client-go/pkg/api"
|
||||
extnapi "k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
"k8s.io/client-go/pkg/util/intstr"
|
||||
|
||||
"github.com/weaveworks/weave/npc/ipset"
|
||||
)
|
||||
|
||||
func (ns *ns) analysePolicy(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, 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.
|
||||
withNormalisedProtoAndPort(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, 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, "", 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.
|
||||
withNormalisedProtoAndPort(ingressRule.Ports, func(proto, port string) {
|
||||
rule := newRuleSpec(&proto, srcSelector, dstSelector, &port)
|
||||
rules[rule.key] = rule
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rules, nsSelectors, podSelectors, nil
|
||||
}
|
||||
|
||||
func withNormalisedProtoAndPort(npps []extnapi.NetworkPolicyPort, f func(proto, port string)) {
|
||||
for _, npp := range npps {
|
||||
// If no proto is specified, default to TCP
|
||||
proto := string(api.ProtocolTCP)
|
||||
if npp.Protocol != nil {
|
||||
proto = string(*npp.Protocol)
|
||||
}
|
||||
|
||||
// If no port is specified, match any port. Let iptables executable handle
|
||||
// service name resolution
|
||||
port := "0:65535"
|
||||
if npp.Port != nil {
|
||||
switch npp.Port.Type {
|
||||
case intstr.Int:
|
||||
port = fmt.Sprintf("%d", npp.Port.IntVal)
|
||||
case intstr.String:
|
||||
port = npp.Port.StrVal
|
||||
}
|
||||
}
|
||||
|
||||
f(proto, port)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package npc
|
||||
|
||||
// These types are defined in https://github.com/kubernetes/kubernetes/blob/master/docs/proposals/network-policy.md
|
||||
// but not in the k8s API yet. Copies are included here for decoding the `net.beta.kubernetes.io/network-policy`
|
||||
// annotation specified in the above document.
|
||||
|
||||
type IngressIsolationPolicy string
|
||||
|
||||
const (
|
||||
// Deny all ingress traffic to pods in this namespace. Ingress means
|
||||
// any incoming traffic to pods, whether that be from other pods within this namespace
|
||||
// or any source outside of this namespace.
|
||||
DefaultDeny IngressIsolationPolicy = "DefaultDeny"
|
||||
)
|
||||
|
||||
// Standard NamespaceSpec object, modified to include a new
|
||||
// NamespaceNetworkPolicy field.
|
||||
type NamespaceSpec struct {
|
||||
// This is a pointer so that it can be left undefined.
|
||||
NetworkPolicy *NamespaceNetworkPolicy `json:"networkPolicy,omitempty"`
|
||||
}
|
||||
|
||||
type NamespaceNetworkPolicy struct {
|
||||
// Ingress configuration for this namespace. This config is
|
||||
// applied to all pods within this namespace. For now, only
|
||||
// ingress is supported. This field is optional - if not
|
||||
// defined, then the cluster default for ingress is applied.
|
||||
Ingress *NamespaceIngressPolicy `json:"ingress,omitempty"`
|
||||
}
|
||||
|
||||
// Configuration for ingress to pods within this namespace.
|
||||
// For now, this only supports specifying an isolation policy.
|
||||
type NamespaceIngressPolicy struct {
|
||||
// The isolation policy to apply to pods in this namespace.
|
||||
// Currently this field only supports "DefaultDeny", but could
|
||||
// be extended to support other policies in the future. When set to DefaultDeny,
|
||||
// pods in this namespace are denied ingress traffic by default. When not defined,
|
||||
// the cluster default ingress isolation policy is applied (currently allow all).
|
||||
Isolation *IngressIsolationPolicy `json:"isolation,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package npc
|
||||
|
||||
const (
|
||||
TableFilter = "filter"
|
||||
|
||||
MainChain = "WEAVE-NPC"
|
||||
DefaultChain = "WEAVE-NPC-DEFAULT"
|
||||
IngressChain = "WEAVE-NPC-INGRESS"
|
||||
)
|
||||
@@ -0,0 +1,173 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/pkg/errors"
|
||||
coreapi "k8s.io/client-go/pkg/api/v1"
|
||||
extnapi "k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/npc/ipset"
|
||||
)
|
||||
|
||||
type NetworkPolicyController interface {
|
||||
AddNamespace(ns *coreapi.Namespace) error
|
||||
UpdateNamespace(oldObj, newObj *coreapi.Namespace) error
|
||||
DeleteNamespace(ns *coreapi.Namespace) error
|
||||
|
||||
AddPod(obj *coreapi.Pod) error
|
||||
UpdatePod(oldObj, newObj *coreapi.Pod) error
|
||||
DeletePod(obj *coreapi.Pod) error
|
||||
|
||||
AddNetworkPolicy(obj *extnapi.NetworkPolicy) error
|
||||
UpdateNetworkPolicy(oldObj, newObj *extnapi.NetworkPolicy) error
|
||||
DeleteNetworkPolicy(obj *extnapi.NetworkPolicy) error
|
||||
}
|
||||
|
||||
type controller struct {
|
||||
sync.Mutex
|
||||
|
||||
ipt *iptables.IPTables
|
||||
ips ipset.Interface
|
||||
|
||||
nss map[string]*ns // ns name -> ns struct
|
||||
nsSelectors *selectorSet // selector string -> nsSelector
|
||||
}
|
||||
|
||||
func New(ipt *iptables.IPTables, ips ipset.Interface) NetworkPolicyController {
|
||||
c := &controller{
|
||||
ipt: ipt,
|
||||
ips: ips,
|
||||
nss: make(map[string]*ns)}
|
||||
|
||||
c.nsSelectors = newSelectorSet(ips, c.onNewNsSelector)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (npc *controller) onNewNsSelector(selector *selector) error {
|
||||
for _, ns := range npc.nss {
|
||||
if ns.namespace != nil {
|
||||
if selector.matches(ns.namespace.ObjectMeta.Labels) {
|
||||
if err := ns.ips.AddEntry(selector.spec.ipsetName, string(ns.allPods.ipsetName)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (npc *controller) withNS(name string, f func(ns *ns) error) error {
|
||||
ns, found := npc.nss[name]
|
||||
if !found {
|
||||
newNs, err := newNS(name, npc.ipt, npc.ips, npc.nsSelectors)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
npc.nss[name] = newNs
|
||||
ns = newNs
|
||||
}
|
||||
if err := f(ns); err != nil {
|
||||
return err
|
||||
}
|
||||
if ns.empty() {
|
||||
if err := ns.destroy(); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(npc.nss, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (npc *controller) AddPod(obj *coreapi.Pod) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Debugf("EVENT AddPod %s", js(obj))
|
||||
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||
return errors.Wrap(ns.addPod(obj), "add pod")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) UpdatePod(oldObj, newObj *coreapi.Pod) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Debugf("EVENT UpdatePod %s %s", js(oldObj), js(newObj))
|
||||
return npc.withNS(oldObj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||
return errors.Wrap(ns.updatePod(oldObj, newObj), "update pod")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) DeletePod(obj *coreapi.Pod) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Debugf("EVENT DeletePod %s", js(obj))
|
||||
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||
return errors.Wrap(ns.deletePod(obj), "delete pod")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) AddNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Infof("EVENT AddNetworkPolicy %s", js(obj))
|
||||
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||
return errors.Wrap(ns.addNetworkPolicy(obj), "add network policy")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) UpdateNetworkPolicy(oldObj, newObj *extnapi.NetworkPolicy) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Infof("EVENT UpdateNetworkPolicy %s %s", js(oldObj), js(newObj))
|
||||
return npc.withNS(oldObj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||
return errors.Wrap(ns.updateNetworkPolicy(oldObj, newObj), "update network policy")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) DeleteNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Infof("EVENT DeleteNetworkPolicy %s", js(obj))
|
||||
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||
return errors.Wrap(ns.deleteNetworkPolicy(obj), "delete network policy")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) AddNamespace(obj *coreapi.Namespace) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Infof("EVENT AddNamespace %s", js(obj))
|
||||
return npc.withNS(obj.ObjectMeta.Name, func(ns *ns) error {
|
||||
return errors.Wrap(ns.addNamespace(obj), "add namespace")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) UpdateNamespace(oldObj, newObj *coreapi.Namespace) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Infof("EVENT UpdateNamespace %s %s", js(oldObj), js(newObj))
|
||||
return npc.withNS(oldObj.ObjectMeta.Name, func(ns *ns) error {
|
||||
return errors.Wrap(ns.updateNamespace(oldObj, newObj), "update namespace")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) DeleteNamespace(obj *coreapi.Namespace) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Infof("EVENT DeleteNamespace %s", js(obj))
|
||||
return npc.withNS(obj.ObjectMeta.Name, func(ns *ns) error {
|
||||
return errors.Wrap(ns.deleteNamespace(obj), "delete namespace")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package ipset
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Name string
|
||||
|
||||
type Type string
|
||||
|
||||
const (
|
||||
ListSet = Type("list:set")
|
||||
HashIP = Type("hash:ip")
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Create(ipsetName Name, ipsetType Type) error
|
||||
AddEntry(ipsetName Name, entry string) error
|
||||
DelEntry(ipsetName Name, entry string) error
|
||||
Flush(ipsetName Name) error
|
||||
Destroy(ipsetName Name) error
|
||||
|
||||
FlushAll() error
|
||||
DestroyAll() error
|
||||
}
|
||||
|
||||
type ipset struct {
|
||||
refCount
|
||||
}
|
||||
|
||||
func New() Interface {
|
||||
return &ipset{refCount: newRefCount()}
|
||||
}
|
||||
|
||||
func (i *ipset) Create(ipsetName Name, ipsetType Type) error {
|
||||
return doExec("create", string(ipsetName), string(ipsetType))
|
||||
}
|
||||
|
||||
func (i *ipset) AddEntry(ipsetName Name, entry string) error {
|
||||
if i.inc(ipsetName, entry) > 1 { // already in the set
|
||||
return nil
|
||||
}
|
||||
return doExec("add", string(ipsetName), entry)
|
||||
}
|
||||
|
||||
func (i *ipset) DelEntry(ipsetName Name, entry string) error {
|
||||
if i.dec(ipsetName, entry) > 0 { // still needed
|
||||
return nil
|
||||
}
|
||||
return doExec("del", string(ipsetName), entry)
|
||||
}
|
||||
|
||||
func (i *ipset) Flush(ipsetName Name) error {
|
||||
i.removeSet(ipsetName)
|
||||
return doExec("flush", string(ipsetName))
|
||||
}
|
||||
|
||||
func (i *ipset) FlushAll() error {
|
||||
i.refCount = newRefCount()
|
||||
return doExec("flush")
|
||||
}
|
||||
|
||||
func (i *ipset) Destroy(ipsetName Name) error {
|
||||
i.removeSet(ipsetName)
|
||||
return doExec("destroy", string(ipsetName))
|
||||
}
|
||||
|
||||
func (i *ipset) DestroyAll() error {
|
||||
i.refCount = newRefCount()
|
||||
return doExec("destroy")
|
||||
}
|
||||
|
||||
func doExec(args ...string) error {
|
||||
if output, err := exec.Command("ipset", args...).CombinedOutput(); err != nil {
|
||||
return errors.Wrapf(err, "ipset %v failed: %s", args, output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reference-counting
|
||||
type key struct {
|
||||
ipsetName Name
|
||||
entry string
|
||||
}
|
||||
|
||||
// note no locking is required as all operations are serialised in the controller
|
||||
type refCount struct {
|
||||
ref map[key]int
|
||||
}
|
||||
|
||||
func newRefCount() refCount {
|
||||
return refCount{ref: make(map[key]int)}
|
||||
}
|
||||
|
||||
func (rc *refCount) inc(ipsetName Name, entry string) int {
|
||||
k := key{ipsetName, entry}
|
||||
rc.ref[k]++
|
||||
return rc.ref[k]
|
||||
}
|
||||
|
||||
func (rc *refCount) dec(ipsetName Name, entry string) int {
|
||||
k := key{ipsetName, entry}
|
||||
rc.ref[k]--
|
||||
return rc.ref[k]
|
||||
}
|
||||
|
||||
func (rc *refCount) removeSet(ipsetName Name) {
|
||||
for k := range rc.ref {
|
||||
if k.ipsetName == ipsetName {
|
||||
delete(rc.ref, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
func js(v interface{}) string {
|
||||
a, _ := json.Marshal(v)
|
||||
return string(a)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/google/gopacket/pcapgo"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
var (
|
||||
blockedConnections = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "weavenpc_blocked_connections_total",
|
||||
Help: "Connection attempts blocked by policy controller.",
|
||||
},
|
||||
[]string{"protocol", "dport"},
|
||||
)
|
||||
)
|
||||
|
||||
func gatherMetrics() {
|
||||
pipe, err := os.Open("/var/log/ulogd.pcap")
|
||||
if err != nil {
|
||||
common.Log.Fatalf("Failed to open pcap: %v", err)
|
||||
}
|
||||
|
||||
reader, err := pcapgo.NewReader(pipe)
|
||||
if err != nil {
|
||||
common.Log.Fatalf("Failed to read pcap header: %v", err)
|
||||
}
|
||||
|
||||
for {
|
||||
data, _, err := reader.ReadPacketData()
|
||||
if err != nil {
|
||||
common.Log.Fatalf("Failed to read pcap packet: %v", err)
|
||||
}
|
||||
|
||||
packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)
|
||||
|
||||
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
|
||||
tcp, _ := tcpLayer.(*layers.TCP)
|
||||
if tcp.SYN && !tcp.ACK { // Only plain SYN constitutes a NEW TCP connection
|
||||
blockedConnections.With(prometheus.Labels{"protocol": "tcp", "dport": strconv.Itoa(int(tcp.DstPort))}).Inc()
|
||||
common.Log.Warnf("TCP connection from %v:%d to %v:%d blocked by Weave NPC.", srcIP(packet), tcp.SrcPort, dstIP(packet), tcp.DstPort)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if udpLayer := packet.Layer(layers.LayerTypeUDP); udpLayer != nil {
|
||||
udp, _ := udpLayer.(*layers.UDP)
|
||||
blockedConnections.With(prometheus.Labels{"protocol": "udp", "dport": strconv.Itoa(int(udp.DstPort))}).Inc()
|
||||
common.Log.Warnf("UDP connection from %v:%d to %v:%d blocked by Weave NPC.", srcIP(packet), udp.SrcPort, dstIP(packet), udp.DstPort)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unknownIP string = "<unknown IP>"
|
||||
|
||||
func srcIP(packet gopacket.Packet) string {
|
||||
if layer := packet.Layer(layers.LayerTypeIPv4); layer != nil {
|
||||
if ip, ok := layer.(*layers.IPv4); ok {
|
||||
return ip.SrcIP.String()
|
||||
}
|
||||
}
|
||||
if layer := packet.Layer(layers.LayerTypeIPv6); layer != nil {
|
||||
if ip, ok := layer.(*layers.IPv6); ok {
|
||||
return ip.SrcIP.String()
|
||||
}
|
||||
}
|
||||
return unknownIP
|
||||
}
|
||||
|
||||
func dstIP(packet gopacket.Packet) string {
|
||||
if layer := packet.Layer(layers.LayerTypeIPv4); layer != nil {
|
||||
if ip, ok := layer.(*layers.IPv4); ok {
|
||||
return ip.DstIP.String()
|
||||
}
|
||||
}
|
||||
if layer := packet.Layer(layers.LayerTypeIPv6); layer != nil {
|
||||
if ip, ok := layer.(*layers.IPv6); ok {
|
||||
return ip.DstIP.String()
|
||||
}
|
||||
}
|
||||
return unknownIP
|
||||
}
|
||||
|
||||
func Start(addr string) error {
|
||||
if err := prometheus.Register(blockedConnections); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
go func() {
|
||||
common.Log.Infof("Serving /metrics on %s", addr)
|
||||
if err := http.ListenAndServe(addr, nil); err != nil {
|
||||
common.Log.Fatalf("Failed to bind metrics server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
go gatherMetrics()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"k8s.io/client-go/pkg/api/unversioned"
|
||||
coreapi "k8s.io/client-go/pkg/api/v1"
|
||||
extnapi "k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
"k8s.io/client-go/pkg/types"
|
||||
"k8s.io/client-go/pkg/util/uuid"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/npc/ipset"
|
||||
)
|
||||
|
||||
type ns struct {
|
||||
ipt *iptables.IPTables // interface to iptables
|
||||
ips ipset.Interface // interface to ipset
|
||||
|
||||
name string // k8s Namespace name
|
||||
namespace *coreapi.Namespace // k8s Namespace object
|
||||
pods map[types.UID]*coreapi.Pod // k8s Pod objects by UID
|
||||
policies map[types.UID]*extnapi.NetworkPolicy // k8s NetworkPolicy objects by UID
|
||||
|
||||
uid types.UID // surrogate UID to own allPods selector
|
||||
allPods *selectorSpec // hash:ip ipset of all pod IPs in this namespace
|
||||
|
||||
nsSelectors *selectorSet
|
||||
podSelectors *selectorSet
|
||||
rules *ruleSet
|
||||
}
|
||||
|
||||
func newNS(name string, ipt *iptables.IPTables, ips ipset.Interface, nsSelectors *selectorSet) (*ns, error) {
|
||||
allPods, err := newSelectorSpec(&unversioned.LabelSelector{}, name, ipset.HashIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ns := &ns{
|
||||
ipt: ipt,
|
||||
ips: ips,
|
||||
name: name,
|
||||
pods: make(map[types.UID]*coreapi.Pod),
|
||||
policies: make(map[types.UID]*extnapi.NetworkPolicy),
|
||||
uid: uuid.NewUUID(),
|
||||
allPods: allPods,
|
||||
nsSelectors: nsSelectors,
|
||||
rules: newRuleSet(ipt)}
|
||||
|
||||
ns.podSelectors = newSelectorSet(ips, ns.onNewPodSelector)
|
||||
|
||||
if err := ns.podSelectors.provision(ns.uid, nil, map[string]*selectorSpec{ns.allPods.key: ns.allPods}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (ns *ns) empty() bool {
|
||||
return len(ns.pods) == 0 && len(ns.policies) == 0 && ns.namespace == nil
|
||||
}
|
||||
|
||||
func (ns *ns) destroy() error {
|
||||
if err := ns.podSelectors.deprovision(ns.uid, map[string]*selectorSpec{ns.allPods.key: ns.allPods}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) onNewPodSelector(selector *selector) error {
|
||||
for _, pod := range ns.pods {
|
||||
if hasIP(pod) {
|
||||
if selector.matches(pod.ObjectMeta.Labels) {
|
||||
if err := selector.addEntry(pod.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) addPod(obj *coreapi.Pod) error {
|
||||
ns.pods[obj.ObjectMeta.UID] = obj
|
||||
|
||||
if !hasIP(obj) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ns.podSelectors.addToMatching(obj.ObjectMeta.Labels, obj.Status.PodIP)
|
||||
}
|
||||
|
||||
func (ns *ns) updatePod(oldObj, newObj *coreapi.Pod) error {
|
||||
delete(ns.pods, oldObj.ObjectMeta.UID)
|
||||
ns.pods[newObj.ObjectMeta.UID] = newObj
|
||||
|
||||
if !hasIP(oldObj) && !hasIP(newObj) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if hasIP(oldObj) && !hasIP(newObj) {
|
||||
return ns.podSelectors.delFromMatching(oldObj.ObjectMeta.Labels, oldObj.Status.PodIP)
|
||||
}
|
||||
|
||||
if !hasIP(oldObj) && hasIP(newObj) {
|
||||
return ns.podSelectors.addToMatching(newObj.ObjectMeta.Labels, newObj.Status.PodIP)
|
||||
}
|
||||
|
||||
if !equals(oldObj.ObjectMeta.Labels, newObj.ObjectMeta.Labels) ||
|
||||
oldObj.Status.PodIP != newObj.Status.PodIP {
|
||||
|
||||
for _, ps := range ns.podSelectors.entries {
|
||||
oldMatch := ps.matches(oldObj.ObjectMeta.Labels)
|
||||
newMatch := ps.matches(newObj.ObjectMeta.Labels)
|
||||
if oldMatch == newMatch && oldObj.Status.PodIP == newObj.Status.PodIP {
|
||||
continue
|
||||
}
|
||||
if oldMatch {
|
||||
if err := ps.delEntry(oldObj.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if newMatch {
|
||||
if err := ps.addEntry(newObj.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) deletePod(obj *coreapi.Pod) error {
|
||||
delete(ns.pods, obj.ObjectMeta.UID)
|
||||
|
||||
if !hasIP(obj) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ns.podSelectors.delFromMatching(obj.ObjectMeta.Labels, obj.Status.PodIP)
|
||||
}
|
||||
|
||||
func (ns *ns) addNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||
ns.policies[obj.ObjectMeta.UID] = obj
|
||||
|
||||
// Analyse policy, determine which rules and ipsets are required
|
||||
rules, nsSelectors, podSelectors, err := ns.analysePolicy(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Provision required resources in dependency order
|
||||
if err := ns.nsSelectors.provision(obj.ObjectMeta.UID, nil, nsSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.podSelectors.provision(obj.ObjectMeta.UID, nil, podSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.rules.provision(obj.ObjectMeta.UID, nil, rules); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) updateNetworkPolicy(oldObj, newObj *extnapi.NetworkPolicy) error {
|
||||
delete(ns.policies, oldObj.ObjectMeta.UID)
|
||||
ns.policies[newObj.ObjectMeta.UID] = newObj
|
||||
|
||||
// Analyse the old and the new policy so we can determine differences
|
||||
oldRules, oldNsSelectors, oldPodSelectors, err := ns.analysePolicy(oldObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRules, newNsSelectors, newPodSelectors, err := ns.analysePolicy(newObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Deprovision unused and provision newly required resources in dependency order
|
||||
if err := ns.rules.deprovision(oldObj.ObjectMeta.UID, oldRules, newRules); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.nsSelectors.deprovision(oldObj.ObjectMeta.UID, oldNsSelectors, newNsSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.podSelectors.deprovision(oldObj.ObjectMeta.UID, oldPodSelectors, newPodSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.nsSelectors.provision(oldObj.ObjectMeta.UID, oldNsSelectors, newNsSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.podSelectors.provision(oldObj.ObjectMeta.UID, oldPodSelectors, newPodSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.rules.provision(oldObj.ObjectMeta.UID, oldRules, newRules); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) deleteNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||
delete(ns.policies, obj.ObjectMeta.UID)
|
||||
|
||||
// Analyse network policy to free resources
|
||||
rules, nsSelectors, podSelectors, err := ns.analysePolicy(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Deprovision unused resources in dependency order
|
||||
if err := ns.rules.deprovision(obj.ObjectMeta.UID, rules, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.nsSelectors.deprovision(obj.ObjectMeta.UID, nsSelectors, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.podSelectors.deprovision(obj.ObjectMeta.UID, podSelectors, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func bypassRule(nsIpsetName ipset.Name) []string {
|
||||
return []string{"-m", "set", "--match-set", string(nsIpsetName), "dst", "-j", "ACCEPT"}
|
||||
}
|
||||
|
||||
func (ns *ns) ensureBypassRule(nsIpsetName ipset.Name) error {
|
||||
return ns.ipt.Append(TableFilter, DefaultChain, bypassRule(ns.allPods.ipsetName)...)
|
||||
}
|
||||
|
||||
func (ns *ns) deleteBypassRule(nsIpsetName ipset.Name) error {
|
||||
return ns.ipt.Delete(TableFilter, DefaultChain, bypassRule(ns.allPods.ipsetName)...)
|
||||
}
|
||||
|
||||
func (ns *ns) addNamespace(obj *coreapi.Namespace) error {
|
||||
ns.namespace = obj
|
||||
|
||||
// Insert a rule to bypass policies if namespace is DefaultAllow
|
||||
if !isDefaultDeny(obj) {
|
||||
return ns.ensureBypassRule(ns.allPods.ipsetName)
|
||||
}
|
||||
|
||||
// Add namespace ipset to matching namespace selectors
|
||||
return ns.nsSelectors.addToMatching(obj.ObjectMeta.Labels, string(ns.allPods.ipsetName))
|
||||
}
|
||||
|
||||
func (ns *ns) updateNamespace(oldObj, newObj *coreapi.Namespace) error {
|
||||
ns.namespace = newObj
|
||||
|
||||
// Update bypass rule if ingress default has changed
|
||||
oldDefaultDeny := isDefaultDeny(oldObj)
|
||||
newDefaultDeny := isDefaultDeny(newObj)
|
||||
|
||||
if oldDefaultDeny != newDefaultDeny {
|
||||
if oldDefaultDeny {
|
||||
return ns.ensureBypassRule(ns.allPods.ipsetName)
|
||||
}
|
||||
if newDefaultDeny {
|
||||
return ns.deleteBypassRule(ns.allPods.ipsetName)
|
||||
}
|
||||
}
|
||||
|
||||
// Re-evaluate namespace selector membership if labels have changed
|
||||
if !equals(oldObj.ObjectMeta.Labels, newObj.ObjectMeta.Labels) {
|
||||
for _, selector := range ns.nsSelectors.entries {
|
||||
oldMatch := selector.matches(oldObj.ObjectMeta.Labels)
|
||||
newMatch := selector.matches(newObj.ObjectMeta.Labels)
|
||||
if oldMatch == newMatch {
|
||||
continue
|
||||
}
|
||||
if oldMatch {
|
||||
if err := selector.delEntry(string(ns.allPods.ipsetName)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if newMatch {
|
||||
if err := selector.addEntry(string(ns.allPods.ipsetName)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) deleteNamespace(obj *coreapi.Namespace) error {
|
||||
ns.namespace = nil
|
||||
|
||||
// Remove bypass rule
|
||||
if !isDefaultDeny(obj) {
|
||||
return ns.deleteBypassRule(ns.allPods.ipsetName)
|
||||
}
|
||||
|
||||
// Remove namespace ipset from any matching namespace selectors
|
||||
return ns.nsSelectors.delFromMatching(obj.ObjectMeta.Labels, string(ns.allPods.ipsetName))
|
||||
}
|
||||
|
||||
func hasIP(pod *coreapi.Pod) bool {
|
||||
// Ensure pod isn't dead, has an IP address and isn't sharing the host network namespace
|
||||
return pod.Status.Phase != "Succeeded" && pod.Status.Phase != "Failed" &&
|
||||
len(pod.Status.PodIP) > 0 && !pod.Spec.HostNetwork
|
||||
}
|
||||
|
||||
func equals(a, b map[string]string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for ak, av := range a {
|
||||
if b[ak] != av {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isDefaultDeny(namespace *coreapi.Namespace) bool {
|
||||
nnpJSON, found := namespace.ObjectMeta.Annotations["net.beta.kubernetes.io/network-policy"]
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"k8s.io/client-go/pkg/types"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
type ruleSpec struct {
|
||||
key string
|
||||
args []string
|
||||
}
|
||||
|
||||
func newRuleSpec(proto *string, srcHost *selectorSpec, dstHost *selectorSpec, dstPort *string) *ruleSpec {
|
||||
args := []string{}
|
||||
if proto != nil {
|
||||
args = append(args, "-p", *proto)
|
||||
}
|
||||
if srcHost != nil {
|
||||
args = append(args, "-m", "set", "--match-set", string(srcHost.ipsetName), "src")
|
||||
}
|
||||
if dstHost != nil {
|
||||
args = append(args, "-m", "set", "--match-set", string(dstHost.ipsetName), "dst")
|
||||
}
|
||||
if dstPort != nil {
|
||||
args = append(args, "--dport", *dstPort)
|
||||
}
|
||||
args = append(args, "-j", "ACCEPT")
|
||||
key := strings.Join(args, " ")
|
||||
|
||||
return &ruleSpec{key, args}
|
||||
}
|
||||
|
||||
type ruleSet struct {
|
||||
ipt *iptables.IPTables
|
||||
users map[string]map[types.UID]struct{}
|
||||
}
|
||||
|
||||
func newRuleSet(ipt *iptables.IPTables) *ruleSet {
|
||||
return &ruleSet{ipt, make(map[string]map[types.UID]struct{})}
|
||||
}
|
||||
|
||||
func (rs *ruleSet) deprovision(user types.UID, current, desired map[string]*ruleSpec) error {
|
||||
for key, spec := range current {
|
||||
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
|
||||
}
|
||||
delete(rs.users, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rs *ruleSet) provision(user types.UID, current, desired map[string]*ruleSpec) error {
|
||||
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
|
||||
}
|
||||
rs.users[key] = make(map[types.UID]struct{})
|
||||
}
|
||||
rs.users[key][user] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"k8s.io/client-go/pkg/api/unversioned"
|
||||
"k8s.io/client-go/pkg/labels"
|
||||
"k8s.io/client-go/pkg/types"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/npc/ipset"
|
||||
)
|
||||
|
||||
type selectorSpec struct {
|
||||
key string // string representation (for hash keying/equality comparison)
|
||||
selector labels.Selector // k8s Selector object (for matching)
|
||||
|
||||
ipsetType ipset.Type // type of ipset to provision
|
||||
ipsetName ipset.Name // generated ipset name
|
||||
}
|
||||
|
||||
func newSelectorSpec(json *unversioned.LabelSelector, nsName string, ipsetType ipset.Type) (*selectorSpec, error) {
|
||||
selector, err := unversioned.LabelSelectorAsSelector(json)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := selector.String()
|
||||
return &selectorSpec{
|
||||
key: key,
|
||||
selector: selector,
|
||||
// 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
|
||||
ipsetName: ipset.Name("weave-" + shortName(nsName+":"+key)),
|
||||
ipsetType: ipsetType}, nil
|
||||
}
|
||||
|
||||
type selector struct {
|
||||
ips ipset.Interface
|
||||
spec *selectorSpec
|
||||
}
|
||||
|
||||
func (s *selector) matches(labelMap map[string]string) bool {
|
||||
return s.spec.selector.Matches(labels.Set(labelMap))
|
||||
}
|
||||
|
||||
func (s *selector) addEntry(entry string) error {
|
||||
common.Log.Infof("adding entry %s to %s", entry, s.spec.ipsetName)
|
||||
return s.ips.AddEntry(s.spec.ipsetName, entry)
|
||||
}
|
||||
|
||||
func (s *selector) delEntry(entry string) error {
|
||||
common.Log.Infof("deleting entry %s from %s", entry, s.spec.ipsetName)
|
||||
return s.ips.DelEntry(s.spec.ipsetName, entry)
|
||||
}
|
||||
|
||||
type selectorFn func(selector *selector) error
|
||||
|
||||
type selectorSet struct {
|
||||
ips ipset.Interface
|
||||
onNewSelector selectorFn
|
||||
users map[string]map[types.UID]struct{} // list of users per selector
|
||||
entries map[string]*selector
|
||||
}
|
||||
|
||||
func newSelectorSet(ips ipset.Interface, onNewSelector selectorFn) *selectorSet {
|
||||
return &selectorSet{
|
||||
ips: ips,
|
||||
onNewSelector: onNewSelector,
|
||||
users: make(map[string]map[types.UID]struct{}),
|
||||
entries: make(map[string]*selector)}
|
||||
}
|
||||
|
||||
func (ss *selectorSet) addToMatching(labelMap map[string]string, entry string) error {
|
||||
for _, s := range ss.entries {
|
||||
if s.matches(labelMap) {
|
||||
if err := s.addEntry(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *selectorSet) delFromMatching(labelMap map[string]string, entry string) error {
|
||||
for _, s := range ss.entries {
|
||||
if s.matches(labelMap) {
|
||||
if err := s.delEntry(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *selectorSet) deprovision(user types.UID, current, desired map[string]*selectorSpec) error {
|
||||
for key, spec := range current {
|
||||
if _, found := desired[key]; !found {
|
||||
delete(ss.users[key], user)
|
||||
if len(ss.users[key]) == 0 {
|
||||
common.Log.Infof("destroying ipset: %#v", spec)
|
||||
if err := ss.ips.Destroy(spec.ipsetName); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(ss.entries, key)
|
||||
delete(ss.users, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *selectorSet) provision(user types.UID, current, desired map[string]*selectorSpec) error {
|
||||
for key, spec := range desired {
|
||||
if _, found := current[key]; !found {
|
||||
if _, found := ss.users[key]; !found {
|
||||
common.Log.Infof("creating ipset: %#v", spec)
|
||||
if err := ss.ips.Create(spec.ipsetName, spec.ipsetType); err != nil {
|
||||
return err
|
||||
}
|
||||
selector := &selector{ss.ips, spec}
|
||||
if err := ss.onNewSelector(selector); err != nil {
|
||||
return err
|
||||
}
|
||||
ss.users[key] = make(map[types.UID]struct{})
|
||||
ss.entries[key] = selector
|
||||
}
|
||||
ss.users[key][user] = struct{}{}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
const (
|
||||
// This array:
|
||||
// * Must include only printable UTF8 characters that are represented with a single byte
|
||||
// * Must be at least of length 85 (`len("weave-") + l(2^160)/l(85)` equals 31, the maximum ipset name length)
|
||||
// * Must not include commas as those are treated specially by `ipset add` when adding a named set to a list:set
|
||||
// * Should not include space for readability
|
||||
// * Should not include single quote or backslash to be nice to shell users
|
||||
ShortNameSymbols = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(){}[]<>_$%^&*|/?.;:@#~"
|
||||
)
|
||||
|
||||
// sha1 hash an arbitrary string and represent it using the full range of
|
||||
// printable ascii characters
|
||||
func shortName(arbitrary string) string {
|
||||
symbols := []byte(ShortNameSymbols)
|
||||
sum := sha1.Sum([]byte(arbitrary))
|
||||
i := big.NewInt(0).SetBytes(sum[:])
|
||||
base := big.NewInt(int64(len(symbols)))
|
||||
zero := big.NewInt(0)
|
||||
result := make([]byte, 0)
|
||||
|
||||
for i.Cmp(zero) > 0 {
|
||||
remainder := new(big.Int).Mod(i, base)
|
||||
i.Sub(i, remainder)
|
||||
i.Div(i, base)
|
||||
result = append(result, symbols[remainder.Int64()])
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package ulogd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
func waitForExit(cmd *exec.Cmd) {
|
||||
if err := cmd.Wait(); err != nil {
|
||||
common.Log.Fatalf("ulogd terminated: %v", err)
|
||||
}
|
||||
common.Log.Fatal("ulogd terminated normally")
|
||||
}
|
||||
|
||||
func Start() error {
|
||||
cmd := exec.Command("/usr/sbin/ulogd", "-v")
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Pdeathsig: syscall.SIGKILL,
|
||||
}
|
||||
stdout, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
go io.Copy(os.Stdout, stdout)
|
||||
go waitForExit(cmd)
|
||||
return nil
|
||||
}
|
||||
+2
-2
@@ -9,6 +9,6 @@ to integrate Weave Net with [Kubernetes](http://kubernetes.io/).
|
||||
The Docker plugin runs automatically when you `weave launch`, provided your
|
||||
Docker daemon is version 1.9 or newer.
|
||||
|
||||
More detail on the Docker plugin [here](http://docs.weave.works/weave/latest_release/plugin.html).
|
||||
More detail on the Docker plugin [here](https://www.weave.works/docs/net/latest/plugin/).
|
||||
|
||||
More detail on the CNI plugin [here](https://github.com/weaveworks/weave/blob/master/site/plugin/cni-plugin-how-to.md).
|
||||
More detail on the CNI plugin [here](https://www.weave.works/docs/net/latest/cni-plugin/).
|
||||
+25
-14
@@ -54,34 +54,40 @@ func (i *Ipam) ReleasePool(poolID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Ipam) RequestAddress(poolID string, address net.IP, options map[string]string) (ip *net.IPNet, _ map[string]string, err error) {
|
||||
i.logReq("RequestAddress", poolID, address, options)
|
||||
defer func() { i.logRes("RequestAddress", err, ip) }()
|
||||
// If we pass magic string "_" to weave IPAM it stores the address under its own string
|
||||
if poolID == "weavepool" { // old-style
|
||||
ip, err = i.weave.AllocateIP("_")
|
||||
return
|
||||
}
|
||||
func splitPoolID(poolID string) (subnet, iprange *net.IPNet, err error) {
|
||||
parts := strings.Split(poolID, "-")
|
||||
if len(parts) != 3 || parts[0] != "weave" {
|
||||
err = fmt.Errorf("Unrecognized pool ID: %s", poolID)
|
||||
return
|
||||
}
|
||||
var subnet, iprange *net.IPNet
|
||||
if _, subnet, err = net.ParseCIDR(parts[1]); err != nil {
|
||||
return
|
||||
}
|
||||
if _, iprange, err = net.ParseCIDR(parts[2]); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (i *Ipam) RequestAddress(poolID string, address net.IP, options map[string]string) (ip *net.IPNet, _ map[string]string, err error) {
|
||||
i.logReq("RequestAddress", poolID, address, options)
|
||||
defer func() { i.logRes("RequestAddress", err, ip) }()
|
||||
if poolID == "weavepool" { // old-style
|
||||
ip, err = i.weave.AllocateIP(api.NoContainerID)
|
||||
return
|
||||
}
|
||||
subnet, iprange, err := splitPoolID(poolID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if address != nil { // try to claim specific address requested
|
||||
ip = &net.IPNet{IP: address, Mask: subnet.Mask}
|
||||
if err = i.weave.ClaimIP("_", ip); err != nil {
|
||||
if err = i.weave.ClaimIP(api.NoContainerID, ip); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if _, iprange, err = net.ParseCIDR(parts[2]); err != nil {
|
||||
return
|
||||
}
|
||||
// We are lying slightly to IPAM here: the range is not a subnet
|
||||
if ip, err = i.weave.AllocateIPInSubnet("_", iprange); err != nil {
|
||||
if ip, err = i.weave.AllocateIPInSubnet(api.NoContainerID, iprange); err != nil {
|
||||
return
|
||||
}
|
||||
ip.Mask = subnet.Mask // fix up the subnet we lied about
|
||||
@@ -91,6 +97,11 @@ func (i *Ipam) RequestAddress(poolID string, address net.IP, options map[string]
|
||||
|
||||
func (i *Ipam) ReleaseAddress(poolID string, address net.IP) error {
|
||||
i.logReq("ReleaseAddress", poolID, address)
|
||||
if subnet, _, err := splitPoolID(poolID); err != nil {
|
||||
return err
|
||||
} else if address.Equal(subnet.IP) { // is it the gateway address we faked earlier?
|
||||
return nil
|
||||
}
|
||||
return i.weave.ReleaseIPsFor(address.String())
|
||||
}
|
||||
|
||||
|
||||
+67
-112
@@ -5,7 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/appc/cni/pkg/ipam"
|
||||
"github.com/appc/cni/pkg/skel"
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netns"
|
||||
weaveapi "github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/common"
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
ipamplugin "github.com/weaveworks/weave/plugin/ipam"
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ func NewCNIPlugin(weave *weaveapi.Client) *CNIPlugin {
|
||||
|
||||
func loadNetConf(bytes []byte) (*NetConf, error) {
|
||||
n := &NetConf{
|
||||
BrName: "weave",
|
||||
BrName: weavenet.WeaveBridgeName,
|
||||
}
|
||||
if err := json.Unmarshal(bytes, n); err != nil {
|
||||
return nil, fmt.Errorf("failed to load netconf: %v", err)
|
||||
@@ -40,6 +40,19 @@ func loadNetConf(bytes []byte) (*NetConf, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *CNIPlugin) getIP(ipamType string, args *skel.CmdArgs) (result *types.Result, err error) {
|
||||
// Default IPAM is Weave's own
|
||||
if ipamType == "" {
|
||||
result, err = ipamplugin.NewIpam(c.weave).Allocate(args)
|
||||
} else {
|
||||
result, err = ipam.ExecAdd(ipamType, args.StdinData)
|
||||
}
|
||||
if err == nil && result.IP4 == nil {
|
||||
return nil, fmt.Errorf("IPAM plugin failed to allocate IP address")
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (c *CNIPlugin) CmdAdd(args *skel.CmdArgs) error {
|
||||
conf, err := loadNetConf(args.StdinData)
|
||||
if err != nil {
|
||||
@@ -53,9 +66,37 @@ func (c *CNIPlugin) CmdAdd(args *skel.CmdArgs) error {
|
||||
return fmt.Errorf("IP Masquerading functionality not supported")
|
||||
}
|
||||
|
||||
result, err := c.getIP(conf.IPAM.Type, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to allocate IP address: %s", err)
|
||||
}
|
||||
|
||||
// If config says nothing about routes or gateway, default one will be via the bridge
|
||||
if result.IP4.Routes == nil && result.IP4.Gateway == nil {
|
||||
bridgeIP, err := weavenet.FindBridgeIP(conf.BrName, &result.IP4.IP)
|
||||
if err == weavenet.ErrBridgeNoIP {
|
||||
bridgeArgs := *args
|
||||
bridgeArgs.ContainerID = "weave:expose"
|
||||
bridgeIPResult, err := c.getIP(conf.IPAM.Type, &bridgeArgs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to allocate IP address for bridge: %s", err)
|
||||
}
|
||||
if err := assignBridgeIP(conf.BrName, bridgeIPResult.IP4.IP); err != nil {
|
||||
return fmt.Errorf("unable to assign IP address to bridge: %s", err)
|
||||
}
|
||||
if err := weavenet.ExposeNAT(bridgeIPResult.IP4.IP); err != nil {
|
||||
return fmt.Errorf("unable to create NAT rules: %s", err)
|
||||
}
|
||||
bridgeIP = bridgeIPResult.IP4.IP.IP
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
result.IP4.Gateway = bridgeIP
|
||||
}
|
||||
|
||||
ns, err := netns.GetFromPath(args.Netns)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("error accessing namespace %q: %s", args.Netns, err)
|
||||
}
|
||||
defer ns.Close()
|
||||
|
||||
@@ -69,93 +110,36 @@ func (c *CNIPlugin) CmdAdd(args *skel.CmdArgs) error {
|
||||
id = fmt.Sprintf("%x", data)
|
||||
}
|
||||
|
||||
local, err := createAndAttach(id, conf.BrName, conf.MTU)
|
||||
if err != nil {
|
||||
if err := weavenet.AttachContainer(args.Netns, id, args.IfName, conf.BrName, conf.MTU, false, []*net.IPNet{&result.IP4.IP}, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cleanup := func(err error) error {
|
||||
netlink.LinkDel(local)
|
||||
return err
|
||||
}
|
||||
guest, err := netlink.LinkByName(local.PeerName)
|
||||
if err != nil {
|
||||
return cleanup(err)
|
||||
}
|
||||
if err = netlink.LinkSetNsFd(guest, int(ns)); err != nil {
|
||||
return cleanup(fmt.Errorf("failed to move veth to container netns: %s", err))
|
||||
}
|
||||
|
||||
if err := netlink.LinkSetUp(local); err != nil {
|
||||
return cleanup(fmt.Errorf("unable to bring veth up: %s", err))
|
||||
}
|
||||
|
||||
var result *types.Result
|
||||
// Default IPAM is Weave's own
|
||||
if conf.IPAM.Type == "" {
|
||||
result, err = ipamplugin.NewIpam(c.weave).Allocate(args)
|
||||
} else {
|
||||
result, err = ipam.ExecAdd(conf.IPAM.Type, args.StdinData)
|
||||
}
|
||||
if err != nil {
|
||||
return cleanup(fmt.Errorf("unable to allocate IP address: %s", err))
|
||||
}
|
||||
if result.IP4 == nil {
|
||||
return cleanup(fmt.Errorf("IPAM plugin failed to allocate IP address"))
|
||||
}
|
||||
|
||||
// If config says nothing about routes or gateway, default one will be via the bridge
|
||||
if result.IP4.Routes == nil && result.IP4.Gateway == nil {
|
||||
bridgeIP, err := findBridgeIP(conf.BrName, result.IP4.IP)
|
||||
if err != nil {
|
||||
return cleanup(err)
|
||||
}
|
||||
result.IP4.Gateway = bridgeIP
|
||||
}
|
||||
|
||||
err = common.WithNetNS(ns, func() error {
|
||||
return setupGuestIP4(local.PeerName, args.IfName, result.IP4.IP, result.IP4.Gateway, result.IP4.Routes)
|
||||
})
|
||||
if err != nil {
|
||||
return cleanup(fmt.Errorf("error setting up interface: %s", err))
|
||||
if err := weavenet.WithNetNSLinkUnsafe(ns, args.IfName, func(link netlink.Link) error {
|
||||
return setupRoutes(link, args.IfName, result.IP4.IP, result.IP4.Gateway, result.IP4.Routes)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("error setting up routes: %s", err)
|
||||
}
|
||||
|
||||
result.DNS = conf.DNS
|
||||
return result.Print()
|
||||
}
|
||||
|
||||
func setupGuestIP4(origName, name string, ipnet net.IPNet, gw net.IP, routes []types.Route) error {
|
||||
guest, err := netlink.LinkByName(origName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = netlink.LinkSetName(guest, name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = netlink.LinkSetUp(guest); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = common.ConfigureARPCache(name); err != nil {
|
||||
return err
|
||||
}
|
||||
func setupRoutes(link netlink.Link, name string, ipnet net.IPNet, gw net.IP, routes []types.Route) error {
|
||||
var err error
|
||||
if routes == nil { // If config says nothing about routes, add a default one
|
||||
if !ipnet.Contains(gw) {
|
||||
// The bridge IP is not on the same subnet; add a specific route to it
|
||||
gw32 := &net.IPNet{IP: gw, Mask: mask32}
|
||||
if err = addRoute(guest, netlink.SCOPE_LINK, gw32, nil); err != nil {
|
||||
if err = weavenet.AddRoute(link, netlink.SCOPE_LINK, gw32, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
routes = []types.Route{{Dst: zeroNetwork}}
|
||||
}
|
||||
if err = netlink.AddrAdd(guest, &netlink.Addr{IPNet: &ipnet}); err != nil {
|
||||
return fmt.Errorf("failed to add IP address to %q: %v", name, err)
|
||||
}
|
||||
for _, r := range routes {
|
||||
if r.GW != nil {
|
||||
err = addRoute(guest, netlink.SCOPE_UNIVERSE, &r.Dst, r.GW)
|
||||
err = weavenet.AddRoute(link, netlink.SCOPE_UNIVERSE, &r.Dst, r.GW)
|
||||
} else {
|
||||
err = addRoute(guest, netlink.SCOPE_UNIVERSE, &r.Dst, gw)
|
||||
err = weavenet.AddRoute(link, netlink.SCOPE_UNIVERSE, &r.Dst, gw)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, name, err)
|
||||
@@ -164,37 +148,20 @@ func setupGuestIP4(origName, name string, ipnet net.IPNet, gw net.IP, routes []t
|
||||
return nil
|
||||
}
|
||||
|
||||
func addRoute(link netlink.Link, scope netlink.Scope, dst *net.IPNet, gw net.IP) error {
|
||||
err := netlink.RouteAdd(&netlink.Route{
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Scope: scope,
|
||||
Dst: dst,
|
||||
Gw: gw,
|
||||
})
|
||||
if os.IsExist(err) { // squash duplicate route errors
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func findBridgeIP(bridgeName string, subnet net.IPNet) (net.IP, error) {
|
||||
netdevs, err := common.GetBridgeNetDev(bridgeName)
|
||||
func assignBridgeIP(bridgeName string, ipnet net.IPNet) error {
|
||||
link, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to get netdev for %q bridge: %s", bridgeName, err)
|
||||
return err
|
||||
}
|
||||
if len(netdevs) == 0 {
|
||||
return nil, fmt.Errorf("Could not find %q bridge", bridgeName)
|
||||
}
|
||||
if len(netdevs[0].CIDRs) == 0 {
|
||||
return nil, fmt.Errorf("Bridge %q has no IP addresses", bridgeName)
|
||||
}
|
||||
for _, cidr := range netdevs[0].CIDRs {
|
||||
if subnet.Contains(cidr.IP) {
|
||||
return cidr.IP, nil
|
||||
if err := netlink.AddrAdd(link, &netlink.Addr{IPNet: &ipnet}); err != nil {
|
||||
// Treat as non-error if this address is already there
|
||||
// - maybe another copy of this program just added it
|
||||
if err == syscall.Errno(syscall.EEXIST) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to add IP address to %q: %v", bridgeName, err)
|
||||
}
|
||||
// None in the required subnet; just return the first one
|
||||
return netdevs[0].CIDRs[0].IP, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CNIPlugin) CmdDel(args *skel.CmdArgs) error {
|
||||
@@ -203,20 +170,8 @@ func (c *CNIPlugin) CmdDel(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
ns, err := netns.GetFromPath(args.Netns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ns.Close()
|
||||
err = common.WithNetNS(ns, func() error {
|
||||
link, err := netlink.LinkByName(args.IfName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return netlink.LinkDel(link)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error removing interface: %s", err)
|
||||
if _, err = weavenet.WithNetNS(args.Netns, "del-iface", args.IfName); err != nil {
|
||||
return fmt.Errorf("error removing interface %q: %s", args.IfName, err)
|
||||
}
|
||||
|
||||
// Default IPAM is Weave's own
|
||||
|
||||
+110
-85
@@ -2,35 +2,45 @@ package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/libnetwork/drivers/remote/api"
|
||||
"github.com/docker/libnetwork/netlabel"
|
||||
"github.com/docker/libnetwork/types"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
weaveapi "github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
"github.com/weaveworks/weave/common/odp"
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
"github.com/weaveworks/weave/plugin/skel"
|
||||
)
|
||||
|
||||
const (
|
||||
WeaveBridge = "weave"
|
||||
MulticastOption = "works.weave.multicast"
|
||||
)
|
||||
|
||||
type driver struct {
|
||||
scope string
|
||||
noMulticastRoute bool
|
||||
sync.RWMutex
|
||||
endpoints map[string]struct{}
|
||||
type network struct {
|
||||
isOurs bool
|
||||
hasMulticastRoute bool
|
||||
}
|
||||
|
||||
func New(client *docker.Client, weave *weaveapi.Client, scope string, noMulticastRoute bool) (skel.Driver, error) {
|
||||
type driver struct {
|
||||
name string
|
||||
scope string
|
||||
docker *docker.Client
|
||||
sync.RWMutex
|
||||
networks map[string]network
|
||||
}
|
||||
|
||||
func New(client *docker.Client, weave *weaveapi.Client, name, scope string) (skel.Driver, error) {
|
||||
driver := &driver{
|
||||
noMulticastRoute: noMulticastRoute,
|
||||
scope: scope,
|
||||
endpoints: make(map[string]struct{}),
|
||||
name: name,
|
||||
scope: scope,
|
||||
docker: client,
|
||||
networks: make(map[string]network),
|
||||
}
|
||||
|
||||
_, err := NewWatcher(client, weave, driver)
|
||||
@@ -53,25 +63,53 @@ func (driver *driver) GetCapabilities() (*api.GetCapabilityResponse, error) {
|
||||
|
||||
func (driver *driver) CreateNetwork(create *api.CreateNetworkRequest) error {
|
||||
driver.logReq("CreateNetwork", create, create.NetworkID)
|
||||
_, err := driver.setupNetworkInfo(create.NetworkID, true, stringOptions(create))
|
||||
return err
|
||||
}
|
||||
|
||||
// Deal with excessively-generic way the options get decoded from JSON
|
||||
func stringOptions(create *api.CreateNetworkRequest) map[string]string {
|
||||
if create.Options != nil {
|
||||
if data, found := create.Options[netlabel.GenericData]; found {
|
||||
if options, ok := data.(map[string]interface{}); ok {
|
||||
out := make(map[string]string, len(options))
|
||||
for key, value := range options {
|
||||
if str, ok := value.(string); ok {
|
||||
out[key] = str
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *driver) DeleteNetwork(delete *api.DeleteNetworkRequest) error {
|
||||
driver.logReq("DeleteNetwork", delete, delete.NetworkID)
|
||||
func (driver *driver) DeleteNetwork(delreq *api.DeleteNetworkRequest) error {
|
||||
driver.logReq("DeleteNetwork", delreq, delreq.NetworkID)
|
||||
driver.Lock()
|
||||
delete(driver.networks, delreq.NetworkID)
|
||||
driver.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *driver) CreateEndpoint(create *api.CreateEndpointRequest) (*api.CreateEndpointResponse, error) {
|
||||
driver.logReq("CreateEndpoint", create, create.EndpointID)
|
||||
endID := create.EndpointID
|
||||
common.Log.Debugf("interface %+v", create.Interface)
|
||||
|
||||
if create.Interface == nil {
|
||||
return nil, driver.error("CreateEndpoint", "Not supported: creating an interface from within CreateEndpoint")
|
||||
}
|
||||
driver.Lock()
|
||||
driver.endpoints[endID] = struct{}{}
|
||||
driver.Unlock()
|
||||
resp := &api.CreateEndpointResponse{}
|
||||
|
||||
// create veths. note we assume endpoint IDs are unique in the first 9 chars
|
||||
name, peerName := vethPair(create.EndpointID)
|
||||
if _, err := weavenet.CreateAndAttachVeth(name, peerName, weavenet.WeaveBridgeName, 0, false, nil); err != nil {
|
||||
return nil, driver.error("JoinEndpoint", "%s", err)
|
||||
}
|
||||
|
||||
// Send back the MAC address
|
||||
link, _ := netlink.LinkByName(peerName)
|
||||
resp := &api.CreateEndpointResponse{Interface: &api.EndpointInterface{MacAddress: link.Attrs().HardwareAddr.String()}}
|
||||
|
||||
driver.logRes("CreateEndpoint", resp)
|
||||
return resp, nil
|
||||
@@ -79,19 +117,20 @@ func (driver *driver) CreateEndpoint(create *api.CreateEndpointRequest) (*api.Cr
|
||||
|
||||
func (driver *driver) DeleteEndpoint(deleteReq *api.DeleteEndpointRequest) error {
|
||||
driver.logReq("DeleteEndpoint", deleteReq, deleteReq.EndpointID)
|
||||
driver.Lock()
|
||||
delete(driver.endpoints, deleteReq.EndpointID)
|
||||
driver.Unlock()
|
||||
name, _ := vethPair(deleteReq.EndpointID)
|
||||
veth := &netlink.Veth{LinkAttrs: netlink.LinkAttrs{Name: name}}
|
||||
if err := netlink.LinkDel(veth); err != nil {
|
||||
// Try again using the name construction from earlier plugin version,
|
||||
// in case user has upgraded with endpoints still extant
|
||||
veth.Name = "vethwl" + deleteReq.EndpointID[:5]
|
||||
if err2 := netlink.LinkDel(veth); err2 != nil {
|
||||
// Note we report the first error
|
||||
driver.warn("LeaveEndpoint", "unable to delete veth %q: %s", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *driver) HasEndpoint(endpointID string) bool {
|
||||
driver.Lock()
|
||||
_, found := driver.endpoints[endpointID]
|
||||
driver.Unlock()
|
||||
return found
|
||||
}
|
||||
|
||||
func (driver *driver) EndpointInfo(req *api.EndpointInfoRequest) (*api.EndpointInfoResponse, error) {
|
||||
driver.logReq("EndpointInfo", req, req.EndpointID)
|
||||
return &api.EndpointInfoResponse{Value: map[string]interface{}{}}, nil
|
||||
@@ -100,24 +139,19 @@ func (driver *driver) EndpointInfo(req *api.EndpointInfoRequest) (*api.EndpointI
|
||||
func (driver *driver) JoinEndpoint(j *api.JoinRequest) (*api.JoinResponse, error) {
|
||||
driver.logReq("JoinEndpoint", j, fmt.Sprintf("%s:%s to %s", j.NetworkID, j.EndpointID, j.SandboxKey))
|
||||
|
||||
local, err := createAndAttach(j.EndpointID, WeaveBridge, 0)
|
||||
network, err := driver.findNetworkInfo(j.NetworkID)
|
||||
if err != nil {
|
||||
return nil, driver.error("JoinEndpoint", "%s", err)
|
||||
}
|
||||
|
||||
if err := netlink.LinkSetUp(local); err != nil {
|
||||
return nil, driver.error("JoinEndpoint", "unable to bring up veth %s-%s: %s", local.Name, local.PeerName, err)
|
||||
}
|
||||
|
||||
ifname := &api.InterfaceName{
|
||||
SrcName: local.PeerName,
|
||||
DstPrefix: "ethwe",
|
||||
return nil, driver.error("JoinEndpoint", "unable to find network info: %s", err)
|
||||
}
|
||||
|
||||
_, peerName := vethPair(j.EndpointID)
|
||||
response := &api.JoinResponse{
|
||||
InterfaceName: ifname,
|
||||
InterfaceName: &api.InterfaceName{
|
||||
SrcName: peerName,
|
||||
DstPrefix: weavenet.VethName,
|
||||
},
|
||||
}
|
||||
if !driver.noMulticastRoute {
|
||||
if network.hasMulticastRoute {
|
||||
multicastRoute := api.StaticRoute{
|
||||
Destination: "224.0.0.0/4",
|
||||
RouteType: types.CONNECTED,
|
||||
@@ -128,53 +162,48 @@ func (driver *driver) JoinEndpoint(j *api.JoinRequest) (*api.JoinResponse, error
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// create and attach local name to the Weave bridge
|
||||
func createAndAttach(id, bridgeName string, mtu int) (*netlink.Veth, error) {
|
||||
maybeBridge, err := netlink.LinkByName(bridgeName)
|
||||
func (driver *driver) findNetworkInfo(id string) (network, error) {
|
||||
driver.Lock()
|
||||
network, found := driver.networks[id]
|
||||
driver.Unlock()
|
||||
if found {
|
||||
return network, nil
|
||||
}
|
||||
info, err := driver.docker.NetworkInfo(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`bridge "%s" not present; did you launch weave?`, bridgeName)
|
||||
return network, err
|
||||
}
|
||||
return driver.setupNetworkInfo(id, info.Driver == driver.name, info.Options)
|
||||
}
|
||||
|
||||
local := vethPair(id[:5])
|
||||
if mtu == 0 {
|
||||
local.Attrs().MTU = maybeBridge.Attrs().MTU
|
||||
} else {
|
||||
local.Attrs().MTU = mtu
|
||||
}
|
||||
if err := netlink.LinkAdd(local); err != nil {
|
||||
return nil, fmt.Errorf(`could not create veth pair %s-%s: %s`, local.Name, local.PeerName, err)
|
||||
}
|
||||
func (driver *driver) setupNetworkInfo(id string, isOurs bool, options map[string]string) (network, error) {
|
||||
network := network{isOurs: isOurs}
|
||||
if isOurs {
|
||||
for key, value := range options {
|
||||
switch key {
|
||||
case MulticastOption:
|
||||
if value == "" { // interpret "--opt works.weave.multicast" as "turn it on"
|
||||
network.hasMulticastRoute = true
|
||||
} else {
|
||||
var err error
|
||||
if network.hasMulticastRoute, err = strconv.ParseBool(value); err != nil {
|
||||
return network, fmt.Errorf("unrecognized value %q for option %s", value, key)
|
||||
}
|
||||
|
||||
switch maybeBridge.(type) {
|
||||
case *netlink.Bridge:
|
||||
if err := netlink.LinkSetMasterByIndex(local, maybeBridge.Attrs().Index); err != nil {
|
||||
return nil, fmt.Errorf(`unable to set master of %s: %s`, local.Name, err)
|
||||
}
|
||||
default:
|
||||
driver.warn("setupNetworkInfo", "unrecognized option: %s", key)
|
||||
}
|
||||
}
|
||||
case *netlink.GenericLink:
|
||||
if maybeBridge.Type() != "openvswitch" {
|
||||
return nil, fmt.Errorf(`device "%s" is of type "%s"`, bridgeName, maybeBridge.Type())
|
||||
}
|
||||
if err := odp.AddDatapathInterface(bridgeName, local.Name); err != nil {
|
||||
return nil, fmt.Errorf(`failed to attach %s to device "%s": %s`, local.Name, bridgeName, err)
|
||||
}
|
||||
case *netlink.Device:
|
||||
// Assume it's our openvswitch device, and the kernel has not been updated to report the kind.
|
||||
if err := odp.AddDatapathInterface(bridgeName, local.Name); err != nil {
|
||||
return nil, fmt.Errorf(`failed to attach %s to device "%s": %s`, local.Name, bridgeName, err)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf(`device "%s" is not a bridge`, bridgeName)
|
||||
}
|
||||
return local, nil
|
||||
driver.Lock()
|
||||
driver.networks[id] = network
|
||||
driver.Unlock()
|
||||
return network, nil
|
||||
}
|
||||
|
||||
func (driver *driver) LeaveEndpoint(leave *api.LeaveRequest) error {
|
||||
driver.logReq("LeaveEndpoint", leave, fmt.Sprintf("%s:%s", leave.NetworkID, leave.EndpointID))
|
||||
|
||||
local := vethPair(leave.EndpointID[:5])
|
||||
if err := netlink.LinkDel(local); err != nil {
|
||||
driver.warn("LeaveEndpoint", "unable to delete veth: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -188,13 +217,9 @@ func (driver *driver) DiscoverDelete(disco *api.DiscoveryNotification) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ===
|
||||
|
||||
func vethPair(suffix string) *netlink.Veth {
|
||||
return &netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{Name: "vethwl" + suffix},
|
||||
PeerName: "vethwg" + suffix,
|
||||
}
|
||||
func vethPair(id string) (string, string) {
|
||||
// IFNAMSIZ is buffer length; subtract 6 for "vethwl" and 1 for terminating nul
|
||||
return "vethwl" + id[:unix.IFNAMSIZ-7], "vethwg" + id[:unix.IFNAMSIZ-7]
|
||||
}
|
||||
|
||||
// logging
|
||||
|
||||
+11
-2
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
weaveapi "github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -32,13 +33,21 @@ func (w *watcher) ContainerStarted(id string) {
|
||||
w.driver.warn("ContainerStarted", "error inspecting container %s: %s", id, err)
|
||||
return
|
||||
}
|
||||
// check that it's on our network, via the endpointID
|
||||
// check that it's on our network
|
||||
for _, net := range info.NetworkSettings.Networks {
|
||||
if w.driver.HasEndpoint(net.EndpointID) {
|
||||
network, err := w.driver.findNetworkInfo(net.NetworkID)
|
||||
if err != nil {
|
||||
w.driver.warn("ContainerStarted", "unable to find network %s info: %s", net.NetworkID, err)
|
||||
continue
|
||||
}
|
||||
if network.isOurs {
|
||||
fqdn := fmt.Sprintf("%s.%s", info.Config.Hostname, info.Config.Domainname)
|
||||
if err := w.weave.RegisterWithDNS(id, fqdn, net.IPAddress); err != nil {
|
||||
w.driver.warn("ContainerStarted", "unable to register %s with weaveDNS: %s", id, err)
|
||||
}
|
||||
if _, err := weavenet.WithNetNSByPid(info.State.Pid, "configure-arp", weavenet.VethName); err != nil {
|
||||
w.driver.warn("ContainerStarted", "unable to configure interfaces: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"k8s.io/client-go/kubernetes"
|
||||
api "k8s.io/client-go/pkg/api/v1"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func getKubePeers() ([]string, error) {
|
||||
config, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodeList, err := c.Nodes().List(api.ListOptions{})
|
||||
if err != nil {
|
||||
// Fallback for cases (e.g. from kube-up.sh) where kube-proxy is not running on master
|
||||
config.Host = "http://localhost:8080"
|
||||
log.Print("error contacting APIServer: ", err, "; trying with fallback: ", config.Host)
|
||||
c, err = kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodeList, err = c.Nodes().List(api.ListOptions{})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addresses := make([]string, 0, len(nodeList.Items))
|
||||
for _, peer := range nodeList.Items {
|
||||
for _, addr := range peer.Status.Addresses {
|
||||
if addr.Type == "InternalIP" {
|
||||
addresses = append(addresses, addr.Address)
|
||||
}
|
||||
}
|
||||
}
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
peers, err := getKubePeers()
|
||||
if err != nil {
|
||||
log.Fatalf("Could not get peers: %v", err)
|
||||
}
|
||||
for _, addr := range peers {
|
||||
fmt.Println(addr)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
FROM weaveworks/weaveexec
|
||||
FROM DOCKERHUB_USER/weaveARCH_EXT
|
||||
ADD ./plugin /home/weave/
|
||||
ENTRYPOINT ["/home/weave/plugin"]
|
||||
Loaded 100 of 318 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user